├── .github
└── workflows
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── .goreleaser.yml
├── LICENSE
├── Makefile
├── README.md
├── docs
├── index.md
└── resources
│ ├── file_reader.md
│ └── file_writer.md
├── examples
├── provider
│ └── provider.tf
└── resources
│ ├── filesystem_file_reader
│ └── resource.tf
│ └── filesystem_file_writer
│ └── resource.tf
├── go.mod
├── go.sum
├── internal
└── filesystem
│ ├── helpers.go
│ ├── provider.go
│ ├── provider_test.go
│ ├── resource_file_reader.go
│ ├── resource_file_reader_test.go
│ ├── resource_file_writer.go
│ └── resource_file_writer_test.go
├── main.go
└── terraform-registry-manifest.json
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: 'Release'
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 | jobs:
8 | goreleaser:
9 | runs-on: 'ubuntu-latest'
10 |
11 | steps:
12 | - uses: 'actions/checkout@v3'
13 | with:
14 | fetch-depth: 0
15 |
16 | - uses: 'actions/setup-go@v3'
17 | with:
18 | go-version: '1.18'
19 |
20 | - id: 'import_gpg'
21 | uses: 'crazy-max/ghaction-import-gpg@v5.2.0'
22 | with:
23 | gpg_private_key: '${{ secrets.GPG_PRIVATE_KEY }}'
24 | passphrase: '${{ secrets.GPG_PASSPHRASE }}'
25 |
26 | - uses: 'goreleaser/goreleaser-action@v3'
27 | with:
28 | version: 'latest'
29 | args: 'release --rm-dist'
30 | env:
31 | GPG_FINGERPRINT: '${{ steps.import_gpg.outputs.fingerprint }}'
32 | GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
33 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: 'Test'
2 |
3 | on:
4 | push:
5 | branches:
6 | - 'main'
7 | pull_request:
8 | branches:
9 | - 'main'
10 | workflow_dispatch:
11 |
12 | concurrency:
13 | group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
14 | cancel-in-progress: true
15 |
16 | jobs:
17 | test:
18 | runs-on: 'ubuntu-latest'
19 |
20 | steps:
21 | - uses: 'actions/checkout@v3'
22 |
23 | - uses: 'actions/setup-go@v3'
24 | with:
25 | go-version: '1.18'
26 |
27 | - run: 'go mod download'
28 |
29 | - run: 'make test-acc'
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | terraform-provider-*
2 | .terraform
3 | terraform.tfstate*
4 | *.log
5 |
6 | build/
7 | dist/
8 |
9 | examples/**/.ssh
10 | examples/**/*.txt
11 |
--------------------------------------------------------------------------------
/.goreleaser.yml:
--------------------------------------------------------------------------------
1 | before:
2 | hooks:
3 | - 'go mod tidy'
4 |
5 | builds:
6 | - env:
7 | - 'CGO_ENABLED=0'
8 | - 'GO111MODULE=on'
9 | - 'GOPROXY=https://proxy.golang.org,direct'
10 | mod_timestamp: '{{ .CommitTimestamp }}'
11 | flags:
12 | - '-a'
13 | - '-trimpath'
14 | ldflags:
15 | - '-s'
16 | - '-w'
17 | - '-X=main.Version={{ .Version }}'
18 | - '-X=main.Commit={{ .Commit }}'
19 | - '-extldflags=-static'
20 | goos:
21 | - 'freebsd'
22 | - 'windows'
23 | - 'linux'
24 | - 'darwin'
25 | goarch:
26 | - '386'
27 | - 'amd64'
28 | - 'arm'
29 | - 'arm64'
30 | ignore:
31 | - goos: darwin
32 | goarch: '386'
33 | binary: '{{ .ProjectName }}_v{{ .Version }}'
34 |
35 | archives:
36 | - format: 'zip'
37 | name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}'
38 |
39 | checksum:
40 | name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS'
41 | algorithm: 'sha256'
42 |
43 | signs:
44 | - artifacts: 'checksum'
45 | args:
46 | - '--batch'
47 | - '--local-user'
48 | - '{{ .Env.GPG_FINGERPRINT }}'
49 | - '--output'
50 | - '${signature}'
51 | - '--detach-sign'
52 | - '${artifact}'
53 |
54 | changelog:
55 | use: 'github'
56 | sort: 'asc'
57 |
58 | release:
59 | draft: false
60 | mode: 'replace'
61 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | dev:
2 | @go install ./...
3 | .PHONY: dev
4 |
5 | generate:
6 | @rm -rf docs/
7 | @go generate ./...
8 | .PHONY: generate
9 |
10 | test:
11 | @go test -count=1 -shuffle=on -short ./...
12 | .PHONY: test
13 |
14 | test-acc:
15 | @TF_ACC=1 go test -count=1 -shuffle=on -race ./...
16 | .PHONY: test-acc
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Terraform FileSystem Provider
2 |
3 | This is a [Terraform][terraform] provider for managing the local filesystem with
4 | Terraform. It enables you to treat "files as code" the same way you already
5 | treat infrastructure as code!
6 |
7 |
8 | ## Installation
9 |
10 | 1. Download the latest compiled binary from [GitHub releases][releases].
11 |
12 | 1. Untar the archive.
13 |
14 | 1. Move it into `$HOME/.terraform.d/plugins`:
15 |
16 | ```sh
17 | $ mkdir -p $HOME/.terraform.d/plugins
18 | $ mv terraform-provider-filesystem $HOME/.terraform.d/plugins/terraform-provider-filesystem
19 | ```
20 |
21 | 1. Create your Terraform configurations as normal, and run `terraform init`:
22 |
23 | ```sh
24 | $ terraform init
25 | ```
26 |
27 | This will find the plugin locally.
28 |
29 |
30 | ## Usage
31 |
32 | 1. Create a Terraform configuration file:
33 |
34 | ```hcl
35 | resource "filesystem_file_writer" "example" {
36 | path = "file.txt"
37 | contents = "hello world"
38 | }
39 |
40 | resource "filesystem_file_reader" "example" {
41 | path = "${filesystem_file_writer.example.path}"
42 | }
43 | ```
44 |
45 | 1. Run `terraform init` to pull in the provider:
46 |
47 | ```sh
48 | $ terraform init
49 | ```
50 |
51 | 1. Run `terraform plan` and `terraform apply` to interact with the filesystem:
52 |
53 | ```sh
54 | $ terraform plan
55 |
56 | $ terraform apply
57 | ```
58 |
59 | ## Examples
60 |
61 | For more examples, please see the [examples][examples] folder in this
62 | repository.
63 |
64 | ## Reference
65 |
66 | ### Filesystem Reader
67 |
68 | #### Usage
69 |
70 | ```hcl
71 | resource "filesystem_file_reader" "read" {
72 | path = "my-file.txt"
73 | }
74 | ```
75 |
76 | #### Arguments
77 |
78 | Arguments are provided as inputs to the resource, in the `*.tf` file.
79 |
80 | - `path` `(string, required)` - the path to the file on disk.
81 |
82 | - `root` `(string: $CWD)` - the root of the Terraform configurations. By
83 | default, this will be the current working directory. If you're running
84 | Terraform against configurations outside of the working directory (like
85 | `terraform apply ../../foo`), set this value to `${path.module}`.
86 |
87 | #### Attributes
88 |
89 | Attributes are values that are only known after creation.
90 |
91 | - `contents` `(string)` - the contents of the file as a string. Contents are
92 | converted to a string, so it is not recommended you use this resource on
93 | binary files.
94 |
95 | - `name` `(string)` - the name of the file.
96 |
97 | - `size` `(int)` - the size of the file in bytes.
98 |
99 | - `mode` `(int)` - the permissions on the file in octal.
100 |
101 |
102 | ### Filesystem Writer
103 |
104 | #### Usage
105 |
106 | ```hcl
107 | resource "filesystem_file_writer" "write" {
108 | path = "my-file.txt"
109 | contents = "hello world!"
110 | }
111 | ```
112 |
113 | #### Arguments
114 |
115 | - `path` `(string, required)` - the path to the file on disk.
116 |
117 | - `contents` `(string, required)` - the contents of the file as a string.
118 |
119 | - `root` `(string: $CWD)` - the root of the Terraform configurations. By
120 | default, this will be the current working directory. If you're running
121 | Terraform against configurations outside of the working directory (like
122 | `terraform apply ../../foo`), set this value to `${path.module}`.
123 |
124 | - `create_parent_dirs` `(bool: true)` - create parent directories if they do not
125 | exist. By default, this is true. If set to false, the parent directories of
126 | the file must exist or this resource will error.
127 |
128 | - `delete_on_destroy` `(bool: true)` - delete this file on destroy. Set this to
129 | false and Terraform will leave the file on disk on `terraform destroy`.
130 |
131 | - `mode` `(int)` - the permissions on the file in octal.
132 |
133 | #### Attributes
134 |
135 | - `name` `(string)` - the name of the file.
136 |
137 | - `size` `(int)` - the size of the file in bytes.
138 |
139 | ## FAQ
140 |
141 | **Q: How is this different than the built-in `${file()}` function?**
142 | A: The built-in `file` function resolves paths and files at compile time. This
143 | means the file must exist before Terraform can begin executing. In some
144 | situations, the Terraform run itself may create files, but they will not exist
145 | at start time. This Terraform provider enables you to treat files just like
146 | other cloud resources, resolving them at runtime. This allows you to read and
147 | write files from other sources without worrying about dependency ordering.
148 |
149 | **Q: How is this different than [terraform-provider-local][terraform-provider-local]?**
150 | A: There are quite a few differences:
151 |
152 | 1. The equivalent "reader" is a data source. Data sources are resolved before
153 | resources run, meaning it is not possible to use the data source to read a file
154 | that is created _during_ the terraform run. Terraform will fail early that it
155 | could not read the file. This provider specifically addresses that challenge by
156 | using a resource instead of a data source.
157 |
158 | 1. The equivalent "reader" does not expose all the fields of the stat file (like
159 | mode and owner permissions).
160 |
161 | 1. The equivalent "writer" does not allow setting file permissions, controlling
162 | parent directory creation, or controlling deletion behavior. Additionally, as a
163 | **super ultra bad thing**, the file permissions are written as 0777 (globally
164 | executable), leaving a large security loophole.
165 |
166 | 1. The equivalent "writer" does not use an atomic file write. For large file
167 | chunks, this can result in a partially committed file and/or improper
168 | permissions that compromise security.
169 |
170 | 1. Neither the equivalent "reader" nor the "writer" limit the size of the file
171 | being read/written. This poses a security threat as an attacker could overflow
172 | the process (think about Terraform running arbitrary configuration as a hosted
173 | service).
174 |
175 | 1. The terraform-provider-local stores the full path of the file in the state,
176 | rendering the configurations un-portable. This provider calculates the filepath
177 | relative to the Terraform module, allowing for more flexibility.
178 |
179 | **Q: Is it secure?**
180 | A: The contents of files written and read are stored **in plain text** in the
181 | statefile. They are marked as sensitive in the output, but they will still be
182 | stored in the state. This is required in order for other resources to be able to
183 | read the values. If you are using these resources with sensitive data, you
184 | should encrypt your state using [remote state][remote-state].
185 |
186 | ## License & Author
187 |
188 | ```
189 | Copyright 2018 Google, Inc.
190 | Copyright 2018 Seth Vargo
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 | ```
204 |
205 | [terraform]: https://www.terraform.io/
206 | [releases]: https://github.com/sethvargo/terraform-provider-filesystem/releases
207 | [examples]: https://github.com/sethvargo/terraform-provider-filesystem/tree/master/examples
208 | [remote-state]: https://www.terraform.io/docs/state/remote.html
209 | [terraform-provider-local]: https://github.com/terraform-providers/terraform-provider-local
210 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "filesystem Provider"
4 | subcategory: ""
5 | description: |-
6 |
7 | ---
8 |
9 | # filesystem Provider
10 |
11 |
12 |
13 | ## Example Usage
14 |
15 | ```terraform
16 | provider "filesystem" {}
17 | ```
18 |
19 |
20 | ## Schema
21 |
--------------------------------------------------------------------------------
/docs/resources/file_reader.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "filesystem_file_reader Resource - terraform-provider-filesystem"
4 | subcategory: ""
5 | description: |-
6 | Reads a file on disk as a resource.
7 | ---
8 |
9 | # filesystem_file_reader (Resource)
10 |
11 | Reads a file on disk as a resource.
12 |
13 | ## Example Usage
14 |
15 | ```terraform
16 | // Assume that "file.txt" does not exist at the start of the run. Perhaps
17 | // there's another resource that creates the file as part of the Terraform run.
18 | // As such, `${file("...")}` would not work as it resolves at the start of the
19 | // run.
20 |
21 | resource "null_resource" "pretend" {
22 | provisioner "local-exec" {
23 | command = "echo 'hello' > ${path.module}/file.txt"
24 | }
25 | }
26 |
27 | resource "filesystem_file_reader" "read" {
28 | path = "${path.module}/file.txt"
29 |
30 | depends_on = ["null_resource.pretend"]
31 | }
32 |
33 | output "contents" {
34 | value = filesystem_file_reader.read.contents
35 | }
36 | ```
37 |
38 |
39 | ## Schema
40 |
41 | ### Required
42 |
43 | - `path` (String) Path to read the file on disk
44 |
45 | ### Optional
46 |
47 | - `root` (String) Path to the root of the module
48 |
49 | ### Read-Only
50 |
51 | - `contents` (String, Sensitive) Raw file contents
52 | - `id` (String) The ID of this resource.
53 | - `mode` (String) File mode bits
54 | - `name` (String) Basename of the file
55 | - `size` (Number) Size of the file in bytes
56 |
57 |
58 |
--------------------------------------------------------------------------------
/docs/resources/file_writer.md:
--------------------------------------------------------------------------------
1 | ---
2 | # generated by https://github.com/hashicorp/terraform-plugin-docs
3 | page_title: "filesystem_file_writer Resource - terraform-provider-filesystem"
4 | subcategory: ""
5 | description: |-
6 | Creates and manages a file on disk.
7 | ---
8 |
9 | # filesystem_file_writer (Resource)
10 |
11 | Creates and manages a file on disk.
12 |
13 | ## Example Usage
14 |
15 | ```terraform
16 | // Generate an SSH key
17 | resource "tls_private_key" "ssh" {
18 | algorithm = "RSA"
19 | rsa_bits = "4096"
20 | }
21 |
22 | // Save the SSH keys to disk
23 | resource "filesystem_file_writer" "save-private-key" {
24 | path = "${path.module}/.ssh/id_rsa"
25 | contents = tls_private_key.ssh.private_key_pem
26 | mode = "0600"
27 | }
28 |
29 | resource "filesystem_file_writer" "save-public-key" {
30 | path = "${path.module}/.ssh/id_rsa.pub"
31 | contents = tls_private_key.ssh.public_key_openssh
32 | mode = "0644"
33 | }
34 | ```
35 |
36 |
37 | ## Schema
38 |
39 | ### Required
40 |
41 | - `path` (String) Path to write the file on disk
42 |
43 | ### Optional
44 |
45 | - `contents` (String, Sensitive) Raw file contents
46 | - `create_parent_dirs` (Boolean) Create parent directories if they do not exist
47 | - `delete_on_destroy` (Boolean) Delete the created file on destroy
48 | - `mode` (String) File mode bits
49 | - `root` (String) Path to the root of the module
50 |
51 | ### Read-Only
52 |
53 | - `id` (String) The ID of this resource.
54 | - `name` (String) Basename of the file
55 | - `size` (Number) Size of the file in bytes
56 |
57 |
58 |
--------------------------------------------------------------------------------
/examples/provider/provider.tf:
--------------------------------------------------------------------------------
1 | provider "filesystem" {}
2 |
--------------------------------------------------------------------------------
/examples/resources/filesystem_file_reader/resource.tf:
--------------------------------------------------------------------------------
1 | // Assume that "file.txt" does not exist at the start of the run. Perhaps
2 | // there's another resource that creates the file as part of the Terraform run.
3 | // As such, `${file("...")}` would not work as it resolves at the start of the
4 | // run.
5 |
6 | resource "null_resource" "pretend" {
7 | provisioner "local-exec" {
8 | command = "echo 'hello' > ${path.module}/file.txt"
9 | }
10 | }
11 |
12 | resource "filesystem_file_reader" "read" {
13 | path = "${path.module}/file.txt"
14 |
15 | depends_on = ["null_resource.pretend"]
16 | }
17 |
18 | output "contents" {
19 | value = filesystem_file_reader.read.contents
20 | }
21 |
--------------------------------------------------------------------------------
/examples/resources/filesystem_file_writer/resource.tf:
--------------------------------------------------------------------------------
1 | // Generate an SSH key
2 | resource "tls_private_key" "ssh" {
3 | algorithm = "RSA"
4 | rsa_bits = "4096"
5 | }
6 |
7 | // Save the SSH keys to disk
8 | resource "filesystem_file_writer" "save-private-key" {
9 | path = "${path.module}/.ssh/id_rsa"
10 | contents = tls_private_key.ssh.private_key_pem
11 | mode = "0600"
12 | }
13 |
14 | resource "filesystem_file_writer" "save-public-key" {
15 | path = "${path.module}/.ssh/id_rsa.pub"
16 | contents = tls_private_key.ssh.public_key_openssh
17 | mode = "0644"
18 | }
19 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/sethvargo/terraform-provider-filesystem
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.1
7 | github.com/mitchellh/go-homedir v1.1.0
8 | )
9 |
10 | require (
11 | github.com/agext/levenshtein v1.2.3 // indirect
12 | github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
13 | github.com/davecgh/go-spew v1.1.1 // indirect
14 | github.com/fatih/color v1.13.0 // indirect
15 | github.com/golang/protobuf v1.5.3 // indirect
16 | github.com/google/go-cmp v0.5.9 // indirect
17 | github.com/hashicorp/errwrap v1.1.0 // indirect
18 | github.com/hashicorp/go-checkpoint v0.5.0 // indirect
19 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
20 | github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect
21 | github.com/hashicorp/go-hclog v1.4.0 // indirect
22 | github.com/hashicorp/go-multierror v1.1.1 // indirect
23 | github.com/hashicorp/go-plugin v1.4.8 // indirect
24 | github.com/hashicorp/go-uuid v1.0.3 // indirect
25 | github.com/hashicorp/go-version v1.6.0 // indirect
26 | github.com/hashicorp/hc-install v0.4.0 // indirect
27 | github.com/hashicorp/hcl/v2 v2.15.0 // indirect
28 | github.com/hashicorp/logutils v1.0.0 // indirect
29 | github.com/hashicorp/terraform-exec v0.17.3 // indirect
30 | github.com/hashicorp/terraform-json v0.14.0 // indirect
31 | github.com/hashicorp/terraform-plugin-go v0.14.2 // indirect
32 | github.com/hashicorp/terraform-plugin-log v0.7.0 // indirect
33 | github.com/hashicorp/terraform-registry-address v0.1.0 // indirect
34 | github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect
35 | github.com/hashicorp/yamux v0.1.1 // indirect
36 | github.com/imdario/mergo v0.3.13 // indirect
37 | github.com/kr/pretty v0.3.0 // indirect
38 | github.com/mattn/go-colorable v0.1.13 // indirect
39 | github.com/mattn/go-isatty v0.0.17 // indirect
40 | github.com/mitchellh/copystructure v1.2.0 // indirect
41 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect
42 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect
43 | github.com/mitchellh/mapstructure v1.5.0 // indirect
44 | github.com/mitchellh/reflectwalk v1.0.2 // indirect
45 | github.com/oklog/run v1.1.0 // indirect
46 | github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
47 | github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect
48 | github.com/vmihailenco/tagparser v0.1.2 // indirect
49 | github.com/zclconf/go-cty v1.12.1 // indirect
50 | golang.org/x/crypto v0.21.0 // indirect
51 | golang.org/x/net v0.23.0 // indirect
52 | golang.org/x/sys v0.18.0 // indirect
53 | golang.org/x/text v0.14.0 // indirect
54 | google.golang.org/appengine v1.6.7 // indirect
55 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
56 | google.golang.org/grpc v1.56.3 // indirect
57 | google.golang.org/protobuf v1.33.0 // indirect
58 | )
59 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
3 | github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk=
4 | github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
5 | github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ=
6 | github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
7 | github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
8 | github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
9 | github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
10 | github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
11 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
12 | github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFUye+ZcSR6opIgz9Co7WcDx6ZcY+RjfFHoA0I=
13 | github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
14 | github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=
15 | github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
16 | github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
17 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
18 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
19 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
20 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
21 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
22 | github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
23 | github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
24 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
25 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
26 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
27 | github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
28 | github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
29 | github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
30 | github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
31 | github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
32 | github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
33 | github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
34 | github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
35 | github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
36 | github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
37 | github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
38 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
39 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
40 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
41 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
42 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
43 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
44 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
45 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
46 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
47 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
48 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
49 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
50 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
51 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
52 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
53 | github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU=
54 | github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg=
55 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
56 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
57 | github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
58 | github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
59 | github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI=
60 | github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs=
61 | github.com/hashicorp/go-hclog v1.4.0 h1:ctuWFGrhFha8BnnzxqeRGidlEcQkDyL5u8J8t5eA11I=
62 | github.com/hashicorp/go-hclog v1.4.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
63 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
64 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
65 | github.com/hashicorp/go-plugin v1.4.8 h1:CHGwpxYDOttQOY7HOWgETU9dyVjOXzniXDqJcYJE1zM=
66 | github.com/hashicorp/go-plugin v1.4.8/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s=
67 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
68 | github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
69 | github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
70 | github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
71 | github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
72 | github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
73 | github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
74 | github.com/hashicorp/hc-install v0.4.0 h1:cZkRFr1WVa0Ty6x5fTvL1TuO1flul231rWkGH92oYYk=
75 | github.com/hashicorp/hc-install v0.4.0/go.mod h1:5d155H8EC5ewegao9A4PUTMNPZaq+TbOzkJJZ4vrXeI=
76 | github.com/hashicorp/hcl/v2 v2.15.0 h1:CPDXO6+uORPjKflkWCCwoWc9uRp+zSIPcCQ+BrxV7m8=
77 | github.com/hashicorp/hcl/v2 v2.15.0/go.mod h1:JRmR89jycNkrrqnMmvPDMd56n1rQJ2Q6KocSLCMCXng=
78 | github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
79 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
80 | github.com/hashicorp/terraform-exec v0.17.3 h1:MX14Kvnka/oWGmIkyuyvL6POx25ZmKrjlaclkx3eErU=
81 | github.com/hashicorp/terraform-exec v0.17.3/go.mod h1:+NELG0EqQekJzhvikkeQsOAZpsw0cv/03rbeQJqscAI=
82 | github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e17dKDpqV7s=
83 | github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM=
84 | github.com/hashicorp/terraform-plugin-go v0.14.2 h1:rhsVEOGCnY04msNymSvbUsXfRLKh9znXZmHlf5e8mhE=
85 | github.com/hashicorp/terraform-plugin-go v0.14.2/go.mod h1:Q12UjumPNGiFsZffxOsA40Tlz1WVXt2Evh865Zj0+UA=
86 | github.com/hashicorp/terraform-plugin-log v0.7.0 h1:SDxJUyT8TwN4l5b5/VkiTIaQgY6R+Y2BQ0sRZftGKQs=
87 | github.com/hashicorp/terraform-plugin-log v0.7.0/go.mod h1:p4R1jWBXRTvL4odmEkFfDdhUjHf9zcs/BCoNHAc7IK4=
88 | github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.1 h1:zHcMbxY0+rFO9gY99elV/XC/UnQVg7FhRCbj1i5b7vM=
89 | github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.1/go.mod h1:+tNlb0wkfdsDJ7JEiERLz4HzM19HyiuIoGzTsM7rPpw=
90 | github.com/hashicorp/terraform-registry-address v0.1.0 h1:W6JkV9wbum+m516rCl5/NjKxCyTVaaUBbzYcMzBDO3U=
91 | github.com/hashicorp/terraform-registry-address v0.1.0/go.mod h1:EnyO2jYO6j29DTHbJcm00E5nQTFeTtyZH3H5ycydQ5A=
92 | github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0=
93 | github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg=
94 | github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
95 | github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
96 | github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
97 | github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
98 | github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
99 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
100 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
101 | github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
102 | github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
103 | github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck=
104 | github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
105 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
106 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
107 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
108 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
109 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
110 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
111 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
112 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
113 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
114 | github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
115 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
116 | github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
117 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
118 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
119 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
120 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
121 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
122 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
123 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
124 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
125 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
126 | github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
127 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
128 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
129 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
130 | github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
131 | github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
132 | github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
133 | github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
134 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
135 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
136 | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
137 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
138 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
139 | github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
140 | github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
141 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
142 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
143 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
144 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
145 | github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
146 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
147 | github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4=
148 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
149 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
150 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
151 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
152 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
153 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
154 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
155 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
156 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
157 | github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
158 | github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
159 | github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
160 | github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
161 | github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
162 | github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U=
163 | github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
164 | github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
165 | github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
166 | github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
167 | github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
168 | github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
169 | github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
170 | github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
171 | github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
172 | github.com/zclconf/go-cty v1.12.1 h1:PcupnljUm9EIvbgSHQnHhUr3fO6oFmkOrvs2BAFNXXY=
173 | github.com/zclconf/go-cty v1.12.1/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA=
174 | github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
175 | golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
176 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
177 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
178 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
179 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
180 | golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
181 | golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
182 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
183 | golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
184 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
185 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
186 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
187 | golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
188 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
189 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
190 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
191 | golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
192 | golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
193 | golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
194 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
195 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
196 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
197 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
198 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
199 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
200 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
201 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
202 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
203 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
204 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
205 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
206 | golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
207 | golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
208 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
209 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
210 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
211 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
212 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
213 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
214 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
215 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
216 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
217 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
218 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
219 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
220 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
221 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
222 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
223 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
224 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
225 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
226 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
227 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
228 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
229 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
230 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
231 | google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
232 | google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
233 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
234 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
235 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
236 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
237 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
238 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
239 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
240 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
241 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
242 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
243 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
244 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
245 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
246 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
247 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
248 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
249 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
250 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
251 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
252 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
253 |
--------------------------------------------------------------------------------
/internal/filesystem/helpers.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google, Inc.
2 | // Copyright 2018 Seth Vargo
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package filesystem
17 |
18 | import (
19 | "fmt"
20 | "io"
21 | "os"
22 | "path/filepath"
23 | "strconv"
24 |
25 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
26 | homedir "github.com/mitchellh/go-homedir"
27 | )
28 |
29 | const (
30 | defaultFilePerms = 0644
31 |
32 | // fileSizeLimit is the limit on the size of the file to read.
33 | fileSizeLimit = 1 * 1024 * 1024 * 1024 // 1 GiB
34 | )
35 |
36 | // expandRelativePath expands the given file path taking into account home directory and
37 | // relative paths to the CWD.
38 | func expandRelativePath(p, root string) (string, error) {
39 | p, err := homedir.Expand(p)
40 | if err != nil {
41 | return "", fmt.Errorf("failed to expand homedir: %w", err)
42 | }
43 |
44 | p, err = filepath.Abs(p)
45 | if err != nil {
46 | return "", fmt.Errorf("failed to expand path: %w", err)
47 | }
48 |
49 | if root == "" {
50 | cwd, err := os.Getwd()
51 | if err != nil {
52 | return "", fmt.Errorf("failed to get working directory: %w", err)
53 | }
54 | root = cwd
55 | }
56 |
57 | p, err = filepath.Rel(root, p)
58 | if err != nil {
59 | return "", fmt.Errorf("failed to get path relative to module: %w", err)
60 | }
61 |
62 | return p, nil
63 | }
64 |
65 | type fileStat struct {
66 | name string
67 | contents string
68 | size int64
69 | mode string
70 | }
71 |
72 | func readFileAndStats(p string) (*fileStat, error) {
73 | f, err := os.Open(p)
74 | if err != nil {
75 | return nil, fmt.Errorf("failed to open: %w", err)
76 | }
77 | defer f.Close()
78 |
79 | stat, err := f.Stat()
80 | if err != nil {
81 | return nil, fmt.Errorf("failed to stat: %w", err)
82 | }
83 |
84 | if stat.Size() > fileSizeLimit {
85 | return nil, fmt.Errorf("file is too large (> 1GiB)")
86 | }
87 |
88 | if stat.IsDir() {
89 | return nil, fmt.Errorf("is a directory")
90 | }
91 |
92 | contents, err := io.ReadAll(f)
93 | if err != nil {
94 | return nil, fmt.Errorf("failed to read: %w", err)
95 | }
96 |
97 | return &fileStat{
98 | name: stat.Name(),
99 | contents: string(contents),
100 | size: stat.Size(),
101 | mode: fmt.Sprintf("%#o", stat.Mode()),
102 | }, nil
103 | }
104 |
105 | // atomicWriteInput is used as input to the atomicWrite function.
106 | type atomicWriteInput struct {
107 | dest string
108 | contents string
109 | createDirs bool
110 | perms os.FileMode
111 | }
112 |
113 | // atomicWrite atomically writes a file with the given contents and permissions.
114 | func atomicWrite(i *atomicWriteInput) error {
115 | d, err := os.MkdirTemp("", "terraform-provider-filesystem")
116 | if err != nil {
117 | return fmt.Errorf("failed to create temp dir: %w", err)
118 | }
119 | defer os.RemoveAll(d)
120 |
121 | b := filepath.Base(i.dest)
122 | f, err := os.CreateTemp(d, b)
123 | if err != nil {
124 | return fmt.Errorf("failed to create temp file: %w", err)
125 | }
126 |
127 | if _, err := f.Write([]byte(i.contents)); err != nil {
128 | return fmt.Errorf("failed to write: %w", err)
129 | }
130 |
131 | if err := f.Sync(); err != nil {
132 | return fmt.Errorf("failed to sync: %w", err)
133 | }
134 |
135 | if err := f.Close(); err != nil {
136 | return fmt.Errorf("failed to close: %w", err)
137 | }
138 |
139 | // If the user did not explicitly set permissions, attempt to lookup the
140 | // current permissions on the file. If the file does not exist, fall back to
141 | // the default. Otherwise, inherit the current permissions.
142 | perms := i.perms
143 | if perms == 0 {
144 | stat, err := os.Stat(i.dest)
145 | if err != nil {
146 | if os.IsNotExist(err) {
147 | perms = defaultFilePerms
148 | } else {
149 | return fmt.Errorf("failed to stat file: %w", err)
150 | }
151 | } else {
152 | perms = stat.Mode()
153 | }
154 | }
155 |
156 | parent := filepath.Dir(i.dest)
157 | if _, err := os.Stat(parent); os.IsNotExist(err) {
158 | if i.createDirs {
159 | if err := os.MkdirAll(parent, 0700); err != nil {
160 | return fmt.Errorf("failed to make parent directory: %w", err)
161 | }
162 | } else {
163 | return fmt.Errorf("no parent directory")
164 | }
165 | }
166 |
167 | if err := os.Rename(f.Name(), i.dest); err != nil {
168 | return fmt.Errorf("failed to rename: %w", err)
169 | }
170 |
171 | if err := os.Chmod(i.dest, perms); err != nil {
172 | return fmt.Errorf("failed to chmod: %w", err)
173 | }
174 |
175 | return nil
176 | }
177 |
178 | func parseFileMode(s string) (os.FileMode, error) {
179 | mode, err := strconv.ParseUint(s, 8, 32)
180 | if err != nil {
181 | return 0, fmt.Errorf("failed to parse mode: %w", err)
182 | }
183 | return os.FileMode(mode), nil
184 | }
185 |
186 | func diffSuppressRelativePath(_, old, new string, d *schema.ResourceData) bool {
187 | root := d.Get("root").(string)
188 |
189 | p, err := expandRelativePath(new, root)
190 | if err != nil {
191 | return false
192 | }
193 |
194 | return p == old
195 | }
196 |
--------------------------------------------------------------------------------
/internal/filesystem/provider.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google, Inc.
2 | // Copyright 2018 Seth Vargo
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package filesystem
17 |
18 | import (
19 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
20 | )
21 |
22 | func init() {
23 | schema.DescriptionKind = schema.StringMarkdown
24 | }
25 |
26 | func New(version string) func() *schema.Provider {
27 | return func() *schema.Provider {
28 | p := &schema.Provider{
29 | ResourcesMap: map[string]*schema.Resource{
30 | "filesystem_file_reader": resourceFileReader(),
31 | "filesystem_file_writer": resourceFileWriter(),
32 | },
33 | }
34 | return p
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/internal/filesystem/provider_test.go:
--------------------------------------------------------------------------------
1 | package filesystem
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
7 | )
8 |
9 | var testProviders = map[string]func() (*schema.Provider, error){
10 | "filesystem": func() (*schema.Provider, error) {
11 | return New("test")(), nil
12 | },
13 | }
14 |
15 | func TestProvider(t *testing.T) {
16 | if err := New("test")().InternalValidate(); err != nil {
17 | t.Fatal(err)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/internal/filesystem/resource_file_reader.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google, Inc.
2 | // Copyright 2018 Seth Vargo
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package filesystem
17 |
18 | import (
19 | "context"
20 |
21 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
22 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
23 | )
24 |
25 | func resourceFileReader() *schema.Resource {
26 | return &schema.Resource{
27 | Description: "Reads a file on disk as a resource.",
28 |
29 | CreateContext: resourceFileReaderCreate,
30 | ReadContext: resourceFileReaderRead,
31 | DeleteContext: resourceFileReaderDelete,
32 |
33 | Schema: map[string]*schema.Schema{
34 | "path": {
35 | Type: schema.TypeString,
36 | Description: "Path to read the file on disk",
37 | ForceNew: true,
38 | Required: true,
39 | DiffSuppressFunc: diffSuppressRelativePath,
40 | },
41 |
42 | "root": {
43 | Type: schema.TypeString,
44 | Description: "Path to the root of the module",
45 | ForceNew: true,
46 | Optional: true,
47 | },
48 |
49 | //
50 | // Computed values
51 | //
52 | "contents": {
53 | Type: schema.TypeString,
54 | Description: "Raw file contents",
55 | Computed: true,
56 | Sensitive: true,
57 | },
58 |
59 | "name": {
60 | Type: schema.TypeString,
61 | Description: "Basename of the file",
62 | Computed: true,
63 | },
64 |
65 | "size": {
66 | Type: schema.TypeInt,
67 | Description: "Size of the file in bytes",
68 | Computed: true,
69 | },
70 |
71 | "mode": {
72 | Type: schema.TypeString,
73 | Description: "File mode bits",
74 | Computed: true,
75 | },
76 | },
77 | }
78 | }
79 |
80 | // resourceFileReaderCreate expands the file path and calls Read.
81 | func resourceFileReaderCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
82 | path := d.Get("path").(string)
83 | root := d.Get("root").(string)
84 |
85 | p, err := expandRelativePath(path, root)
86 | if err != nil {
87 | return diag.FromErr(err)
88 | }
89 |
90 | d.SetId(p)
91 |
92 | return resourceFileReaderRead(ctx, d, meta)
93 | }
94 |
95 | // resourceFileReaderRead reads the file contents from disk. It returns an error if
96 | // it fails to read the contents. The entire file contents are read into memory
97 | // because Terraform cannot pass around an io.Reader.
98 | func resourceFileReaderRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
99 | p := d.Id()
100 |
101 | stat, err := readFileAndStats(p)
102 | if err != nil {
103 | return diag.FromErr(err)
104 | }
105 |
106 | d.Set("name", stat.name)
107 | d.Set("contents", stat.contents)
108 | d.Set("size", stat.size)
109 | d.Set("mode", stat.mode)
110 |
111 | return nil
112 | }
113 |
114 | // resourceFileReaderDelete deletes our tracking of that file. It is basically a
115 | // no-op. It does not delete the file on disk.
116 | func resourceFileReaderDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
117 | d.SetId("")
118 | return nil
119 | }
120 |
--------------------------------------------------------------------------------
/internal/filesystem/resource_file_reader_test.go:
--------------------------------------------------------------------------------
1 | package filesystem
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path/filepath"
7 | "testing"
8 |
9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
10 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
11 | )
12 |
13 | func TestFileSystemFileReader(t *testing.T) {
14 | t.Run("local", func(t *testing.T) {
15 | t.Parallel()
16 |
17 | contents := "This is some content!"
18 |
19 | f, err := os.CreateTemp("", "")
20 | if err != nil {
21 | t.Fatal(err)
22 | }
23 | defer os.Remove(f.Name())
24 |
25 | if _, err := f.Write([]byte(contents)); err != nil {
26 | t.Fatal(err)
27 | }
28 | if err := f.Sync(); err != nil {
29 | t.Fatal(err)
30 | }
31 | if err := f.Close(); err != nil {
32 | t.Fatal(err)
33 | }
34 | if err := os.Chmod(f.Name(), 0644); err != nil {
35 | t.Fatal(err)
36 | }
37 |
38 | config := fmt.Sprintf(`
39 | resource "filesystem_file_reader" "file" {
40 | path = "%s"
41 | }
42 | `, f.Name())
43 |
44 | resource.UnitTest(t, resource.TestCase{
45 | IsUnitTest: true,
46 | ProviderFactories: testProviders,
47 | Steps: []resource.TestStep{
48 | {
49 | Config: config,
50 | Check: func(s *terraform.State) error {
51 | attrs := s.RootModule().Resources["filesystem_file_reader.file"].Primary.Attributes
52 |
53 | name := filepath.Base(f.Name())
54 | if act, exp := attrs["name"], name; act != exp {
55 | t.Errorf("expected %q to be %q", act, exp)
56 | }
57 | if act, exp := attrs["mode"], "0644"; act != exp {
58 | t.Errorf("expected %q to be %q", act, exp)
59 | }
60 | if act, exp := attrs["contents"], contents; act != exp {
61 | t.Errorf("expected %q to be %q", act, exp)
62 | }
63 | return nil
64 | },
65 | },
66 | },
67 | CheckDestroy: func(*terraform.State) error {
68 | // The reader should NOT destroy the file.
69 | if _, err := os.Stat(f.Name()); os.IsNotExist(err) {
70 | t.Errorf("file should not have been deleted")
71 | }
72 | return nil
73 | },
74 | })
75 | })
76 | }
77 |
--------------------------------------------------------------------------------
/internal/filesystem/resource_file_writer.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google, Inc.
2 | // Copyright 2018 Seth Vargo
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package filesystem
17 |
18 | import (
19 | "context"
20 | "fmt"
21 | "os"
22 |
23 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
24 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
25 | )
26 |
27 | func resourceFileWriter() *schema.Resource {
28 | return &schema.Resource{
29 | Description: "Creates and manages a file on disk.",
30 |
31 | CreateContext: resourceFileWriterCreate,
32 | ReadContext: resourceFileWriterRead,
33 | UpdateContext: resourceFileWriterUpdate,
34 | DeleteContext: resourceFileWriterDelete,
35 |
36 | Schema: map[string]*schema.Schema{
37 | "path": {
38 | Type: schema.TypeString,
39 | Description: "Path to write the file on disk",
40 | ForceNew: true,
41 | Required: true,
42 | DiffSuppressFunc: diffSuppressRelativePath,
43 | },
44 |
45 | "root": {
46 | Type: schema.TypeString,
47 | Description: "Path to the root of the module",
48 | ForceNew: true,
49 | Optional: true,
50 | },
51 |
52 | "contents": {
53 | Type: schema.TypeString,
54 | Description: "Raw file contents",
55 | Optional: true,
56 | Sensitive: true,
57 | },
58 |
59 | "mode": {
60 | Type: schema.TypeString,
61 | Description: "File mode bits",
62 | Default: "0644",
63 | Optional: true,
64 | },
65 |
66 | "create_parent_dirs": {
67 | Type: schema.TypeBool,
68 | Description: "Create parent directories if they do not exist",
69 | Default: true,
70 | Optional: true,
71 | },
72 |
73 | "delete_on_destroy": {
74 | Type: schema.TypeBool,
75 | Description: "Delete the created file on destroy",
76 | Default: true,
77 | Optional: true,
78 | },
79 |
80 | //
81 | // Computed
82 | //
83 | "name": {
84 | Type: schema.TypeString,
85 | Description: "Basename of the file",
86 | Computed: true,
87 | },
88 |
89 | "size": {
90 | Type: schema.TypeInt,
91 | Description: "Size of the file in bytes",
92 | Computed: true,
93 | },
94 | },
95 | }
96 | }
97 |
98 | // resourceFileWriterCreate expands the file path and writes the file to disk.
99 | func resourceFileWriterCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
100 | path := d.Get("path").(string)
101 | root := d.Get("root").(string)
102 |
103 | p, err := expandRelativePath(path, root)
104 | if err != nil {
105 | return diag.FromErr(err)
106 | }
107 |
108 | d.Set("path", p)
109 |
110 | mode, err := parseFileMode(d.Get("mode").(string))
111 | if err != nil {
112 | return diag.FromErr(err)
113 | }
114 |
115 | if err := atomicWrite(&atomicWriteInput{
116 | dest: p,
117 | contents: d.Get("contents").(string),
118 | createDirs: d.Get("create_parent_dirs").(bool),
119 | perms: os.FileMode(mode),
120 | }); err != nil {
121 | return diag.FromErr(err)
122 | }
123 |
124 | d.SetId(p)
125 |
126 | return resourceFileWriterRead(ctx, d, meta)
127 | }
128 |
129 | // resourceFileWriter reads the file contents from disk. It returns an error if
130 | // it fails to read the contents. The entire file contents are read into memory
131 | // because Terraform cannot pass around an io.Reader.
132 | func resourceFileWriterRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
133 | p := d.Id()
134 |
135 | stat, err := readFileAndStats(p)
136 | if err != nil {
137 | return diag.FromErr(err)
138 | }
139 |
140 | d.Set("path", p)
141 | d.Set("name", stat.name)
142 | d.Set("contents", stat.contents)
143 | d.Set("size", stat.size)
144 | d.Set("mode", stat.mode)
145 |
146 | return nil
147 | }
148 |
149 | // resourceFileWriterUpdate updates the file contents.
150 | func resourceFileWriterUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
151 | p := d.Id()
152 |
153 | // If the contents have changed, everything else will chagen too on the atomic
154 | // write, so just delegate.
155 | if d.HasChange("contents") {
156 | return resourceFileWriterCreate(ctx, d, meta)
157 | }
158 |
159 | if d.HasChange("mode") {
160 | mode, err := parseFileMode(d.Get("mode").(string))
161 | if err != nil {
162 | return diag.FromErr(err)
163 | }
164 |
165 | if err := os.Chmod(p, mode); err != nil {
166 | return diag.Errorf("failed to chmod: %s", err)
167 | }
168 |
169 | d.Set("mode", fmt.Sprintf("%#o", mode))
170 | }
171 |
172 | return nil
173 | }
174 |
175 | // resourceFileWriterDelete deletes the file if the user specified to delete it.
176 | func resourceFileWriterDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
177 | p := d.Id()
178 |
179 | if d.Get("delete_on_destroy").(bool) {
180 | if err := os.Remove(p); err != nil && !os.IsNotExist(err) {
181 | return diag.Errorf("failed to delete: %s", err)
182 | }
183 | }
184 |
185 | d.SetId("")
186 |
187 | return nil
188 | }
189 |
--------------------------------------------------------------------------------
/internal/filesystem/resource_file_writer_test.go:
--------------------------------------------------------------------------------
1 | package filesystem
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path/filepath"
7 | "testing"
8 |
9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
10 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
11 | )
12 |
13 | func TestFilesystemFileWriter(t *testing.T) {
14 | var cases = []struct {
15 | name string
16 | config string
17 | }{
18 | {
19 | "basic",
20 | `
21 | resource "filesystem_file_writer" "file" {
22 | path = "%s"
23 | contents = "%s"
24 | mode = "%s"
25 | }
26 | `,
27 | },
28 | {
29 | "delete_on_destroy",
30 | `
31 | resource "filesystem_file_writer" "file" {
32 | path = "%s"
33 | contents = "%s"
34 | mode = "%s"
35 |
36 | delete_on_destroy = "false"
37 | }
38 | `,
39 | },
40 | }
41 |
42 | for _, tc := range cases {
43 | tc := tc
44 |
45 | t.Run(tc.name, func(t *testing.T) {
46 | t.Parallel()
47 |
48 | f, err := os.CreateTemp("", "")
49 | if err != nil {
50 | t.Fatal(err)
51 | }
52 | defer os.Remove(f.Name())
53 | if err := f.Close(); err != nil {
54 | t.Fatal(err)
55 | }
56 |
57 | contents := "This is some content!"
58 | perms := "0755"
59 | config := fmt.Sprintf(tc.config, f.Name(), contents, perms)
60 |
61 | resource.UnitTest(t, resource.TestCase{
62 | ProviderFactories: testProviders,
63 | Steps: []resource.TestStep{
64 | {
65 | Config: config,
66 | Check: func(s *terraform.State) error {
67 | attrs := s.RootModule().Resources["filesystem_file_writer.file"].Primary.Attributes
68 |
69 | name := filepath.Base(f.Name())
70 | if act, exp := attrs["name"], name; act != exp {
71 | t.Errorf("expected %q to be %q", act, exp)
72 | }
73 | if act, exp := attrs["mode"], perms; act != exp {
74 | t.Errorf("expected %q to be %q", act, exp)
75 | }
76 | if act, exp := attrs["contents"], contents; act != exp {
77 | t.Errorf("expected %q to be %q", act, exp)
78 | }
79 |
80 | b, err := os.ReadFile(f.Name())
81 | if err != nil {
82 | t.Fatal(err)
83 | }
84 |
85 | if act, exp := string(b), contents; act != exp {
86 | t.Errorf("expected %q to be %q", act, exp)
87 | }
88 |
89 | return nil
90 | },
91 | },
92 | },
93 | CheckDestroy: func(s *terraform.State) error {
94 | attrs := s.RootModule().Resources["filesystem_file_writer.file"].Primary.Attributes
95 |
96 | _, err := os.Stat(f.Name())
97 |
98 | if attrs["delete_on_destroy"] == "true" {
99 | if !os.IsNotExist(err) {
100 | t.Errorf("expected file to be deleted")
101 | }
102 | } else {
103 | if os.IsNotExist(err) {
104 | t.Errorf("expected file to not be deleted")
105 | }
106 | }
107 |
108 | return nil
109 | },
110 | })
111 | })
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 |
6 | "github.com/hashicorp/terraform-plugin-sdk/v2/plugin"
7 | "github.com/sethvargo/terraform-provider-filesystem/internal/filesystem"
8 | )
9 |
10 | //go:generate terraform fmt -recursive ./examples/
11 | //go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs
12 |
13 | var (
14 | Version string = "dev"
15 | Commit string = ""
16 | )
17 |
18 | func main() {
19 | debugMode := flag.Bool("debug", false, "set to true to run the provider with support for debuggers like delve")
20 | flag.Parse()
21 |
22 | opts := &plugin.ServeOpts{
23 | Debug: *debugMode,
24 | ProviderAddr: "github.com/sethvargo/terraform-provider-filesystem",
25 | ProviderFunc: filesystem.New(Version),
26 | }
27 |
28 | plugin.Serve(opts)
29 | }
30 |
--------------------------------------------------------------------------------
/terraform-registry-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "metadata": {
4 | "protocol_versions": ["5.0"]
5 | }
6 | }
7 |
--------------------------------------------------------------------------------