diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 1eb1551..88b491b 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -5,12 +5,12 @@ name: Test
on:
pull_request:
paths-ignore:
- - 'README.md'
+ - README.md
push:
paths-ignore:
- - 'README.md'
+ - README.md
# For systems with an upstream API that could drift unexpectedly (like most SaaS systems, etc.),
- # we recommend testing at a regular interval not necessarily tied to code changes. This will
+ # we recommend testing at a regular interval not necessarily tied to code changes. This will
# ensure you are alerted to something breaking due to an API change, even if the code did not
# change.
# schedule:
@@ -65,7 +65,9 @@ jobs:
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v4.2.1
-
+ - '0.12.*'
+ - '0.13.*'
+ - '0.14.*'
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
@@ -93,12 +95,12 @@ jobs:
matrix:
# list whatever Terraform versions here you would like to support
terraform:
- - '0.12.*'
- - '0.13.*'
- - '0.14.*'
- - '0.15.*'
- - '1.0.*'
- - '1.1.*'
+ - 0.15.*
+ - 1.0.*
+ - 1.1.*
+ - 1.2.*
+ - 1.3.*
+ - 1.4.*
services:
remotehost:
image: ghcr.io/tenstad/remotehost:${{ github.sha }}
@@ -109,7 +111,7 @@ jobs:
steps:
- name: Install unzip
run: apt-get update && apt-get install -y unzip
-
+
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ matrix.terraform }}
diff --git a/GNUmakefile b/GNUmakefile
index 81a632d..8ca83ba 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -9,7 +9,7 @@ CONTAINER_NETWORK ?= $(shell \
; fi)
.PHONY: test
-default: test
+default: test doc
# Start host containers used for playground and testing
hosts: clean
@@ -32,7 +32,7 @@ ifeq ($(DEVCONTAINER),true)
else
$(CONTAINER_RUNTIME) run --rm --net remote -v ~/go:/go:z -v $(PWD):/provider:z --workdir /provider \
-e "TF_LOG=INFO" -e "TF_ACC=1" -e "TF_ACC_TERRAFORM_VERSION=1.0.11" -e "TESTARGS=$(TESTARGS)" \
- golang:1.22 bash tests/test.sh
+ docker.io/golang:1.22 bash tests/test.sh
endif
# Install provider in playground
@@ -45,4 +45,4 @@ install:
go build -ldflags="-s -w -X main.version=99.0.0" -o $(BIN_PATH)
doc:
- go generate
+ cd tools && go generate
diff --git a/docs/data-sources/file.md b/docs/data-sources/file.md
index dc456ec..61d7fb5 100644
--- a/docs/data-sources/file.md
+++ b/docs/data-sources/file.md
@@ -1,6 +1,6 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
-page_title: "remote_file Data Source - terraform-provider-remote"
+page_title: "remote_file Data Source - remote"
subcategory: ""
description: |-
File on remote host.
@@ -46,14 +46,14 @@ data "remote_file" "server2_hosts" {
### Optional
-- `conn` (Block List, Max: 1) Connection to host where files are located. (see [below for nested schema](#nestedblock--conn))
+- `conn` (Block List) Connection to host where files are located. (see [below for nested schema](#nestedblock--conn))
### Read-Only
- `content` (String) Content of file.
- `group` (String) Group ID (GID) of file owner.
- `group_name` (String) Group name of file owner.
-- `id` (String) The ID of this resource.
+- `id` (String) ID.
- `owner` (String) User ID (UID) of file owner.
- `owner_name` (String) User name of file owner.
- `permissions` (String) Permissions of file (in octal form).
@@ -68,11 +68,11 @@ Required:
Optional:
-- `agent` (Boolean) Use a local SSH agent to login to the remote host. Defaults to `false`.
+- `agent` (Boolean) Use a local SSH agent to login to the remote host.
- `password` (String, Sensitive) The pasword for the user on the remote host.
-- `port` (Number) The ssh port on the remote host. Defaults to `22`.
+- `port` (Number) The ssh port on the remote host.
- `private_key` (String, Sensitive) The private key used to login to the remote host.
- `private_key_env_var` (String) The name of the local environment variable containing the private key used to login to the remote host.
- `private_key_path` (String) The local path to the private key used to login to the remote host.
-- `sudo` (Boolean) Use sudo to gain access to file. Defaults to `false`.
+- `sudo` (Boolean) Use sudo to gain access to file.
- `timeout` (Number) The maximum amount of time, in milliseconds, for the TCP connection to establish. Timeout of zero means no timeout.
diff --git a/docs/index.md b/docs/index.md
index feb1d2c..8d77ca7 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -52,8 +52,8 @@ provider "remote" {
### Optional
-- `conn` (Block List, Max: 1) Default connection to host where files are located. Can be overridden in resources and data sources. (see [below for nested schema](#nestedblock--conn))
-- `max_sessions` (Number) Maximum number of open sessions in each host connection. Defaults to `3`.
+- `conn` (Block List) Default connection to host where files are located. Can be overridden in resources and data sources. (see [below for nested schema](#nestedblock--conn))
+- `max_sessions` (Number) Maximum number of open sessions in each host connection.
### Nested Schema for `conn`
@@ -65,11 +65,11 @@ Required:
Optional:
-- `agent` (Boolean) Use a local SSH agent to login to the remote host. Defaults to `false`.
+- `agent` (Boolean) Use a local SSH agent to login to the remote host.
- `password` (String, Sensitive) The pasword for the user on the remote host.
-- `port` (Number) The ssh port on the remote host. Defaults to `22`.
+- `port` (Number) The ssh port on the remote host.
- `private_key` (String, Sensitive) The private key used to login to the remote host.
- `private_key_env_var` (String) The name of the local environment variable containing the private key used to login to the remote host.
- `private_key_path` (String) The local path to the private key used to login to the remote host.
-- `sudo` (Boolean) Use sudo to gain access to file. Defaults to `false`.
+- `sudo` (Boolean) Use sudo to gain access to file.
- `timeout` (Number) The maximum amount of time, in milliseconds, for the TCP connection to establish. Timeout of zero means no timeout.
diff --git a/docs/resources/file.md b/docs/resources/file.md
index 2c52f97..3aa6eed 100644
--- a/docs/resources/file.md
+++ b/docs/resources/file.md
@@ -1,6 +1,6 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
-page_title: "remote_file Resource - terraform-provider-remote"
+page_title: "remote_file Resource - remote"
subcategory: ""
description: |-
File on remote host.
@@ -57,16 +57,16 @@ resource "remote_file" "server2_bashrc" {
### Optional
-- `conn` (Block List, Max: 1) Connection to host where files are located. (see [below for nested schema](#nestedblock--conn))
+- `conn` (Block List) Connection to host where files are located. (see [below for nested schema](#nestedblock--conn))
- `group` (String) Group ID (GID) of file owner. Mutually exclusive with `group_name`.
- `group_name` (String) Group name of file owner. Mutually exclusive with `group`.
- `owner` (String) User ID (UID) of file owner. Mutually exclusive with `owner_name`.
- `owner_name` (String) User name of file owner. Mutually exclusive with `owner`.
-- `permissions` (String) Permissions of file (in octal form). Defaults to `0644`.
+- `permissions` (String) Permissions of file (in octal form).
### Read-Only
-- `id` (String) The ID of this resource.
+- `id` (String) ID.
### Nested Schema for `conn`
@@ -78,11 +78,11 @@ Required:
Optional:
-- `agent` (Boolean) Use a local SSH agent to login to the remote host. Defaults to `false`.
+- `agent` (Boolean) Use a local SSH agent to login to the remote host.
- `password` (String, Sensitive) The pasword for the user on the remote host.
-- `port` (Number) The ssh port on the remote host. Defaults to `22`.
+- `port` (Number) The ssh port on the remote host.
- `private_key` (String, Sensitive) The private key used to login to the remote host.
- `private_key_env_var` (String) The name of the local environment variable containing the private key used to login to the remote host.
- `private_key_path` (String) The local path to the private key used to login to the remote host.
-- `sudo` (Boolean) Use sudo to gain access to file. Defaults to `false`.
+- `sudo` (Boolean) Use sudo to gain access to file.
- `timeout` (Number) The maximum amount of time, in milliseconds, for the TCP connection to establish. Timeout of zero means no timeout.
diff --git a/go.mod b/go.mod
index 903cc85..fa05b53 100644
--- a/go.mod
+++ b/go.mod
@@ -1,85 +1,74 @@
module github.com/tenstad/terraform-provider-remote
-go 1.22
+go 1.22.7
+
+toolchain go1.23.1
require (
github.com/bramvdbogaerde/go-scp v1.5.0
- github.com/hashicorp/terraform-plugin-docs v0.19.4
- github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0
github.com/pkg/sftp v1.13.6
golang.org/x/crypto v0.28.0
)
require (
- github.com/BurntSushi/toml v1.2.1 // indirect
- github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect
- github.com/Masterminds/goutils v1.1.1 // indirect
- github.com/Masterminds/semver/v3 v3.2.0 // indirect
- github.com/Masterminds/sprig/v3 v3.2.3 // indirect
+ github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
+ github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 // indirect
+ github.com/kr/pretty v0.3.0 // indirect
+ github.com/rogpeppe/go-internal v1.12.0 // indirect
+ github.com/stretchr/testify v1.8.2 // indirect
+)
+
+require (
github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // indirect
github.com/agext/levenshtein v1.2.2 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
- github.com/armon/go-radix v1.0.0 // indirect
- github.com/bgentry/speakeasy v0.1.0 // indirect
- github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
- github.com/google/uuid v1.6.0 // indirect
- github.com/hashicorp/cli v1.1.6 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-checkpoint v0.5.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect
- github.com/hashicorp/go-hclog v1.5.0 // indirect
+ github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
- github.com/hashicorp/go-plugin v1.6.0 // indirect
+ github.com/hashicorp/go-plugin v1.6.1 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
- github.com/hashicorp/hc-install v0.7.0 // indirect
- github.com/hashicorp/hcl/v2 v2.20.1 // indirect
+ github.com/hashicorp/hc-install v0.8.0 // indirect
+ github.com/hashicorp/hcl/v2 v2.21.0 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-exec v0.21.0 // indirect
github.com/hashicorp/terraform-json v0.22.1 // indirect
- github.com/hashicorp/terraform-plugin-go v0.23.0 // indirect
+ github.com/hashicorp/terraform-plugin-framework v1.12.0
+ github.com/hashicorp/terraform-plugin-framework-validators v0.14.0
+ github.com/hashicorp/terraform-plugin-go v0.24.0
github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
+ github.com/hashicorp/terraform-plugin-testing v1.10.0
github.com/hashicorp/terraform-registry-address v0.2.3 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
- github.com/huandu/xstrings v1.3.3 // indirect
- github.com/imdario/mergo v0.3.15 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
- github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/oklog/run v1.0.0 // indirect
- github.com/posener/complete v1.2.3 // indirect
- github.com/shopspring/decimal v1.3.1 // indirect
- github.com/spf13/cast v1.5.0 // indirect
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
- github.com/yuin/goldmark v1.7.1 // indirect
- github.com/yuin/goldmark-meta v1.1.0 // indirect
- github.com/zclconf/go-cty v1.14.4 // indirect
- go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect
- golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
- golang.org/x/mod v0.17.0 // indirect
- golang.org/x/net v0.25.0 // indirect
+ github.com/zclconf/go-cty v1.15.0 // indirect
+ golang.org/x/mod v0.19.0 // indirect
+ golang.org/x/net v0.26.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/appengine v1.6.8 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
- google.golang.org/grpc v1.63.2 // indirect
- google.golang.org/protobuf v1.34.0 // indirect
- gopkg.in/yaml.v2 v2.3.0 // indirect
- gopkg.in/yaml.v3 v3.0.1 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
+ google.golang.org/grpc v1.66.2 // indirect
+ google.golang.org/protobuf v1.34.2 // indirect
)
diff --git a/go.sum b/go.sum
index 89bce26..ffae372 100644
--- a/go.sum
+++ b/go.sum
@@ -1,15 +1,5 @@
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
-github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
-github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
-github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0=
-github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc=
-github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
-github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
-github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
-github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
-github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
-github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg=
@@ -19,18 +9,13 @@ github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki
github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
-github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
-github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
-github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
-github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
-github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
-github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bramvdbogaerde/go-scp v1.5.0 h1:a9BinAjTfQh273eh7vd3qUgmBC+bx+3TRDtkZWmIpzM=
github.com/bramvdbogaerde/go-scp v1.5.0/go.mod h1:on2aH5AxaFb2G0N5Vsdy6B0Ml7k9HuHSwfo1y0QzAbQ=
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -41,8 +26,6 @@ github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FM
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
-github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
-github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
@@ -62,11 +45,6 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
-github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/hashicorp/cli v1.1.6 h1:CMOV+/LJfL1tXCOKrgAX0uRKnzjj/mpmqNXloRSy2K8=
-github.com/hashicorp/cli v1.1.6/go.mod h1:MPon5QYlgjjo0BSoAiN0ESeT5fRzDjVRp+uioJ0piz4=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -77,47 +55,47 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI=
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs=
-github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
-github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
-github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
+github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
-github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A=
-github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI=
+github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI=
+github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0=
+github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
+github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
-github.com/hashicorp/hc-install v0.7.0 h1:Uu9edVqjKQxxuD28mR5TikkKDd/p55S8vzPC1659aBk=
-github.com/hashicorp/hc-install v0.7.0/go.mod h1:ELmmzZlGnEcqoUMKUuykHaPCIR1sYLYX+KSggWSKZuA=
-github.com/hashicorp/hcl/v2 v2.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdxtc=
-github.com/hashicorp/hcl/v2 v2.20.1/go.mod h1:TZDqQ4kNKCbh1iJp99FdPiUaVDDUPivbqxZulxDYqL4=
+github.com/hashicorp/hc-install v0.8.0 h1:LdpZeXkZYMQhoKPCecJHlKvUkQFixN/nvyR1CdfOLjI=
+github.com/hashicorp/hc-install v0.8.0/go.mod h1:+MwJYjDfCruSD/udvBmRB22Nlkwwkwf5sAB6uTIhSaU=
+github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit14=
+github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ=
github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg=
github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec=
github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A=
-github.com/hashicorp/terraform-plugin-docs v0.19.4 h1:G3Bgo7J22OMtegIgn8Cd/CaSeyEljqjH3G39w28JK4c=
-github.com/hashicorp/terraform-plugin-docs v0.19.4/go.mod h1:4pLASsatTmRynVzsjEhbXZ6s7xBlUw/2Kt0zfrq8HxA=
-github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/125t+AAurhqphJ2Co=
-github.com/hashicorp/terraform-plugin-go v0.23.0/go.mod h1:1E3Cr9h2vMlahWMbsSEcNrOCxovCZhOOIXjFHbjc/lQ=
+github.com/hashicorp/terraform-plugin-framework v1.12.0 h1:7HKaueHPaikX5/7cbC1r9d1m12iYHY+FlNZEGxQ42CQ=
+github.com/hashicorp/terraform-plugin-framework v1.12.0/go.mod h1:N/IOQ2uYjW60Jp39Cp3mw7I/OpC/GfZ0385R0YibmkE=
+github.com/hashicorp/terraform-plugin-framework-validators v0.14.0 h1:3PCn9iyzdVOgHYOBmncpSSOxjQhCTYmc+PGvbdlqSaI=
+github.com/hashicorp/terraform-plugin-framework-validators v0.14.0/go.mod h1:LwDKNdzxrDY/mHBrlC6aYfE2fQ3Dk3gaJD64vNiXvo4=
+github.com/hashicorp/terraform-plugin-go v0.24.0 h1:2WpHhginCdVhFIrWHxDEg6RBn3YaWzR2o6qUeIEat2U=
+github.com/hashicorp/terraform-plugin-go v0.24.0/go.mod h1:tUQ53lAsOyYSckFGEefGC5C8BAaO0ENqzFd3bQeuYQg=
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 h1:kJiWGx2kiQVo97Y5IOGR4EMcZ8DtMswHhUuFibsCQQE=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0/go.mod h1:sl/UoabMc37HA6ICVMmGO+/0wofkVIRxf+BMb/dnoIg=
+github.com/hashicorp/terraform-plugin-testing v1.10.0 h1:2+tmRNhvnfE4Bs8rB6v58S/VpqzGC6RCh9Y8ujdn+aw=
+github.com/hashicorp/terraform-plugin-testing v1.10.0/go.mod h1:iWRW3+loP33WMch2P/TEyCxxct/ZEcCGMquSLSCVsrc=
github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI=
github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM=
github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ=
github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
-github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
-github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
-github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
-github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
-github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
@@ -142,9 +120,6 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
-github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
-github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
@@ -153,7 +128,6 @@ github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
@@ -164,25 +138,16 @@ github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
-github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
-github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
-github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
-github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
-github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
-github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@@ -198,35 +163,25 @@ github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=
-github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
-github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
-github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
-github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8=
-github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
-github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI=
-github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
-go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw=
-go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU=
+github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ=
+github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
+github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
+github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
-golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
-golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
-golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
-golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
+golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
-golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
-golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
-golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
+golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -244,14 +199,12 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -272,22 +225,20 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
-google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
-google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
+google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo=
+google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4=
-google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
-gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/provider/connection.go b/internal/provider/connection.go
index edc0c5f..f1dc278 100644
--- a/internal/provider/connection.go
+++ b/internal/provider/connection.go
@@ -1,173 +1,132 @@
package provider
import (
- "context"
"fmt"
"net"
"os"
+ "strconv"
+ "strings"
"time"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/types"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
)
-var connectionSchemaResource = &schema.Resource{
- Schema: map[string]*schema.Schema{
- "host": {
- Type: schema.TypeString,
- Required: true,
- ForceNew: true,
- Description: "The remote host.",
- },
- "port": {
- Type: schema.TypeInt,
- Optional: true,
- Default: 22,
- ForceNew: true,
- Description: "The ssh port on the remote host.",
- },
- "timeout": {
- Type: schema.TypeInt,
- Optional: true,
- Description: "The maximum amount of time, in milliseconds, for the TCP connection to establish. Timeout of zero means no timeout.",
- },
- "user": {
- Type: schema.TypeString,
- Required: true,
- Description: "The user on the remote host.",
- },
- "sudo": {
- Type: schema.TypeBool,
- Optional: true,
- Default: false,
- Description: "Use sudo to gain access to file.",
- },
- "agent": {
- Type: schema.TypeBool,
- Optional: true,
- Default: false,
- Description: "Use a local SSH agent to login to the remote host.",
- },
- "password": {
- Type: schema.TypeString,
- Optional: true,
- Sensitive: true,
- Description: "The pasword for the user on the remote host.",
- },
- "private_key": {
- Type: schema.TypeString,
- Optional: true,
- Sensitive: true,
- Description: "The private key used to login to the remote host.",
- },
- "private_key_path": {
- Type: schema.TypeString,
- Optional: true,
- Description: "The local path to the private key used to login to the remote host.",
- },
- "private_key_env_var": {
- Type: schema.TypeString,
- Optional: true,
- Description: "The name of the local environment variable containing the private key used to login to the remote host.",
- },
- },
+type ConnectionResourceModel struct {
+ Host types.String `tfsdk:"host"`
+ Port types.Int64 `tfsdk:"port"`
+ Timeout types.Int64 `tfsdk:"timeout"`
+ User types.String `tfsdk:"user"`
+ Sudo types.Bool `tfsdk:"sudo"`
+ Agent types.Bool `tfsdk:"agent"`
+ Password types.String `tfsdk:"password"`
+ PrivateKey types.String `tfsdk:"private_key"`
+ PrivateKeyPath types.String `tfsdk:"private_key_path"`
+ PrivateKeyEnvVar types.String `tfsdk:"private_key_env_var"`
}
-func ConnectionFromResourceData(ctx context.Context, d *schema.ResourceData) (string, *ssh.ClientConfig, error) {
- if _, ok := d.GetOk("conn"); !ok {
- return "", nil, fmt.Errorf("resouce does not have a connection configured")
- }
+// Validate validates required and defaulted fields
+func (m *ConnectionResourceModel) Validate() diag.Diagnostics {
+ dia := diag.Diagnostics{}
- host, err := Get[string](d, "conn.0.host")
- if err != nil {
- return "", nil, err
- }
+ // if m.Host.IsNull() || m.Host.IsUnknown() {
+ // dia.AddAttributeError(path.Root("conn").AtName("host"), "Client Error", "Host is unknown")
+ // }
- port, err := Get[int](d, "conn.0.port")
- if err != nil {
- return "", nil, err
- }
+ // if m.Port.IsNull() || m.Port.IsUnknown() {
+ // dia.AddAttributeError(path.Root("conn").AtName("port"), "Client Error", "Port is unknown")
+ // }
+
+ // if m.User.IsNull() || m.User.IsUnknown() {
+ // dia.AddAttributeError(path.Root("conn").AtName("user"), "Client Error", "User is unknown")
+ // }
+
+ // if m.Sudo.IsNull() || m.Sudo.IsUnknown() {
+ // dia.AddAttributeError(path.Root("conn").AtName("sudo"), "Client Error", "Sudo is unknown")
+ // }
- user, err := Get[string](d, "conn.0.user")
- if err != nil {
- return "", nil, err
+ // if m.Agent.IsNull() || m.Agent.IsUnknown() {
+ // dia.AddAttributeError(path.Root("conn").AtName("agent"), "Client Error", "Agent is unknown")
+ // }
+
+ return dia
+}
+
+func (d *ConnectionResourceModel) ConnectionHash() string {
+ elements := []string{
+ d.Host.ValueString(),
+ d.User.ValueString(),
+ strconv.Itoa(int(d.Port.ValueInt64())),
+ d.Password.ValueString(),
+ d.PrivateKey.ValueString(),
+ d.PrivateKeyPath.ValueString(),
+ strconv.FormatBool(d.Agent.ValueBool()),
}
+ return strings.Join(elements, "::")
+}
+
+func (conn *ConnectionResourceModel) Connection() (string, *ssh.ClientConfig, diag.Diagnostics) {
+ dia := diag.Diagnostics{}
+
+ host := conn.Host.ValueString()
+ port := conn.Port.ValueInt64()
+ user := conn.User.ValueString()
clientConfig := ssh.ClientConfig{
User: user,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
- if password, ok, err := GetOk[string](d, "conn.0.password"); ok {
- if err != nil {
- return "", nil, err
- }
-
- clientConfig.Auth = append(clientConfig.Auth, ssh.Password(password))
+ if !conn.Password.IsNull() && !conn.Password.IsUnknown() {
+ clientConfig.Auth = append(clientConfig.Auth, ssh.Password(conn.Password.ValueString()))
}
- if privateKey, ok, err := GetOk[string](d, "conn.0.private_key"); ok {
- if err != nil {
- return "", nil, err
- }
-
- signer, err := ssh.ParsePrivateKey([]byte(privateKey))
+ if !conn.PrivateKey.IsNull() && !conn.PrivateKey.IsUnknown() {
+ signer, err := ssh.ParsePrivateKey([]byte(conn.PrivateKey.ValueString()))
if err != nil {
- return "", nil, fmt.Errorf("couldn't create a ssh client config from private key: %s", err.Error())
+ dia.AddError("Error", fmt.Sprintf("couldn't create a ssh client config from private key: %s", err.Error()))
+ } else {
+ clientConfig.Auth = append(clientConfig.Auth, ssh.PublicKeys(signer))
}
- clientConfig.Auth = append(clientConfig.Auth, ssh.PublicKeys(signer))
}
- if privateKeyPath, ok, err := GetOk[string](d, "conn.0.private_key_path"); ok {
+ if !conn.PrivateKeyPath.IsNull() && !conn.PrivateKeyPath.IsUnknown() {
+ content, err := os.ReadFile(conn.PrivateKeyPath.ValueString())
if err != nil {
- return "", nil, err
+ dia.AddError("Error", fmt.Sprintf("couldn't read private key: %s", err.Error()))
+ } else {
+ signer, err := ssh.ParsePrivateKey(content)
+ if err != nil {
+ dia.AddError("Error", fmt.Sprintf("couldn't create a ssh client config from private key file: %s", err.Error()))
+ } else {
+ clientConfig.Auth = append(clientConfig.Auth, ssh.PublicKeys(signer))
+ }
}
-
- content, err := os.ReadFile(privateKeyPath)
- if err != nil {
- return "", nil, fmt.Errorf("couldn't read private key: %s", err.Error())
- }
- signer, err := ssh.ParsePrivateKey(content)
- if err != nil {
- return "", nil, fmt.Errorf("couldn't create a ssh client config from private key file: %s", err.Error())
- }
- clientConfig.Auth = append(clientConfig.Auth, ssh.PublicKeys(signer))
}
- if privateKeyEnvVar, ok, err := GetOk[string](d, "conn.0.private_key_env_var"); ok {
- if err != nil {
- return "", nil, err
- }
-
- content := []byte(os.Getenv(privateKeyEnvVar))
+ if !conn.PrivateKeyEnvVar.IsNull() && !conn.PrivateKeyEnvVar.IsUnknown() {
+ content := []byte(os.Getenv(conn.PrivateKeyEnvVar.ValueString()))
signer, err := ssh.ParsePrivateKey(content)
if err != nil {
- return "", nil, fmt.Errorf("couldn't create a ssh client config from private key env var: %s", err.Error())
+ dia.AddError("Error", fmt.Sprintf("couldn't create a ssh client config from private key env var: %s", err.Error()))
+ } else {
+ clientConfig.Auth = append(clientConfig.Auth, ssh.PublicKeys(signer))
}
- clientConfig.Auth = append(clientConfig.Auth, ssh.PublicKeys(signer))
}
-
- // Don't check ok as terraform struggles with zero values.
- if enableAgent, _, err := GetOk[bool](d, "conn.0.agent"); enableAgent {
- if err != nil {
- return "", nil, err
- }
-
+ if conn.Agent.ValueBool() {
connection, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
if err != nil {
- return "", nil, fmt.Errorf("couldn't connect to SSH agent: %s", err.Error())
+ dia.AddError("Error", fmt.Sprintf("couldn't connect to SSH agent: %s", err.Error()))
+ } else {
+ clientConfig.Auth = append(clientConfig.Auth, ssh.PublicKeysCallback(agent.NewClient(connection).Signers))
}
- clientConfig.Auth = append(clientConfig.Auth, ssh.PublicKeysCallback(agent.NewClient(connection).Signers))
}
- if timeout, ok, err := GetOk[int](d, "conn.0.timeout"); ok {
- if err != nil {
- return "", nil, err
- }
-
- clientConfig.Timeout = time.Duration(timeout) * time.Millisecond
+ if conn.Timeout.IsNull() || conn.Timeout.IsUnknown() {
+ clientConfig.Timeout = time.Duration(conn.Timeout.ValueInt64()) * time.Millisecond
}
- return fmt.Sprintf("%s:%d", host, port), &clientConfig, nil
+ return fmt.Sprintf("%s:%d", host, port), &clientConfig, dia
}
diff --git a/internal/provider/data_source_remote_file.go b/internal/provider/data_source_remote_file.go
deleted file mode 100644
index f7861f1..0000000
--- a/internal/provider/data_source_remote_file.go
+++ /dev/null
@@ -1,150 +0,0 @@
-package provider
-
-import (
- "context"
-
- "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
-)
-
-func dataSourceRemoteFile() *schema.Resource {
- return &schema.Resource{
- Description: "File on remote host.",
-
- ReadContext: dataSourceRemoteFileRead,
-
- Schema: map[string]*schema.Schema{
- "conn": {
- Type: schema.TypeList,
- MinItems: 0,
- MaxItems: 1,
- Optional: true,
- Description: "Connection to host where files are located.",
- Elem: connectionSchemaResource,
- },
- "path": {
- Description: "Path to file on remote host.",
- Type: schema.TypeString,
- Required: true,
- },
- "content": {
- Description: "Content of file.",
- Type: schema.TypeString,
- Computed: true,
- },
- "permissions": {
- Description: "Permissions of file (in octal form).",
- Type: schema.TypeString,
- Computed: true,
- },
- "group": {
- Description: "Group ID (GID) of file owner.",
- Type: schema.TypeString,
- Computed: true,
- },
- "group_name": {
- Description: "Group name of file owner.",
- Type: schema.TypeString,
- Computed: true,
- },
- "owner": {
- Description: "User ID (UID) of file owner.",
- Type: schema.TypeString,
- Computed: true,
- },
- "owner_name": {
- Description: "User name of file owner.",
- Type: schema.TypeString,
- Computed: true,
- },
- },
- }
-}
-
-func dataSourceRemoteFileRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
- conn, err := meta.(*apiClient).getConnWithDefault(d)
- if err != nil {
- return diag.Errorf(err.Error())
- }
-
- if err := setResourceID(d, conn); err != nil {
- return diag.Errorf(err.Error())
- }
-
- client, err := meta.(*apiClient).getRemoteClient(ctx, conn)
- if err != nil {
- return diag.Errorf("unable to open remote client: %s", err.Error())
- }
-
- sudo, _, err := GetOk[bool](conn, "conn.0.sudo")
- if err != nil {
- return diag.Diagnostics{{Severity: diag.Error, Summary: err.Error()}}
- }
-
- path, err := Get[string](d, "path")
- if err != nil {
- return diag.Diagnostics{{Severity: diag.Error, Summary: err.Error()}}
- }
-
- exists, err := client.FileExists(path, sudo)
- if err != nil {
- return diag.Errorf("unable to check if remote file exists: %s", err.Error())
- }
- if !exists {
- return diag.Errorf("cannot read file, it does not exist")
- }
-
- content, err := client.ReadFile(path, sudo)
- if err != nil {
- return diag.Errorf("unable to read remote file: %s", err.Error())
- }
- if err := d.Set("content", content); err != nil {
- return diag.Errorf(err.Error())
- }
-
- permissions, err := client.ReadFilePermissions(path, sudo)
- if err != nil {
- return diag.Errorf("unable to read remote file permissions: %s", err.Error())
- }
- if err := d.Set("permissions", permissions); err != nil {
- return diag.Errorf(err.Error())
- }
-
- owner, err := client.ReadFileOwner(path, sudo)
- if err != nil {
- return diag.Errorf("unable to read remote file owner: %s", err.Error())
- }
- if err := d.Set("owner", owner); err != nil {
- return diag.Errorf(err.Error())
- }
-
- ownerName, err := client.ReadFileOwnerName(path, sudo)
- if err != nil {
- return diag.Errorf("unable to read remote file owner_name: %s", err.Error())
- }
- if err := d.Set("owner_name", ownerName); err != nil {
- return diag.Errorf(err.Error())
- }
-
- group, err := client.ReadFileGroup(path, sudo)
- if err != nil {
- return diag.Errorf("unable to read remote file group: %s", err.Error())
- }
- if err := d.Set("group", group); err != nil {
- return diag.Errorf(err.Error())
- }
-
- groupName, err := client.ReadFileGroupName(path, sudo)
- if err != nil {
- return diag.Errorf("unable to read remote file group_name: %s", err.Error())
- }
- if err := d.Set("group_name", groupName); err != nil {
- return diag.Errorf(err.Error())
- }
-
- if err := meta.(*apiClient).closeRemoteClient(conn); err != nil {
- return diag.Errorf("unable to close remote client: %s", err.Error())
- }
-
- return diag.Diagnostics{}
-}
diff --git a/internal/provider/file_data_source.go b/internal/provider/file_data_source.go
new file mode 100644
index 0000000..7991f47
--- /dev/null
+++ b/internal/provider/file_data_source.go
@@ -0,0 +1,255 @@
+package provider
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ tfpath "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+// Ensure provider defined types fully satisfy framework interfaces.
+var _ datasource.DataSource = &FileDataSource{}
+
+func NewFileDataSource() datasource.DataSource {
+ return &FileDataSource{}
+}
+
+// FileDataSource defines the resource implementation.
+type FileDataSource struct {
+ client *apiClient
+}
+
+// FileDataSourceModel describes the resource data model.
+type FileDataSourceModel struct {
+ FileResourceModel
+}
+
+func (d *FileDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_file"
+}
+
+func (d *FileDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ MarkdownDescription: "File on remote host.",
+
+ Blocks: map[string]schema.Block{
+ "conn": schema.ListNestedBlock{
+ NestedObject: schema.NestedBlockObject{
+ Attributes: map[string]schema.Attribute{
+ "host": schema.StringAttribute{
+ Required: true,
+ //FIXME: ForceNew: true,
+ MarkdownDescription: "The remote host.",
+ },
+ "port": schema.Int64Attribute{
+ Optional: true,
+ //FIXME: Default: 22,
+ //FIXME: ForceNew: true,
+ MarkdownDescription: "The ssh port on the remote host.",
+ },
+ "timeout": schema.Int64Attribute{
+ Optional: true,
+ MarkdownDescription: "The maximum amount of time, in milliseconds, for the TCP connection to establish. Timeout of zero means no timeout.",
+ },
+ "user": schema.StringAttribute{
+ Required: true,
+ MarkdownDescription: "The user on the remote host.",
+ },
+ "sudo": schema.BoolAttribute{
+ Optional: true,
+ //FIXME: Default: false,
+ MarkdownDescription: "Use sudo to gain access to file.",
+ },
+ "agent": schema.BoolAttribute{
+ Optional: true,
+ //FIXME: Default: false
+ MarkdownDescription: "Use a local SSH agent to login to the remote host.",
+ },
+ "password": schema.StringAttribute{
+ Optional: true,
+ Sensitive: true,
+ MarkdownDescription: "The pasword for the user on the remote host.",
+ },
+ "private_key": schema.StringAttribute{
+ Optional: true,
+ Sensitive: true,
+ MarkdownDescription: "The private key used to login to the remote host.",
+ },
+ "private_key_path": schema.StringAttribute{
+ Optional: true,
+ MarkdownDescription: "The local path to the private key used to login to the remote host.",
+ },
+ "private_key_env_var": schema.StringAttribute{
+ Optional: true,
+ MarkdownDescription: "The name of the local environment variable containing the private key used to login to the remote host.",
+ },
+ },
+ },
+ Validators: []validator.List{
+ listvalidator.SizeAtMost(1),
+ },
+ MarkdownDescription: "Connection to host where files are located.",
+ },
+ },
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ MarkdownDescription: "ID.",
+ Computed: true,
+ },
+ "path": schema.StringAttribute{
+ MarkdownDescription: "Path to file on remote host.",
+ Required: true,
+ },
+ "content": schema.StringAttribute{
+ MarkdownDescription: "Content of file.",
+ Computed: true,
+ },
+ "permissions": schema.StringAttribute{
+ MarkdownDescription: "Permissions of file (in octal form).",
+ Computed: true,
+ },
+ "group": schema.StringAttribute{
+ MarkdownDescription: "Group ID (GID) of file owner.",
+ Computed: true,
+ },
+ "group_name": schema.StringAttribute{
+ MarkdownDescription: "Group name of file owner.",
+ Computed: true,
+ },
+ "owner": schema.StringAttribute{
+ MarkdownDescription: "User ID (UID) of file owner.",
+ Computed: true,
+ },
+ "owner_name": schema.StringAttribute{
+ MarkdownDescription: "User name of file owner.",
+ Computed: true,
+ },
+ },
+ }
+}
+
+func (d *FileDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ // Prevent panic if the provider has not been configured.
+ if req.ProviderData == nil {
+ return
+ }
+
+ client, ok := req.ProviderData.(*apiClient)
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected Resource Configure Type",
+ fmt.Sprintf("Expected *apiClient, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+ )
+
+ return
+ }
+
+ d.client = client
+}
+
+func (d *FileDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ if d.client == nil {
+ resp.Diagnostics.AddError("Provider Error", "Data source has no client.")
+ return
+ }
+
+ var data FileDataSourceModel
+
+ // Read Terraform prior state data into the model
+ resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ conn, dia := d.client.getConnWithDefault(ctx, data.Connection)
+ if dia.HasError() {
+ resp.Diagnostics.Append(dia...)
+ return
+ }
+
+ resp.Diagnostics.Append(conn.Validate()...)
+ resp.Diagnostics.Append(data.Validate()...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ client, dia := d.client.getRemoteClient(ctx, conn)
+ if dia.HasError() {
+ resp.Diagnostics.Append(dia...)
+ resp.Diagnostics.AddError("Client Error", "unable to open remote client")
+ return
+ }
+ defer func() {
+ if dia := d.client.closeRemoteClient(conn); dia.HasError() {
+ resp.Diagnostics.Append(dia...)
+ resp.Diagnostics.AddError("Client Error", "unable to close remote client")
+ return
+ }
+ }()
+
+ sudo := conn.Sudo.ValueBool()
+ path := data.Path.ValueString()
+
+ exists, err := client.FileExists(data.Path.ValueString(), conn.Sudo.ValueBool())
+ if err != nil {
+ resp.Diagnostics.AddError("Remote Error", fmt.Sprintf("unable to check if remote file exists: %s", err.Error()))
+ return
+ }
+ if exists {
+ content, err := client.ReadFile(path, sudo)
+ if err != nil {
+ resp.Diagnostics.AddAttributeError(tfpath.Root("content"), "Remote Error", fmt.Sprintf("unable to read from remote: %s", err.Error()))
+ return
+ }
+ data.Content = types.StringValue(content)
+
+ permissions, err := client.ReadFilePermissions(path, sudo)
+ if err != nil {
+ resp.Diagnostics.AddAttributeError(tfpath.Root("permissions"), "Remote Error", fmt.Sprintf("unable to read from remote: %s", err.Error()))
+ return
+ }
+ data.Permissions = types.StringValue(permissions)
+
+ owner, err := client.ReadFileOwner(path, sudo)
+ if err != nil {
+ resp.Diagnostics.AddAttributeError(tfpath.Root("owner"), "Remote Error", fmt.Sprintf("unable to read from remote: %s", err.Error()))
+ return
+ }
+ data.Owner = types.StringValue(owner)
+
+ ownerName, err := client.ReadFileOwnerName(path, sudo)
+ if err != nil {
+ resp.Diagnostics.AddAttributeError(tfpath.Root("owner_name"), "Remote Error", fmt.Sprintf("unable to read from remote: %s", err.Error()))
+ return
+ }
+ data.OwnerName = types.StringValue(ownerName)
+
+ group, err := client.ReadFileGroup(path, sudo)
+ if err != nil {
+ resp.Diagnostics.AddAttributeError(tfpath.Root("group"), "Remote Error", fmt.Sprintf("unable to read from remote: %s", err.Error()))
+ return
+ }
+ data.Group = types.StringValue(group)
+
+ groupName, err := client.ReadFileGroupName(path, sudo)
+ if err != nil {
+ resp.Diagnostics.AddAttributeError(tfpath.Root("group_name"), "Remote Error", fmt.Sprintf("unable to read from remote: %s", err.Error()))
+ return
+ }
+ data.GroupName = types.StringValue(groupName)
+ } else {
+ resp.Diagnostics.AddError("Error", "cannot read file, it does not exist")
+ return
+ }
+
+ data.ID = types.StringValue(getResourceID(&data.FileResourceModel, conn))
+
+ // Save updated data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
diff --git a/internal/provider/data_source_remote_file_test.go b/internal/provider/file_data_source_test.go
similarity index 94%
rename from internal/provider/data_source_remote_file_test.go
rename to internal/provider/file_data_source_test.go
index 29183e9..2a51fcb 100644
--- a/internal/provider/data_source_remote_file_test.go
+++ b/internal/provider/file_data_source_test.go
@@ -5,7 +5,7 @@ import (
"regexp"
"testing"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestAccDataSourceRemoteFile(t *testing.T) {
@@ -14,10 +14,10 @@ func TestAccDataSourceRemoteFile(t *testing.T) {
testAccPreCheck(t)
writeFileToHost("remotehost:22", "/tmp/data_1.txt", "data_1", "root", "bob")
},
- ProviderFactories: providerFactories,
+ ProtoV6ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
- Config: `
+ Config: providerConfig + `
data "remote_file" "data_1" {
conn {
host = "remotehost"
@@ -54,10 +54,10 @@ func TestAccDataSourceRemoteFileOverridingDefaultConnection(t *testing.T) {
testAccPreCheck(t)
writeFileToHost("remotehost2:22", "/tmp/data_2.txt", "data_2", "root", "root")
},
- ProviderFactories: providerFactories,
+ ProtoV6ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
- Config: `
+ Config: providerConfig + `
data "remote_file" "data_2" {
provider = remotehost
conn {
@@ -87,10 +87,10 @@ func TestAccDataSourceRemoteFilePrivateKey(t *testing.T) {
testAccPreCheck(t)
writeFileToHost("remotehost:22", "/tmp/data_3.txt", "data_3", "root", "root")
},
- ProviderFactories: providerFactories,
+ ProtoV6ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
- Config: `
+ Config: providerConfig + `
data "remote_file" "data_3" {
conn {
host = "remotehost"
@@ -158,10 +158,10 @@ func TestAccDataSourceRemoteFilePrivateKeyPath(t *testing.T) {
testAccPreCheck(t)
writeFileToHost("remotehost:22", "/tmp/data_4.txt", "data_4", "root", "root")
},
- ProviderFactories: providerFactories,
+ ProtoV6ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
- Config: `
+ Config: providerConfig + `
data "remote_file" "data_4" {
conn {
host = "remotehost"
diff --git a/internal/provider/file_resource.go b/internal/provider/file_resource.go
new file mode 100644
index 0000000..40695c6
--- /dev/null
+++ b/internal/provider/file_resource.go
@@ -0,0 +1,538 @@
+package provider
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ tfpath "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+// Ensure provider defined types fully satisfy framework interfaces.
+var _ resource.Resource = &FileResource{}
+var _ resource.ResourceWithImportState = &FileResource{}
+
+func NewFileResource() resource.Resource {
+ return &FileResource{}
+}
+
+// FileResource defines the resource implementation.
+type FileResource struct {
+ client *apiClient
+}
+
+// FileResourceModel describes the resource data model.
+type FileResourceModel struct {
+ ID types.String `tfsdk:"id"`
+ Connection types.List `tfsdk:"conn"`
+ Path types.String `tfsdk:"path"`
+ Content types.String `tfsdk:"content"`
+ Permissions types.String `tfsdk:"permissions"`
+ Group types.String `tfsdk:"group"`
+ GroupName types.String `tfsdk:"group_name"`
+ Owner types.String `tfsdk:"owner"`
+ OwnerName types.String `tfsdk:"owner_name"`
+}
+
+func (r *FileResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_file"
+}
+
+func (r *FileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ MarkdownDescription: "File on remote host.",
+
+ Blocks: map[string]schema.Block{
+ "conn": schema.ListNestedBlock{
+ NestedObject: schema.NestedBlockObject{
+ Attributes: map[string]schema.Attribute{
+ "host": schema.StringAttribute{
+ Required: true,
+ //FIXME: ForceNew: true,
+ MarkdownDescription: "The remote host.",
+ },
+ "port": schema.Int64Attribute{
+ Optional: true,
+ Default: int64default.StaticInt64(22),
+ Computed: true,
+ //FIXME: ForceNew: true,
+ MarkdownDescription: "The ssh port on the remote host.",
+ },
+ "timeout": schema.Int64Attribute{
+ Optional: true,
+ MarkdownDescription: "The maximum amount of time, in milliseconds, for the TCP connection to establish. Timeout of zero means no timeout.",
+ },
+ "user": schema.StringAttribute{
+ Required: true,
+ MarkdownDescription: "The user on the remote host.",
+ },
+ "sudo": schema.BoolAttribute{
+ Optional: true,
+ Default: booldefault.StaticBool(false),
+ Computed: true,
+ MarkdownDescription: "Use sudo to gain access to file.",
+ },
+ "agent": schema.BoolAttribute{
+ Optional: true,
+ Default: booldefault.StaticBool(false),
+ Computed: true,
+ MarkdownDescription: "Use a local SSH agent to login to the remote host.",
+ },
+ "password": schema.StringAttribute{
+ Optional: true,
+ Sensitive: true,
+ MarkdownDescription: "The pasword for the user on the remote host.",
+ },
+ "private_key": schema.StringAttribute{
+ Optional: true,
+ Sensitive: true,
+ MarkdownDescription: "The private key used to login to the remote host.",
+ },
+ "private_key_path": schema.StringAttribute{
+ Optional: true,
+ MarkdownDescription: "The local path to the private key used to login to the remote host.",
+ },
+ "private_key_env_var": schema.StringAttribute{
+ Optional: true,
+ MarkdownDescription: "The name of the local environment variable containing the private key used to login to the remote host.",
+ },
+ },
+ },
+ Validators: []validator.List{
+ listvalidator.SizeAtMost(1),
+ },
+ MarkdownDescription: "Connection to host where files are located.",
+ },
+ },
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Computed: true,
+ MarkdownDescription: "ID.",
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "path": schema.StringAttribute{
+ MarkdownDescription: "Path to file on remote host.",
+ //FIXME: ForceNew: true,
+ Required: true,
+ },
+ "content": schema.StringAttribute{
+ MarkdownDescription: "Content of file.",
+ Required: true,
+ },
+ "permissions": schema.StringAttribute{
+ MarkdownDescription: "Permissions of file (in octal form).",
+ Optional: true,
+ Default: stringdefault.StaticString("0644"),
+ Computed: true,
+ },
+ "group": schema.StringAttribute{
+ MarkdownDescription: "Group ID (GID) of file owner. Mutually exclusive with `group_name`.",
+ Optional: true,
+ //FIXME: ConflictsWith: []string{"group_name"},
+ },
+ "group_name": schema.StringAttribute{
+ MarkdownDescription: "Group name of file owner. Mutually exclusive with `group`.",
+ Optional: true,
+ //FIXME: ConflictsWith: []string{"group"},
+ },
+ "owner": schema.StringAttribute{
+ MarkdownDescription: "User ID (UID) of file owner. Mutually exclusive with `owner_name`.",
+ Optional: true,
+ //FIXME: ConflictsWith: []string{"owner_name"},
+ },
+ "owner_name": schema.StringAttribute{
+ MarkdownDescription: "User name of file owner. Mutually exclusive with `owner`.",
+ Optional: true,
+ //FIXME: ConflictsWith: []string{"owner"},
+ },
+ },
+ }
+}
+
+func (r *FileResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ // Prevent panic if the provider has not been configured.
+ if req.ProviderData == nil {
+ return
+ }
+
+ client, ok := req.ProviderData.(*apiClient)
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected Resource Configure Type",
+ fmt.Sprintf("Expected *apiClient, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+ )
+
+ return
+ }
+
+ r.client = client
+}
+
+func (r *FileResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ if r.client == nil {
+ resp.Diagnostics.AddError("Provider Error", "Resource has no client.")
+ return
+ }
+
+ var data FileResourceModel
+
+ // Read Terraform plan data into the model
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ conn, dia := r.client.getConnWithDefault(ctx, data.Connection)
+ if dia.HasError() {
+ resp.Diagnostics.Append(dia...)
+ return
+ }
+
+ resp.Diagnostics.Append(conn.Validate()...)
+ resp.Diagnostics.Append(data.Validate()...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ client, dia := r.client.getRemoteClient(ctx, conn)
+ if dia.HasError() {
+ resp.Diagnostics.Append(dia...)
+ resp.Diagnostics.AddError("Client Error", "unable to open remote client")
+ return
+ }
+ defer func() {
+ if dia := r.client.closeRemoteClient(conn); dia.HasError() {
+ resp.Diagnostics.Append(dia...)
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("unable to close remote client"))
+ return
+ }
+ }()
+
+ sudo := conn.Sudo.ValueBool()
+ content := data.Content.ValueString()
+ path := data.Path.ValueString()
+ permissions := data.Permissions.ValueString()
+
+ if err := client.WriteFile(ctx, content, path, permissions, sudo); err != nil {
+ resp.Diagnostics.AddError("Remote Error", fmt.Sprintf("unable to create remote file: %s", err.Error()))
+ return
+ }
+
+ if err := client.ChmodFile(path, permissions, sudo); err != nil {
+ resp.Diagnostics.AddAttributeError(tfpath.Root("permissions"), "Remote Error", fmt.Sprintf("unable to update remote file: %s", err.Error()))
+ return
+ }
+
+ if group, ok := data.GroupOk(); ok {
+ if err := client.ChgrpFile(path, group, sudo); err != nil {
+ resp.Diagnostics.AddError("Remote Error", fmt.Sprintf("unable to change group of remote file: %s", err.Error()))
+ return
+ }
+ }
+
+ if owner, ok := data.OwnerOk(); ok {
+ if err := client.ChownFile(path, owner, sudo); err != nil {
+ resp.Diagnostics.AddError("Remote Error", fmt.Sprintf("unable to change owner of remote file: %s", err.Error()))
+ return
+
+ }
+ }
+
+ data.ID = types.StringValue(getResourceID(&data, conn))
+
+ // Save data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *FileResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ if r.client == nil {
+ resp.Diagnostics.AddError("Provider Error", "Resource has no client.")
+ return
+ }
+
+ var data FileResourceModel
+
+ // Read Terraform prior state data into the model
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ conn, dia := r.client.getConnWithDefault(ctx, data.Connection)
+ if dia.HasError() {
+ resp.Diagnostics.Append(dia...)
+ return
+ }
+
+ resp.Diagnostics.Append(conn.Validate()...)
+ resp.Diagnostics.Append(data.Validate()...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ client, dia := r.client.getRemoteClient(ctx, conn)
+ if dia.HasError() {
+ resp.Diagnostics.Append(dia...)
+ resp.Diagnostics.AddError("Client Error", "unable to open remote client")
+ return
+ }
+ defer func() {
+ if dia := r.client.closeRemoteClient(conn); dia.HasError() {
+ resp.Diagnostics.Append(dia...)
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("unable to close remote client"))
+ return
+ }
+ }()
+
+ sudo := conn.Sudo.ValueBool()
+ path := data.Path.ValueString()
+
+ exists, err := client.FileExists(data.Path.ValueString(), conn.Sudo.ValueBool())
+ if err != nil {
+ resp.Diagnostics.AddError("Remote Error", fmt.Sprintf("unable to check if remote file exists: %s", err.Error()))
+ return
+ }
+ if exists {
+ content, err := client.ReadFile(path, sudo)
+ if err != nil {
+ resp.Diagnostics.AddAttributeError(tfpath.Root("content"), "Remote Error", fmt.Sprintf("unable to read from remote: %s", err.Error()))
+ return
+ }
+ data.Content = types.StringValue(content)
+
+ permissions, err := client.ReadFilePermissions(path, sudo)
+ if err != nil {
+ resp.Diagnostics.AddAttributeError(tfpath.Root("permissions"), "Remote Error", fmt.Sprintf("unable to read from remote: %s", err.Error()))
+ return
+ }
+ data.Permissions = types.StringValue(permissions)
+
+ if !data.Owner.IsNull() && !data.Owner.IsUnknown() {
+ owner, err := client.ReadFileOwner(path, sudo)
+ if err != nil {
+ resp.Diagnostics.AddAttributeError(tfpath.Root("owner"), "Remote Error", fmt.Sprintf("unable to read from remote: %s", err.Error()))
+ return
+ }
+ data.Owner = types.StringValue(owner)
+ }
+ if !data.OwnerName.IsNull() && !data.OwnerName.IsUnknown() {
+ ownerName, err := client.ReadFileOwnerName(path, sudo)
+ if err != nil {
+ resp.Diagnostics.AddAttributeError(tfpath.Root("owner_name"), "Remote Error", fmt.Sprintf("unable to read from remote: %s", err.Error()))
+ return
+ }
+ data.OwnerName = types.StringValue(ownerName)
+ }
+
+ if !data.Group.IsNull() && !data.Group.IsUnknown() {
+ group, err := client.ReadFileGroup(path, sudo)
+ if err != nil {
+ resp.Diagnostics.AddAttributeError(tfpath.Root("group"), "Remote Error", fmt.Sprintf("unable to read from remote: %s", err.Error()))
+ return
+ }
+ data.Group = types.StringValue(group)
+ }
+ if !data.GroupName.IsNull() && !data.GroupName.IsUnknown() {
+ groupName, err := client.ReadFileGroupName(path, sudo)
+ if err != nil {
+ resp.Diagnostics.AddAttributeError(tfpath.Root("group_name"), "Remote Error", fmt.Sprintf("unable to read from remote: %s", err.Error()))
+ return
+ }
+ data.GroupName = types.StringValue(groupName)
+ }
+ } else {
+ data.ID = types.StringValue("")
+ }
+
+ // Save updated data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+// Note: copy-paste of Create (minus setting ID)
+func (r *FileResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ if r.client == nil {
+ resp.Diagnostics.AddError("Provider Error", "Resource has no client.")
+ return
+ }
+
+ var data FileResourceModel
+
+ // Read Terraform plan data into the model
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ conn, dia := r.client.getConnWithDefault(ctx, data.Connection)
+ if dia.HasError() {
+ resp.Diagnostics.Append(dia...)
+ return
+ }
+
+ resp.Diagnostics.Append(conn.Validate()...)
+ resp.Diagnostics.Append(data.Validate()...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ client, dia := r.client.getRemoteClient(ctx, conn)
+ if dia.HasError() {
+ resp.Diagnostics.Append(dia...)
+ resp.Diagnostics.AddError("Client Error", "unable to open remote client")
+ return
+ }
+ defer func() {
+ if dia := r.client.closeRemoteClient(conn); dia.HasError() {
+ resp.Diagnostics.Append(dia...)
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("unable to close remote client"))
+ return
+ }
+ }()
+
+ sudo := conn.Sudo.ValueBool()
+ content := data.Content.ValueString()
+ path := data.Path.ValueString()
+ permissions := data.Permissions.ValueString()
+
+ if err := client.WriteFile(ctx, content, path, permissions, sudo); err != nil {
+ resp.Diagnostics.AddError("Remote Error", fmt.Sprintf("unable to create remote file: %s", err.Error()))
+ return
+ }
+
+ if err := client.ChmodFile(path, permissions, sudo); err != nil {
+ resp.Diagnostics.AddAttributeError(tfpath.Root("permissions"), "Remote Error", fmt.Sprintf("unable to update remote file: %s", err.Error()))
+ return
+ }
+
+ if group, ok := data.GroupOk(); ok {
+ if err := client.ChgrpFile(path, group, sudo); err != nil {
+ resp.Diagnostics.AddError("Remote Error", fmt.Sprintf("unable to change group of remote file: %s", err.Error()))
+ return
+ }
+ }
+
+ if owner, ok := data.OwnerOk(); ok {
+ if err := client.ChownFile(path, owner, sudo); err != nil {
+ resp.Diagnostics.AddError("Remote Error", fmt.Sprintf("unable to change owner of remote file: %s", err.Error()))
+ return
+ }
+ }
+
+ // Save data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func (r *FileResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ if r.client == nil {
+ resp.Diagnostics.AddError("Provider Error", "Resource has no client.")
+ return
+ }
+
+ var data FileResourceModel
+
+ // Read Terraform prior state data into the model
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ conn, dia := r.client.getConnWithDefault(ctx, data.Connection)
+ if dia.HasError() {
+ resp.Diagnostics.Append(dia...)
+ return
+ }
+
+ resp.Diagnostics.Append(conn.Validate()...)
+ resp.Diagnostics.Append(data.Validate()...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ client, dia := r.client.getRemoteClient(ctx, conn)
+ if dia.HasError() {
+ resp.Diagnostics.Append(dia...)
+ resp.Diagnostics.AddError("Client Error", "unable to open remote client")
+ return
+ }
+ defer func() {
+ if dia := r.client.closeRemoteClient(conn); dia.HasError() {
+ resp.Diagnostics.Append(dia...)
+ resp.Diagnostics.AddError("Client Error", fmt.Sprintf("unable to close remote client"))
+ return
+ }
+ }()
+
+ sudo := conn.Sudo.ValueBool()
+ path := data.Path.ValueString()
+
+ exists, err := client.FileExists(path, sudo)
+ if err != nil {
+ resp.Diagnostics.AddError("Remote Error", fmt.Sprintf("unable to check if remote file exists: %s", err.Error()))
+ return
+ }
+ if exists {
+ if err := client.DeleteFile(path, sudo); err != nil {
+ resp.Diagnostics.AddError("Remote Error", fmt.Sprintf("unable to delete remote file: %s", err.Error()))
+ return
+ }
+ }
+}
+
+func (r *FileResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ resource.ImportStatePassthroughID(ctx, tfpath.Root("id"), req, resp)
+}
+
+// Validate validates required and defaulted fields
+func (m *FileResourceModel) Validate() diag.Diagnostics {
+ dia := diag.Diagnostics{}
+
+ // if m.Path.IsNull() || m.Path.IsUnknown() {
+ // dia.AddAttributeError(tfpath.Root("path"), "Client Error", "Path is unknown")
+ // }
+
+ // if m.Content.IsNull() || m.Content.IsUnknown() {
+ // dia.AddAttributeError(tfpath.Root("content"), "Client Error", "Content is unknown")
+ // }
+
+ // if m.Permissions.IsNull() || m.Permissions.IsUnknown() {
+ // dia.AddAttributeError(tfpath.Root("permissions"), "Client Error", "Permissions is unknown")
+ // }
+
+ return dia
+}
+
+func (m *FileResourceModel) OwnerOk() (string, bool) {
+ if !m.Owner.IsNull() && !m.Owner.IsUnknown() {
+ return m.Owner.ValueString(), true
+ }
+ if !m.OwnerName.IsNull() && !m.OwnerName.IsUnknown() {
+ return m.OwnerName.ValueString(), true
+ }
+ return "", false
+}
+
+func (m *FileResourceModel) GroupOk() (string, bool) {
+ if !m.Group.IsNull() && !m.Group.IsUnknown() {
+ return m.Group.ValueString(), true
+ }
+ if !m.GroupName.IsNull() && !m.GroupName.IsUnknown() {
+ return m.GroupName.ValueString(), true
+ }
+ return "", false
+}
diff --git a/internal/provider/resource_remote_file_test.go b/internal/provider/file_resource_test.go
similarity index 83%
rename from internal/provider/resource_remote_file_test.go
rename to internal/provider/file_resource_test.go
index 87930cb..6d7264d 100644
--- a/internal/provider/resource_remote_file_test.go
+++ b/internal/provider/file_resource_test.go
@@ -5,16 +5,16 @@ import (
"regexp"
"testing"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestAccResourceRemoteFile(t *testing.T) {
resource.UnitTest(t, resource.TestCase{
- PreCheck: func() { testAccPreCheck(t) },
- ProviderFactories: providerFactories,
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
- Config: `
+ Config: providerConfig + `
resource "remote_file" "resource_1" {
conn {
host = "remotehost"
@@ -39,11 +39,11 @@ func TestAccResourceRemoteFile(t *testing.T) {
func TestAccResourceRemoteFileWithDefaultConnection(t *testing.T) {
resource.UnitTest(t, resource.TestCase{
- PreCheck: func() { testAccPreCheck(t) },
- ProviderFactories: providerFactories,
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
- Config: `
+ Config: providerConfig + `
resource "remote_file" "resource_2" {
provider = remotehost
@@ -68,11 +68,11 @@ func TestAccResourceRemoteFileWithAgent(t *testing.T) {
}
resource.UnitTest(t, resource.TestCase{
- PreCheck: func() { testAccPreCheck(t) },
- ProviderFactories: providerFactories,
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
- Config: `
+ Config: providerConfig + `
resource "remote_file" "resource_3" {
conn {
host = "remotehost"
@@ -94,11 +94,11 @@ func TestAccResourceRemoteFileWithAgent(t *testing.T) {
func TestAccResourceRemoteFileOwnership(t *testing.T) {
resource.UnitTest(t, resource.TestCase{
- PreCheck: func() { testAccPreCheck(t) },
- ProviderFactories: providerFactories,
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
- Config: `
+ Config: providerConfig + `
resource "remote_file" "resource_4" {
conn {
host = "remotehost"
@@ -130,11 +130,11 @@ func TestAccResourceRemoteFileOwnership(t *testing.T) {
func TestAccResourceRemoteFileOwnershipWithDefaultConnection(t *testing.T) {
resource.UnitTest(t, resource.TestCase{
- PreCheck: func() { testAccPreCheck(t) },
- ProviderFactories: providerFactories,
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
- Config: `
+ Config: providerConfig + `
resource "remote_file" "resource_5" {
provider = remotehost
path = "/tmp/resource_5.txt"
@@ -160,11 +160,11 @@ func TestAccResourceRemoteFileOwnershipWithDefaultConnection(t *testing.T) {
func TestAccResourceRemoteFileOwnershipNames(t *testing.T) {
resource.UnitTest(t, resource.TestCase{
- PreCheck: func() { testAccPreCheck(t) },
- ProviderFactories: providerFactories,
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
- Config: `
+ Config: providerConfig + `
resource "remote_file" "resource_6" {
conn {
host = "remotehost"
diff --git a/internal/provider/move_file_test.go b/internal/provider/move_file_test.go
index fd72b8f..4271c2a 100644
--- a/internal/provider/move_file_test.go
+++ b/internal/provider/move_file_test.go
@@ -5,7 +5,7 @@ import (
"regexp"
"testing"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
// Updating fields in provider or swapping provider should ideally be supported
@@ -18,11 +18,11 @@ func TestMovingFileByModifyingProvider(t *testing.T) {
PreCheck: func() {
testAccPreCheck(t)
},
- ProviderFactories: providerFactories,
+ ProtoV6ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
// Create file on 'remotehost'
- Config: `
+ Config: providerConfig + `
resource "remote_file" "move_file" {
provider = remotehost
path = "/tmp/move_file.txt"
@@ -32,7 +32,7 @@ func TestMovingFileByModifyingProvider(t *testing.T) {
},
{
// Move file to 'remotehost2'
- Config: `
+ Config: providerConfig + `
resource "remote_file" "move_file" {
provider = remotehost2
path = "/tmp/move_file.txt"
@@ -42,7 +42,7 @@ func TestMovingFileByModifyingProvider(t *testing.T) {
},
{
// Read file on 'remotehost2'
- Config: `
+ Config: providerConfig + `
resource "remote_file" "move_file" {
provider = remotehost2
path = "/tmp/move_file.txt"
@@ -67,11 +67,11 @@ func TestMovingFileByModifyingConn(t *testing.T) {
PreCheck: func() {
testAccPreCheck(t)
},
- ProviderFactories: providerFactories,
+ ProtoV6ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
// Create file on 'remotehost'
- Config: `
+ Config: providerConfig + `
resource "remote_file" "move_file_2" {
conn {
host = "remotehost"
@@ -85,7 +85,7 @@ func TestMovingFileByModifyingConn(t *testing.T) {
},
{
// Move file to 'remotehost2'
- Config: `
+ Config: providerConfig + `
resource "remote_file" "move_file_2" {
conn {
host = "remotehost2"
@@ -99,7 +99,7 @@ func TestMovingFileByModifyingConn(t *testing.T) {
},
{
// Read file on 'remotehost2'
- Config: `
+ Config: providerConfig + `
resource "remote_file" "move_file_2" {
conn {
host = "remotehost2"
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index 734ce1f..14a8e48 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -2,107 +2,228 @@ package provider
import (
"context"
- "errors"
"fmt"
- "strconv"
- "strings"
"sync"
- "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+ "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/function"
+ "github.com/hashicorp/terraform-plugin-framework/provider"
+ "github.com/hashicorp/terraform-plugin-framework/provider/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
)
-func init() {
- // Set descriptions to support markdown syntax, this will be used in document generation
- // and the language server.
- schema.DescriptionKind = schema.StringMarkdown
-
- // Customize the content of descriptions when output. For example you can add defaults on
- // to the exported descriptions if present.
- schema.SchemaDescriptionBuilder = func(s *schema.Schema) string {
- desc := s.Description
- if s.Default != nil {
- desc += fmt.Sprintf(" Defaults to `%v`.", s.Default)
- }
- return strings.TrimSpace(desc)
- }
+// Ensure RemoteProvider satisfies various provider interfaces.
+var _ provider.Provider = &RemoteProvider{}
+var _ provider.ProviderWithFunctions = &RemoteProvider{}
+
+// RemoteProvider defines the provider implementation.
+type RemoteProvider struct {
+ // version is set to the provider version on release, "dev" when the
+ // provider is built and ran locally, and "test" when running acceptance
+ // testing.
+ version string
}
-func New(version string) func() *schema.Provider {
- return func() *schema.Provider {
- p := &schema.Provider{
- DataSourcesMap: map[string]*schema.Resource{
- "remote_file": dataSourceRemoteFile(),
- },
- ResourcesMap: map[string]*schema.Resource{
- "remote_file": resourceRemoteFile(),
- },
- Schema: map[string]*schema.Schema{
- "conn": {
- Type: schema.TypeList,
- MinItems: 0,
- MaxItems: 1,
- Optional: true,
- Description: "Default connection to host where files are located. Can be overridden in resources and data sources.",
- Elem: connectionSchemaResource,
+// RemoteProviderModel describes the provider data model.
+type RemoteProviderModel struct {
+ Connection types.List `tfsdk:"conn"`
+ MaxSessions types.Int64 `tfsdk:"max_sessions"`
+}
+
+func (p *RemoteProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
+ resp.TypeName = "remote"
+ resp.Version = p.version
+}
+
+func (p *RemoteProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Blocks: map[string]schema.Block{
+ "conn": schema.ListNestedBlock{
+ NestedObject: schema.NestedBlockObject{
+ Attributes: map[string]schema.Attribute{
+ "host": schema.StringAttribute{
+ Required: true,
+ //FIXME: ForceNew: true,
+ MarkdownDescription: "The remote host.",
+ },
+ "port": schema.Int64Attribute{
+ Optional: true,
+ //FIXME: Default: 22,
+ //FIXME: ForceNew: true,
+ MarkdownDescription: "The ssh port on the remote host.",
+ },
+ "timeout": schema.Int64Attribute{
+ Optional: true,
+ MarkdownDescription: "The maximum amount of time, in milliseconds, for the TCP connection to establish. Timeout of zero means no timeout.",
+ },
+ "user": schema.StringAttribute{
+ Required: true,
+ MarkdownDescription: "The user on the remote host.",
+ },
+ "sudo": schema.BoolAttribute{
+ Optional: true,
+ //FIXME: Default: false,
+ MarkdownDescription: "Use sudo to gain access to file.",
+ },
+ "agent": schema.BoolAttribute{
+ Optional: true,
+ //FIXME: Default: false,
+ MarkdownDescription: "Use a local SSH agent to login to the remote host.",
+ },
+ "password": schema.StringAttribute{
+ Optional: true,
+ Sensitive: true,
+ MarkdownDescription: "The pasword for the user on the remote host.",
+ },
+ "private_key": schema.StringAttribute{
+ Optional: true,
+ Sensitive: true,
+ MarkdownDescription: "The private key used to login to the remote host.",
+ },
+ "private_key_path": schema.StringAttribute{
+ Optional: true,
+ MarkdownDescription: "The local path to the private key used to login to the remote host.",
+ },
+ "private_key_env_var": schema.StringAttribute{
+ Optional: true,
+ MarkdownDescription: "The name of the local environment variable containing the private key used to login to the remote host.",
+ },
+ },
},
- "max_sessions": {
- Type: schema.TypeInt,
- Optional: true,
- Default: 3,
- Description: "Maximum number of open sessions in each host connection.",
+ Validators: []validator.List{
+ listvalidator.SizeAtMost(1),
},
+ MarkdownDescription: "Default connection to host where files are located. Can be overridden in resources and data sources.",
},
- }
+ },
+ Attributes: map[string]schema.Attribute{
+ "max_sessions": schema.Int64Attribute{
+ MarkdownDescription: "Maximum number of open sessions in each host connection.",
+ Optional: true,
+ //FIXME: Default: 3,
+ },
+ },
+ }
+}
- p.ConfigureContextFunc = configure(version, p)
+func (p *RemoteProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
+ var data RemoteProviderModel
- return p
+ resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ client := apiClient{
+ resourceData: &data,
+ maxSessions: int(data.MaxSessions.ValueInt64()),
+ mux: &sync.Mutex{},
+ remoteClients: map[string]*RemoteClient{},
+ activeSessions: map[string]int{},
}
+
+ resp.DataSourceData = &client
+ resp.ResourceData = &client
}
+func (p *RemoteProvider) Resources(ctx context.Context) []func() resource.Resource {
+ return []func() resource.Resource{
+ NewFileResource,
+ }
+}
+
+func (p *RemoteProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
+ return []func() datasource.DataSource{
+ NewFileDataSource,
+ }
+}
+
+func (p *RemoteProvider) Functions(ctx context.Context) []func() function.Function {
+ return []func() function.Function{}
+}
+
+// FIXME:
+// func (p *RemoteProvider) ConfigValidators(ctx context.Context) []provider.ConfigValidator {
+// return []provider.ConfigValidator{}
+// }
+
+func New(version string) func() provider.Provider {
+ return func() provider.Provider {
+ return &RemoteProvider{
+ version: version,
+ }
+ }
+}
+
+// FIXME:
+// func init() {
+
+// // Customize the content of descriptions when output. For example you can add defaults on
+// // to the exported descriptions if present.
+// schema.SchemaDescriptionBuilder = func(s *schema.Schema) string {
+// desc := s.Description
+// if s.Default != nil {
+// desc += fmt.Sprintf(" Defaults to `%v`.", s.Default)
+// }
+// return strings.TrimSpace(desc)
+// }
+// }
+
type apiClient struct {
- resourceData *schema.ResourceData
+ resourceData *RemoteProviderModel
mux *sync.Mutex
remoteClients map[string]*RemoteClient
activeSessions map[string]int
maxSessions int
}
-func configure(version string, p *schema.Provider) func(context.Context, *schema.ResourceData) (interface{}, diag.Diagnostics) {
- return func(c context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
- client := apiClient{
- resourceData: d,
- maxSessions: d.Get("max_sessions").(int),
- mux: &sync.Mutex{},
- remoteClients: map[string]*RemoteClient{},
- activeSessions: map[string]int{},
- }
+func ConnAs(ctx context.Context, conn types.List) (*ConnectionResourceModel, diag.Diagnostics) {
+ connections := []ConnectionResourceModel{}
+ if diag := conn.ElementsAs(ctx, &connections, false); diag.HasError() {
+ return nil, diag
+ }
- return &client, diag.Diagnostics{}
+ if len(connections) > 0 {
+ return &connections[0], nil
}
+
+ return nil, nil
}
-func (c *apiClient) getConnWithDefault(d *schema.ResourceData) (*schema.ResourceData, error) {
- if _, ok := d.GetOk("conn"); ok {
- return d, nil
+func (c *apiClient) getConnWithDefault(ctx context.Context, conn types.List) (*ConnectionResourceModel, diag.Diagnostics) {
+ connection, dia := ConnAs(ctx, conn)
+ if dia.HasError() {
+ return nil, dia
+ }
+ if connection != nil {
+ return connection, dia
}
c.mux.Lock()
defer c.mux.Unlock()
- if _, ok := c.resourceData.GetOk("conn"); ok {
- return c.resourceData, nil
+ connection, dia = ConnAs(ctx, c.resourceData.Connection)
+ if dia.HasError() {
+ return nil, dia
+ }
+ if connection != nil {
+ return connection, dia
}
- return nil, errors.New("neither the provider nor the resource/data source have a configured connection")
+ dia.AddError(
+ "Configuration error",
+ "neither the provider nor the resource/data source have a configured connection",
+ )
+ return nil, dia
}
-func (c *apiClient) getRemoteClient(ctx context.Context, d *schema.ResourceData) (*RemoteClient, error) {
- connectionID, err := resourceConnectionHash(d)
- if err != nil {
- return nil, err
- }
+func (c *apiClient) getRemoteClient(ctx context.Context, conn *ConnectionResourceModel) (*RemoteClient, diag.Diagnostics) {
+ connectionID := conn.ConnectionHash()
defer c.mux.Unlock()
for {
@@ -118,9 +239,9 @@ func (c *apiClient) getRemoteClient(ctx context.Context, d *schema.ResourceData)
return client, nil
}
- client, err := remoteClientFromResourceData(ctx, d)
- if err != nil {
- return nil, err
+ client, dia := remoteClientFromResourceData(ctx, conn)
+ if dia.HasError() {
+ return nil, dia
}
c.remoteClients[connectionID] = client
@@ -129,19 +250,16 @@ func (c *apiClient) getRemoteClient(ctx context.Context, d *schema.ResourceData)
}
}
-func remoteClientFromResourceData(ctx context.Context, d *schema.ResourceData) (*RemoteClient, error) {
- host, clientConfig, err := ConnectionFromResourceData(ctx, d)
- if err != nil {
- return nil, err
+func remoteClientFromResourceData(ctx context.Context, conn *ConnectionResourceModel) (*RemoteClient, diag.Diagnostics) {
+ host, clientConfig, dia := conn.Connection()
+ if dia.HasError() {
+ return nil, dia
}
return NewRemoteClient(host, clientConfig)
}
-func (c *apiClient) closeRemoteClient(d *schema.ResourceData) error {
- connectionID, err := resourceConnectionHash(d)
- if err != nil {
- return err
- }
+func (c *apiClient) closeRemoteClient(conn *ConnectionResourceModel) diag.Diagnostics {
+ connectionID := conn.ConnectionHash()
c.mux.Lock()
defer c.mux.Unlock()
@@ -150,85 +268,18 @@ func (c *apiClient) closeRemoteClient(d *schema.ResourceData) error {
if c.activeSessions[connectionID] == 0 {
client := c.remoteClients[connectionID]
delete(c.remoteClients, connectionID)
- return client.Close()
- }
-
- return nil
-}
-
-func setResourceID(d *schema.ResourceData, conn *schema.ResourceData) error {
- host, err := Get[string](conn, "conn.0.host")
- if err != nil {
- return err
- }
-
- port, err := Get[int](conn, "conn.0.port")
- if err != nil {
- return err
- }
-
- path, err := Get[string](d, "path")
- if err != nil {
- return err
+ if err := client.Close(); err != nil {
+ return diag.Diagnostics{diag.NewErrorDiagnostic("Remote Client Error", err.Error())}
+ }
}
- d.SetId(fmt.Sprintf("%s:%d:%s",
- host,
- port,
- path,
- ))
-
return nil
}
-func resourceConnectionHash(d *schema.ResourceData) (string, error) {
- host, err := Get[string](d, "conn.0.host")
- if err != nil {
- return "", err
- }
-
- user, err := Get[string](d, "conn.0.user")
- if err != nil {
- return "", err
- }
-
- port, err := Get[int](d, "conn.0.port")
- if err != nil {
- return "", err
- }
-
- password, _, err := GetOk[string](d, "conn.0.password")
- if err != nil {
- return "", err
- }
-
- privateKey, _, err := GetOk[string](d, "conn.0.private_key")
- if err != nil {
- return "", err
- }
-
- privateKeyPath, _, err := GetOk[string](d, "conn.0.private_key_path")
- if err != nil {
- return "", err
- }
-
- // Should ideally use Get as it has a default and should always exist.
- // However GetOk as Terraform returns false for exists when value equals
- // zero value (which the default for agent does). Could maybe use
- // GetOkExists, but discouraged.
- agent, _, err := GetOk[bool](d, "conn.0.agent")
- if err != nil {
- return "", err
- }
-
- elements := []string{
- host,
- user,
- strconv.Itoa(port),
- password,
- privateKey,
- privateKeyPath,
- strconv.FormatBool(agent),
- }
- return strings.Join(elements, "::"), nil
+func getResourceID(d *FileResourceModel, conn *ConnectionResourceModel) string {
+ return fmt.Sprintf("%s:%d:%s",
+ conn.Host.ValueString(),
+ conn.Port.ValueInt64(),
+ d.Path.ValueString(),
+ )
}
diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go
index f493085..f73a41f 100644
--- a/internal/provider/provider_test.go
+++ b/internal/provider/provider_test.go
@@ -1,63 +1,47 @@
package provider
import (
- "context"
"testing"
- "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+ "github.com/hashicorp/terraform-plugin-framework/providerserver"
+ "github.com/hashicorp/terraform-plugin-go/tfprotov6"
)
-// providerFactories are used to instantiate a provider during acceptance testing.
-// The factory function will be invoked for every Terraform CLI command executed
-// to create a provider server to which the CLI can reattach.
-var providerFactories = map[string]func() (*schema.Provider, error){
- "remote": func() (*schema.Provider, error) {
- return New("dev")(), nil
- },
- "remotehost": func() (*schema.Provider, error) {
- provider := New("dev")()
- configureProvider := provider.ConfigureContextFunc
- provider.ConfigureContextFunc = func(c context.Context, rd *schema.ResourceData) (interface{}, diag.Diagnostics) {
- if err := rd.Set("conn", []interface{}{
- map[string]interface{}{
- "host": "remotehost",
- "user": "root",
- "password": "password",
- "port": 22,
- },
- }); err != nil {
- return nil, diag.Errorf(err.Error())
- }
- return configureProvider(c, rd)
- }
- return provider, nil
- },
- "remotehost2": func() (*schema.Provider, error) {
- provider := New("dev")()
- configureProvider := provider.ConfigureContextFunc
- provider.ConfigureContextFunc = func(c context.Context, rd *schema.ResourceData) (interface{}, diag.Diagnostics) {
- if err := rd.Set("conn", []interface{}{
- map[string]interface{}{
- "host": "remotehost2",
- "user": "root",
- "password": "password",
- "port": 22,
- },
- }); err != nil {
- return nil, diag.Errorf(err.Error())
- }
- return configureProvider(c, rd)
- }
- return provider, nil
- },
+const (
+ // providerConfig is a shared configuration to combine with the actual test
+ // configuration.
+ providerConfig = `
+provider "remote" {}
+
+provider "remotehost" {
+ conn {
+ host = "remotehost"
+ user = "root"
+ password = "password"
+ port = 22
+ }
}
-func TestProvider(t *testing.T) {
- if err := New("dev")().InternalValidate(); err != nil {
- t.Fatalf("err: %s", err)
+provider "remotehost2" {
+ conn {
+ host = "remotehost2"
+ user = "root"
+ password = "password"
+ port = 22
}
}
+`
+)
+
+// providerFactories are used to instantiate a provider during
+// acceptance testing. The factory function will be invoked for every Terraform
+// CLI command executed to create a provider server to which the CLI can
+// reattach.
+var providerFactories = map[string]func() (tfprotov6.ProviderServer, error){
+ "remote": providerserver.NewProtocol6WithError(New("test")()),
+ "remotehost": providerserver.NewProtocol6WithError(New("test")()),
+ "remotehost2": providerserver.NewProtocol6WithError(New("test")()),
+}
func testAccPreCheck(t *testing.T) {
// You can add code here to run prior to any test case execution, for example assertions
diff --git a/internal/provider/remote_client.go b/internal/provider/remote_client.go
index 47cfed2..b766610 100644
--- a/internal/provider/remote_client.go
+++ b/internal/provider/remote_client.go
@@ -7,6 +7,7 @@ import (
"strings"
"github.com/bramvdbogaerde/go-scp"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
@@ -301,15 +302,16 @@ func (c *RemoteClient) DeleteFileShell(path string) error {
return run(session, cmd)
}
-func NewRemoteClient(host string, clientConfig *ssh.ClientConfig) (*RemoteClient, error) {
+func NewRemoteClient(host string, clientConfig *ssh.ClientConfig) (*RemoteClient, diag.Diagnostics) {
client, err := ssh.Dial("tcp", host, clientConfig)
if err != nil {
- return nil, fmt.Errorf("couldn't establish a connection to the remote server '%s@%s': %s", clientConfig.User, host, err.Error())
+ return nil, diag.Diagnostics{diag.NewErrorDiagnostic(
+ "Connection Error",
+ fmt.Sprintf("couldn't establish a connection to the remote server '%s@%s: %s'", clientConfig.User, host, err.Error()),
+ )}
}
- return &RemoteClient{
- sshClient: client,
- }, nil
+ return &RemoteClient{sshClient: client}, nil
}
func (c *RemoteClient) Close() error {
diff --git a/internal/provider/resource_remote_file.go b/internal/provider/resource_remote_file.go
deleted file mode 100644
index bd42159..0000000
--- a/internal/provider/resource_remote_file.go
+++ /dev/null
@@ -1,313 +0,0 @@
-package provider
-
-import (
- "context"
-
- "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
-)
-
-func resourceRemoteFile() *schema.Resource {
- return &schema.Resource{
- Description: "File on remote host.",
-
- CreateContext: resourceRemoteFileCreate,
- ReadContext: resourceRemoteFileRead,
- UpdateContext: resourceRemoteFileUpdate,
- DeleteContext: resourceRemoteFileDelete,
-
- Schema: map[string]*schema.Schema{
- "conn": {
- Type: schema.TypeList,
- MinItems: 0,
- MaxItems: 1,
- Optional: true,
- Description: "Connection to host where files are located.",
- Elem: connectionSchemaResource,
- },
- "path": {
- Description: "Path to file on remote host.",
- Type: schema.TypeString,
- ForceNew: true,
- Required: true,
- },
- "content": {
- Description: "Content of file.",
- Type: schema.TypeString,
- Required: true,
- },
- "permissions": {
- Description: "Permissions of file (in octal form).",
- Type: schema.TypeString,
- Default: "0644",
- Optional: true,
- },
- "group": {
- Description: "Group ID (GID) of file owner. Mutually exclusive with `group_name`.",
- Type: schema.TypeString,
- Optional: true,
- },
- "group_name": {
- Description: "Group name of file owner. Mutually exclusive with `group`.",
- Type: schema.TypeString,
- Optional: true,
- ConflictsWith: []string{"group"},
- },
- "owner": {
- Description: "User ID (UID) of file owner. Mutually exclusive with `owner_name`.",
- Type: schema.TypeString,
- Optional: true,
- },
- "owner_name": {
- Description: "User name of file owner. Mutually exclusive with `owner`.",
- Type: schema.TypeString,
- Optional: true,
- ConflictsWith: []string{"owner"},
- },
- },
- }
-}
-
-func resourceRemoteFileCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
- conn, err := meta.(*apiClient).getConnWithDefault(d)
- if err != nil {
- return diag.Diagnostics{{Severity: diag.Error, Summary: err.Error()}}
- }
-
- if err := setResourceID(d, conn); err != nil {
- return diag.Errorf(err.Error())
- }
-
- client, err := meta.(*apiClient).getRemoteClient(ctx, conn)
- if err != nil {
- return diag.Errorf("unable to open remote client: %s", err.Error())
- }
-
- sudo, _, err := GetOk[bool](conn, "conn.0.sudo")
- if err != nil {
- return diag.Diagnostics{{Severity: diag.Error, Summary: err.Error()}}
- }
-
- content, err := Get[string](d, "content")
- if err != nil {
- return diag.Diagnostics{{Severity: diag.Error, Summary: err.Error()}}
- }
-
- path, err := Get[string](d, "path")
- if err != nil {
- return diag.Diagnostics{{Severity: diag.Error, Summary: err.Error()}}
- }
-
- permissions, err := Get[string](d, "permissions")
- if err != nil {
- return diag.Diagnostics{{Severity: diag.Error, Summary: err.Error()}}
- }
-
- var group string
- if g, ok, err := GetOk[string](d, "group"); ok {
- if err != nil {
- return diag.Diagnostics{{Severity: diag.Error, Summary: err.Error()}}
- }
- group = g
- } else if g, ok, err := GetOk[string](d, "group_name"); ok {
- if err != nil {
- return diag.Diagnostics{{Severity: diag.Error, Summary: err.Error()}}
- }
- group = g
- }
-
- var owner string
- if o, ok, err := GetOk[string](d, "owner"); ok {
- if err != nil {
- return diag.Diagnostics{{Severity: diag.Error, Summary: err.Error()}}
- }
- owner = o
- } else if o, ok, err := GetOk[string](d, "owner_name"); ok {
- if err != nil {
- return diag.Diagnostics{{Severity: diag.Error, Summary: err.Error()}}
- }
- owner = o
- }
-
- if err := client.WriteFile(ctx, content, path, permissions, sudo); err != nil {
- return diag.Errorf("unable to create remote file: %s", err.Error())
- }
-
- if err := client.ChmodFile(path, permissions, sudo); err != nil {
- return diag.Errorf("unable to change permissions of remote file: %s", err.Error())
- }
-
- if group != "" {
- if err := client.ChgrpFile(path, group, sudo); err != nil {
- return diag.Errorf("unable to change group of remote file: %s", err.Error())
- }
- }
-
- if owner != "" {
- if err := client.ChownFile(path, owner, sudo); err != nil {
- return diag.Errorf("unable to change owner of remote file: %s", err.Error())
- }
- }
-
- if err := meta.(*apiClient).closeRemoteClient(conn); err != nil {
- return diag.Errorf("unable to close remote client: %s", err.Error())
- }
-
- return diag.Diagnostics{}
-}
-
-func resourceRemoteFileRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
- conn, err := meta.(*apiClient).getConnWithDefault(d)
- if err != nil {
- return diag.Errorf(err.Error())
- }
-
- if err := setResourceID(d, conn); err != nil {
- return diag.Errorf(err.Error())
- }
-
- client, err := meta.(*apiClient).getRemoteClient(ctx, conn)
- if err != nil {
- return diag.Errorf("unable to open remote client: %s", err.Error())
- }
-
- sudo, _, err := GetOk[bool](conn, "conn.0.sudo")
- if err != nil {
- return diag.Diagnostics{{Severity: diag.Error, Summary: err.Error()}}
- }
-
- path, err := Get[string](d, "path")
- if err != nil {
- return diag.Diagnostics{{Severity: diag.Error, Summary: err.Error()}}
- }
-
- _, groupOk, err := GetOk[string](d, "group")
- if err != nil {
- return diag.Diagnostics{{Severity: diag.Error, Summary: err.Error()}}
- }
-
- _, groupNameOk, err := GetOk[string](d, "group_name")
- if err != nil {
- return diag.Diagnostics{{Severity: diag.Error, Summary: err.Error()}}
- }
-
- _, ownerOk, err := GetOk[string](d, "owner")
- if err != nil {
- return diag.Diagnostics{{Severity: diag.Error, Summary: err.Error()}}
- }
-
- _, ownerNameOk, err := GetOk[string](d, "owner_name")
- if err != nil {
- return diag.Diagnostics{{Severity: diag.Error, Summary: err.Error()}}
- }
-
- exists, err := client.FileExists(path, sudo)
- if err != nil {
- return diag.Errorf("unable to check if remote file exists: %s", err.Error())
- }
- if exists {
- content, err := client.ReadFile(path, sudo)
- if err != nil {
- return diag.Errorf("unable to read remote file: %s", err.Error())
- }
- if err := d.Set("content", content); err != nil {
- return diag.Errorf(err.Error())
- }
-
- permissions, err := client.ReadFilePermissions(path, sudo)
- if err != nil {
- return diag.Errorf("unable to read remote file permissions: %s", err.Error())
- }
- if err := d.Set("permissions", permissions); err != nil {
- return diag.Errorf(err.Error())
- }
-
- if ownerOk {
- owner, err := client.ReadFileOwner(path, sudo)
- if err != nil {
- return diag.Errorf("unable to read remote file owner: %s", err.Error())
- }
- if err := d.Set("owner", owner); err != nil {
- return diag.Errorf(err.Error())
- }
- }
- if ownerNameOk {
- ownerName, err := client.ReadFileOwnerName(path, sudo)
- if err != nil {
- return diag.Errorf("unable to read remote file owner_name: %s", err.Error())
- }
- if err := d.Set("owner_name", ownerName); err != nil {
- return diag.Errorf(err.Error())
- }
- }
-
- if groupOk {
- group, err := client.ReadFileGroup(path, sudo)
- if err != nil {
- return diag.Errorf("unable to read remote file group: %s", err.Error())
- }
- if err := d.Set("group", group); err != nil {
- return diag.Errorf(err.Error())
- }
- }
- if groupNameOk {
- groupName, err := client.ReadFileGroupName(path, sudo)
- if err != nil {
- return diag.Errorf("unable to read remote file group_name: %s", err.Error())
- }
- if err := d.Set("group_name", groupName); err != nil {
- return diag.Errorf(err.Error())
- }
- }
- } else {
- d.SetId("")
- }
-
- if err := meta.(*apiClient).closeRemoteClient(conn); err != nil {
- return diag.Errorf("unable to close remote client: %s", err.Error())
- }
-
- return diag.Diagnostics{}
-}
-
-func resourceRemoteFileUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
- return resourceRemoteFileCreate(ctx, d, meta)
-}
-
-func resourceRemoteFileDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
- conn, err := meta.(*apiClient).getConnWithDefault(d)
- if err != nil {
- return diag.Diagnostics{{Severity: diag.Error, Summary: err.Error()}}
- }
-
- client, err := meta.(*apiClient).getRemoteClient(ctx, conn)
- if err != nil {
- return diag.Errorf("unable to open remote client: %s", err.Error())
- }
-
- sudo, _, err := GetOk[bool](conn, "conn.0.sudo")
- if err != nil {
- return diag.Diagnostics{{Severity: diag.Error, Summary: err.Error()}}
- }
-
- path, err := Get[string](d, "path")
- if err != nil {
- return diag.Diagnostics{{Severity: diag.Error, Summary: err.Error()}}
- }
-
- exists, err := client.FileExists(path, sudo)
- if err != nil {
- return diag.Errorf("unable to check if remote file exists: %s", err.Error())
- }
- if exists {
- if err := client.DeleteFile(path, sudo); err != nil {
- return diag.Errorf("unable to delete remote file: %s", err.Error())
- }
- }
-
- if err := meta.(*apiClient).closeRemoteClient(conn); err != nil {
- return diag.Errorf("unable to close remote client: %s", err.Error())
- }
-
- return diag.Diagnostics{}
-}
diff --git a/internal/provider/utils.go b/internal/provider/utils_test.go
similarity index 53%
rename from internal/provider/utils.go
rename to internal/provider/utils_test.go
index 4f76c32..fd38a6e 100644
--- a/internal/provider/utils.go
+++ b/internal/provider/utils_test.go
@@ -1,42 +1,11 @@
package provider
import (
- "errors"
"fmt"
- "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"golang.org/x/crypto/ssh"
)
-var (
- errTypecast = errors.New("typecast error")
- errGet = errors.New("get error")
-)
-
-func Get[T any](d *schema.ResourceData, key string) (T, error) {
- value, ok, err := GetOk[T](d, key)
- if !ok {
- var t T
- return t, fmt.Errorf("%w: %s is undefined", errGet, key)
- }
- return value, err
-}
-
-func GetOk[T any](d *schema.ResourceData, key string) (T, bool, error) {
- raw, ok := d.GetOk(key)
- if !ok {
- var t T
- return t, false, nil
- }
-
- if value, ok := raw.(T); ok {
- return value, true, nil
- }
-
- var t T
- return t, true, fmt.Errorf("%w: %s to %T: %v", errTypecast, key, t, raw)
-}
-
func writeFileToHost(host string, filename string, content string, group string, user string) {
sshClient, err := ssh.Dial("tcp", host, &ssh.ClientConfig{
User: "root",
diff --git a/main.go b/main.go
index d4f346c..8ca0eab 100644
--- a/main.go
+++ b/main.go
@@ -5,20 +5,10 @@ import (
"flag"
"log"
- "github.com/hashicorp/terraform-plugin-sdk/v2/plugin"
+ "github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/tenstad/terraform-provider-remote/internal/provider"
)
-// Run "go generate" to format example terraform files and generate the docs for the registry/website
-
-// If you do not have terraform installed, you can remove the formatting command, but its suggested to
-// ensure the documentation is formatted properly.
-//go:generate terraform fmt -recursive ./examples/
-
-// Run the docs generation tool, check its repository for more information on how it works and how docs
-// can be customized.
-//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs
-
var (
// these will be set by the goreleaser configuration
// to appropriate values for the compiled binary
@@ -29,20 +19,17 @@ var (
)
func main() {
- var debugMode bool
+ var debug bool
- flag.BoolVar(&debugMode, "debug", false, "set to true to run the provider with support for debuggers like delve")
+ flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve")
flag.Parse()
- opts := &plugin.ServeOpts{ProviderFunc: provider.New(version)}
-
- if debugMode {
- err := plugin.Debug(context.Background(), "registry.terraform.io/tenstad/remote", opts)
- if err != nil {
- log.Fatal(err.Error())
- }
- return
+ opts := providerserver.ServeOpts{
+ Address: "registry.terraform.io/tenstad/remote",
+ Debug: debug,
}
- plugin.Serve(opts)
+ if err := providerserver.Serve(context.Background(), provider.New(version), opts); err != nil {
+ log.Fatal(err.Error())
+ }
}
diff --git a/terraform-registry-manifest.json b/terraform-registry-manifest.json
index 1931b0e..fec2a56 100644
--- a/terraform-registry-manifest.json
+++ b/terraform-registry-manifest.json
@@ -1,6 +1,6 @@
{
"version": 1,
"metadata": {
- "protocol_versions": ["5.0"]
+ "protocol_versions": ["6.0"]
}
}
diff --git a/tools/go.mod b/tools/go.mod
new file mode 100644
index 0000000..006f575
--- /dev/null
+++ b/tools/go.mod
@@ -0,0 +1,52 @@
+module tools
+
+go 1.23.1
+
+require github.com/hashicorp/terraform-plugin-docs v0.19.4
+
+require (
+ github.com/BurntSushi/toml v1.2.1 // indirect
+ github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect
+ github.com/Masterminds/goutils v1.1.1 // indirect
+ github.com/Masterminds/semver/v3 v3.2.0 // indirect
+ github.com/Masterminds/sprig/v3 v3.2.3 // indirect
+ github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // indirect
+ github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
+ github.com/armon/go-radix v1.0.0 // indirect
+ github.com/bgentry/speakeasy v0.1.0 // indirect
+ github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
+ github.com/cloudflare/circl v1.3.7 // indirect
+ github.com/fatih/color v1.16.0 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/hashicorp/cli v1.1.6 // indirect
+ github.com/hashicorp/errwrap v1.1.0 // indirect
+ github.com/hashicorp/go-checkpoint v0.5.0 // indirect
+ github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
+ github.com/hashicorp/go-multierror v1.1.1 // indirect
+ github.com/hashicorp/go-uuid v1.0.3 // indirect
+ github.com/hashicorp/go-version v1.7.0 // indirect
+ github.com/hashicorp/hc-install v0.7.0 // indirect
+ github.com/hashicorp/terraform-exec v0.21.0 // indirect
+ github.com/hashicorp/terraform-json v0.22.1 // indirect
+ github.com/huandu/xstrings v1.3.3 // indirect
+ github.com/imdario/mergo v0.3.15 // indirect
+ github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/mattn/go-runewidth v0.0.9 // indirect
+ github.com/mitchellh/copystructure v1.2.0 // indirect
+ github.com/mitchellh/reflectwalk v1.0.2 // indirect
+ github.com/posener/complete v1.2.3 // indirect
+ github.com/shopspring/decimal v1.3.1 // indirect
+ github.com/spf13/cast v1.5.0 // indirect
+ github.com/yuin/goldmark v1.7.1 // indirect
+ github.com/yuin/goldmark-meta v1.1.0 // indirect
+ github.com/zclconf/go-cty v1.14.4 // indirect
+ go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect
+ golang.org/x/crypto v0.21.0 // indirect
+ golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
+ golang.org/x/mod v0.17.0 // indirect
+ golang.org/x/sys v0.18.0 // indirect
+ golang.org/x/text v0.15.0 // indirect
+ gopkg.in/yaml.v2 v2.3.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/tools/go.sum b/tools/go.sum
new file mode 100644
index 0000000..473f90a
--- /dev/null
+++ b/tools/go.sum
@@ -0,0 +1,189 @@
+dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
+dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
+github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
+github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0=
+github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc=
+github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
+github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
+github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
+github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
+github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
+github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
+github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
+github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
+github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg=
+github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
+github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
+github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
+github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
+github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
+github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
+github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
+github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
+github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
+github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
+github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
+github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
+github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
+github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
+github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
+github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
+github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
+github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
+github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/hashicorp/cli v1.1.6 h1:CMOV+/LJfL1tXCOKrgAX0uRKnzjj/mpmqNXloRSy2K8=
+github.com/hashicorp/cli v1.1.6/go.mod h1:MPon5QYlgjjo0BSoAiN0ESeT5fRzDjVRp+uioJ0piz4=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
+github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU=
+github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg=
+github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
+github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
+github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
+github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
+github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/hc-install v0.7.0 h1:Uu9edVqjKQxxuD28mR5TikkKDd/p55S8vzPC1659aBk=
+github.com/hashicorp/hc-install v0.7.0/go.mod h1:ELmmzZlGnEcqoUMKUuykHaPCIR1sYLYX+KSggWSKZuA=
+github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ=
+github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg=
+github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec=
+github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A=
+github.com/hashicorp/terraform-plugin-docs v0.19.4 h1:G3Bgo7J22OMtegIgn8Cd/CaSeyEljqjH3G39w28JK4c=
+github.com/hashicorp/terraform-plugin-docs v0.19.4/go.mod h1:4pLASsatTmRynVzsjEhbXZ6s7xBlUw/2Kt0zfrq8HxA=
+github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
+github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
+github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
+github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
+github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
+github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
+github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
+github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
+github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
+github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
+github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
+github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
+github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
+github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
+github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
+github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
+github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
+github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
+github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
+github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
+github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
+github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
+github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
+github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
+github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=
+github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
+github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
+github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
+github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8=
+github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
+go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw=
+go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
+golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
+golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
+golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
+golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
+golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
+golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
+golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
+golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/tools/tools.go b/tools/tools.go
index f03ce3d..faa36d9 100644
--- a/tools/tools.go
+++ b/tools/tools.go
@@ -1,4 +1,4 @@
-// +build tools
+//go:build generate
package tools
@@ -6,3 +6,11 @@ import (
// document generation
_ "github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs"
)
+
+// Format Terraform code for use in documentation.
+// If you do not have Terraform installed, you can remove the formatting command, but it is suggested
+// to ensure the documentation is formatted properly.
+//go:generate terraform fmt -recursive ../examples/
+
+// Generate documentation.
+//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs generate --provider-dir .. -provider-name remote