├── .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 | --------------------------------------------------------------------------------