diff --git a/.changes/2.2.3.md b/.changes/2.2.3.md new file mode 100644 index 00000000..85600bc7 --- /dev/null +++ b/.changes/2.2.3.md @@ -0,0 +1,130 @@ +## 2.2.3 (May 18, 2022) + +NOTES: + +* resource/local_file: Update docs to prevent confusion that exactly one of the arguments `content`, + `sensitive_content`, `content_base64`, and `source` needs to be specified ([#123](https://github.com/hashicorp/terraform-provider-local/pull/123)). + +* resource/local_sensitive_file: Update docs to prevent confusion that exactly one of the arguments `content`, + `content_base64`, and `source` needs to be specified ([#123](https://github.com/hashicorp/terraform-provider-local/pull/123)). + +* No functional changes from 2.2.2. + +## 2.2.2 (March 11, 2022) + +NOTES: + +* resource/local_sensitive_file: Fixed typo in documentation (default permission is `"0700"`, not `"0777"`). +* No functional changes from 2.2.1. + +## 2.2.1 (March 10, 2022) + +NOTES: + +* This release is a republishing of the 2.2.0 release to fix release asset checksum errors. It is identical otherwise. + +## 2.2.0 (March 10, 2022) + +NOTES: + +* resource/local_file: Argument `sensitive_content` is `Deprecated`. For creating or accessing files containing sensitive data, + please use the new resource and data source `local_sensitive_file`. + Both are identical to their `local_file` counterparts, but `content` and `content_base64` attributes are marked as _sensitive_. + +FEATURES: + +* **New Data Source:** `local_sensitive_file` ([#101](https://github.com/hashicorp/terraform-provider-local/pull/101) and [#106](https://github.com/hashicorp/terraform-provider-local/pull/106)) +* **New Resource:** `local_sensitive_file` ([#106](https://github.com/hashicorp/terraform-provider-local/pull/106)) + +## 2.1.0 (February 19, 2021) + +NOTES: + +* Binary releases of this provider now include the` darwin-arm64` platform. +* This version contains no further changes. + +## 2.0.0 (October 14, 2020) + +NOTES: + +* Binary releases of this provider now include the `linux-arm64` platform. + +BREAKING CHANGES: + +* Upgrade to version 2 of the Terraform Plugin SDK, which drops support for Terraform 0.11. + This provider will continue to work as expected for users of Terraform 0.11, which will not download the new version. + ([#42](https://github.com/terraform-providers/terraform-provider-local/issues/42)) + +FEATURES: + +* resource/local_file: Added `source` attribute as alternative way to provide content + for the `local_file` resource. + ([#44](https://github.com/terraform-providers/terraform-provider-local/issues/44)) + +## 1.4.0 (September 30, 2019) + +NOTES: + +* The provider has switched to the standalone TF SDK, there should be no noticeable impact on compatibility. + ([#32](https://github.com/terraform-providers/terraform-provider-local/issues/32)) + +FEATURES: + +* resource/local_file: Added support for configurable permissions + ([#30](https://github.com/terraform-providers/terraform-provider-local/issues/30)) + +## 1.3.0 (June 26, 2019) + +FEATURES: + +* resource/local_file: Added support for base64 encoded content + ([#29](https://github.com/terraform-providers/terraform-provider-local/issues/29)) +* data-source/local_file: Added support for base64 encoded content + ([#29](https://github.com/terraform-providers/terraform-provider-local/issues/29)) + +## 1.2.2 (May 01, 2019) + +NOTES: + +* This releases includes another Terraform SDK upgrade intended to align with that being used for other providers + as we prepare for the Core `v0.12.0` release. It should have no significant changes in behavior for this provider. + +## 1.2.1 (April 11, 2019) + +NOTES: + +* This releases includes only a Terraform SDK upgrade intended to align with that being used for other providers + as we prepare for the Core `v0.12.0` release. It should have no significant changes in behavior for this provider. + +## 1.2.0 (March 20, 2019) + +FEATURES: + +* The provider is now compatible with Terraform v0.12, while retaining compatibility with prior versions. +* resource/local_file: added optional `sensitive_content` attribute, which can be used instead of `content` + in situations where the content contains sensitive information that should not be displayed in a rendered diff. + ([#9](https://github.com/terraform-providers/terraform-provider-local/issues/9)) + +## 1.1.0 (January 04, 2018) + +FEATURES: + +* data-source/local_file: Added for reading files in a way that participates in Terraform's dependency graph, + which allows reading of files that are created dynamically during `terraform apply`. + ([#6](https://github.com/terraform-providers/terraform-provider-local/issues/6)) + +## 1.0.0 (September 15, 2017) + +NOTES: + +* No changes from 0.1.0; just adjusting to + [the new version numbering scheme](https://www.hashicorp.com/blog/hashicorp-terraform-provider-versioning/). + +## 0.1.0 (June 21, 2017) + +NOTES: + +* Same functionality as that of Terraform 0.9.8. + Repacked as part of [Provider Splitout](https://www.hashicorp.com/blog/upcoming-provider-changes-in-terraform-0-10/) + + diff --git a/.changes/unreleased/.gitkeep b/.changes/unreleased/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/.changes/unreleased/NOTES-20221213-150546.yaml b/.changes/unreleased/NOTES-20221213-150546.yaml new file mode 100644 index 00000000..6e65b803 --- /dev/null +++ b/.changes/unreleased/NOTES-20221213-150546.yaml @@ -0,0 +1,5 @@ +kind: NOTES +body: 'provider: Rewritten to use the [`terraform-plugin-framework`](https://www.terraform.io/plugin/framework)' +time: 2022-12-13T15:05:46.163881-05:00 +custom: + Issue: "155" \ No newline at end of file diff --git a/.changie.yaml b/.changie.yaml new file mode 100644 index 00000000..921c1e0f --- /dev/null +++ b/.changie.yaml @@ -0,0 +1,22 @@ +changesDir: .changes +unreleasedDir: unreleased +changelogPath: CHANGELOG.md +versionExt: md +versionFormat: '## {{.Version}} ({{.Time.Format "January 02, 2006"}})' +kindFormat: '{{.Kind}}:' +changeFormat: '* {{.Body}} ([#{{.Custom.Issue}}](https://github.com/hashicorp/terraform-provider-local/issues/{{.Custom.Issue}}))' +custom: + - key: Issue + label: Issue/PR Number + type: int + minInt: 1 +kinds: + - label: BREAKING CHANGES + - label: NOTES + - label: FEATURES + - label: IMPROVEMENTS + - label: BUG FIXES +newlines: + afterKind: 1 + beforeKind: 1 + endOfVersion: 2 \ No newline at end of file diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index d3d270e9..3f77cb55 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,71 +1,135 @@ -# Contributing to `terraform-provider-local` +# Contributing -**First:** if you're unsure or afraid of _anything_, just ask or submit the issue describing the problem you're aiming to solve. +Thank you for investing your time and energy by contributing to our project: please ensure you are familiar +with the [HashiCorp Code of Conduct](https://github.com/hashicorp/.github/blob/master/CODE_OF_CONDUCT.md). -The `local` provider is a HashiCorp *standard library* provider, which means we consider it part of the core Terraform experience. Our priority is stability and correctness, and any bug fix or feature has to be considered in the context of this provider's many users and the wider Terraform ecosystem. -This is great as your contribution can have a big positive impact, but we have to assess potential negative impact too. +This provider is a HashiCorp **utility provider**, which means any bug fix and feature +has to be considered in the context of the thousands/millions of configurations in which this provider is used. +This is great as your contribution can have a big positive impact, but we have to assess potential negative impact too +(e.g. breaking existing configurations). _Stability over features_. -To provide some safety to the Terraform ecosystem, we strictly follow [semantic versioning](https://semver.org/) and any changes that could be considered as breaking will only be released as part of a major release. +To provide some safety to the wider provider ecosystem, we strictly follow +[semantic versioning](https://semver.org/) and HashiCorp's own +[versioning specification](https://www.terraform.io/plugin/sdkv2/best-practices/versioning#versioning-specification). +Any changes that could be considered as breaking will only be included as part of a major release. +In case multiple breaking changes need to happen, we will group them in the next upcoming major release. -## Table of Contents +## Asking Questions - - [I just have a question](#i-just-have-a-question) - - [I want to report a vulnerability](#i-want-to-report-a-vulnerability) - - [New Issue](#new-issue) - - [New Pull Request](#new-pull-request) +For questions, curiosity, or if still unsure what you are dealing with, +please see the HashiCorp [Terraform Providers Discuss](https://discuss.hashicorp.com/c/terraform-providers/31) +forum. -## I just have a question +## Reporting Vulnerabilities -> **Note:** We use GitHub for tracking bugs and feature requests only. +Please disclose security vulnerabilities responsibly by following the +[HashiCorp Vulnerability Reporting guidelines](https://www.hashicorp.com/security#vulnerability-reporting). -For questions, please see relevant channels at https://www.terraform.io/community.html +## Raising Issues -## I want to report a vulnerability +We welcome issues of all kinds including feature requests, bug reports or documentation suggestions. +Below are guidelines for well-formed issues of each type. -Please disclose security vulnerabilities responsibly by following the procedure -described at https://www.hashicorp.com/security#vulnerability-reporting +### Bug Reports -## New Issue +* [ ] **Test against latest release**: Make sure you test against the latest available version of Terraform and the provider. + It is possible we may have already fixed the bug you're experiencing. +* [ ] **Search for duplicates**: It's helpful to keep bug reports consolidated to one thread, so do a quick search + on existing bug reports to check if anybody else has reported the same thing. + You can scope searches by the label `bug` to help narrow things down. +* [ ] **Include steps to reproduce**: Provide steps to reproduce the issue, along with code examples and/or real code, + so we can try to reproduce it. Without this, it makes it much harder (sometimes impossible) to fix the issue. -We welcome issues of all kinds including feature requests, bug reports or documentation suggestions. Below are guidelines for well-formed issues of each type. +### Feature Requests -### Bug Reports +* [ ] **Search for possible duplicate requests**: It's helpful to keep requests consolidated to one thread, + so do a quick search on existing requests to check if anybody else has reported the same thing. + You can scope searches by the label `enhancement` to help narrow things down. +* [ ] **Include a use case description**: In addition to describing the behavior of the feature you'd like to see added, + it's helpful to also make a case for why the feature would be important and how it would benefit + the provider and, potentially, the wider Terraform ecosystem. - - **Test against latest release**: Make sure you test against the latest avaiable version of both Terraform and the provider. -It is possible we already fixed the bug you're experiencing. +## New Pull Request - - **Search for duplicates**: It's helpful to keep bug reports consolidated to one thread, so do a quick search on existing bug reports to check if anybody else has reported the same thing. You can scope searches by the label `bug` to help narrow things down. +Thank you for contributing! - - **Include steps to reproduce**: Provide steps to reproduce the issue, along with code examples (both HCL and Go, where applicable) and/or real code, so we can try to reproduce it. Without this, it makes it much harder (sometimes impossible) to fix the issue. +We are happy to review pull requests without associated issues, +but we **highly recommend** starting by describing and discussing +your problem or feature and attaching use cases to an issue first +before raising a pull request. -### Feature Requests +* [ ] **Early validation of idea and implementation plan**: provider development is complicated enough that there + are often several ways to implement something, each of which has different implications and tradeoffs. + Working through a plan of attack with the team before you dive into implementation will help ensure that you're + working in the right direction. +* [ ] **Tests**: It may go without saying, but every new patch should be covered by tests wherever possible. + For bug-fixes, tests to prove the fix is valid. For features, tests to exercise the new code paths. +* [ ] **Go Modules**: We use [Go Modules](https://github.com/golang/go/wiki/Modules) to manage and version our dependencies. + Please make sure that you reflect dependency changes in your pull requests appropriately + (e.g. `go get`, `go mod tidy` or other commands). + Refer to the [dependency updates](#dependency-updates) section for more information about how + this project maintains existing dependencies. +* [ ] **Changelog**: Refer to the [changelog](#changelog) section for more information about how to create changelog entries. - - **Search for possible duplicate requests**: It's helpful to keep requests consolidated to one thread, so do a quick search on existing requests to check if anybody else has reported the same thing. You can scope searches by the label `enhancement` to help narrow things down. +### Dependency Updates - - **Include a use case description**: In addition to describing the behavior of the feature you'd like to see added, it's helpful to also lay out the reason why the feature would be important and how it would benefit the wider Terraform ecosystem. Use case in context of 1 provider is good, wider context of more providers is better. +Dependency management is performed by [Dependabot](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates). +Where possible, dependency updates should occur through that system to ensure all Go module files are appropriately +updated and to prevent duplicated effort of concurrent update submissions. +Once available, updates are expected to be verified and merged to prevent latent technical debt. -## New Pull Request +### Changelog -Thank you for contributing! +HashiCorp’s open-source projects have always maintained user-friendly, readable `CHANGELOG`s that allow +practitioners and developers to tell at a glance whether a release should have any effect on them, +and to gauge the risk of an upgrade. + +We follow Terraform Plugin +[changelog specifications](https://www.terraform.io/plugin/sdkv2/best-practices/versioning#changelog-specification). + +#### Changie Automation Tool +This provider uses the [Changie](https://changie.dev/) automation tool for changelog automation. +To add a new entry to the `CHANGELOG` install Changie using the following [instructions](https://changie.dev/guide/installation/) +and run +```bash +changie new +``` +then choose a `kind` of change corresponding to the Terraform Plugin [changelog categories](https://developer.hashicorp.com/terraform/plugin/sdkv2/best-practices/versioning#categorization) +and then fill out the body following the entry format. Changie will then prompt for a Github issue or pull request number. +Repeat this process for any additional changes. The `.yaml` files created in the `.changes/unreleased` folder +should be pushed the repository along with any code changes. + +#### Entry format + +Entries that are specific to _resources_ or _data sources_, they should look like: + +```markdown +* resource/RESOURCE_NAME: ENTRY DESCRIPTION -We are happy to review pull requests without associated issues, but we highly recommend starting by describing and discussing your problem or feature and attaching use cases to an issue first before raising a pull request. +* data-source/DATA-SOURCE_NAME: ENTRY DESCRIPTION +``` -- **Early validation of idea and implementation plan**: Terraform's SDK is complicated enough that there are often several ways to implement something, each of which has different implications and tradeoffs. Working through a plan of attack with the team before you dive into implementation will help ensure that you're working in the right direction. +#### Pull Request Types to `CHANGELOG` -- **Acceptance Tests**: It may go without saying, but every new patch should be covered by tests wherever possible. +The `CHANGELOG` is intended to show developer-impacting changes to the codebase for a particular version. +If every change or commit to the code resulted in an entry, the `CHANGELOG` would become less useful for developers. +The lists below are general guidelines to decide whether a change should have an entry. -- **Go Modules**: We use [Go Modules](https://github.com/golang/go/wiki/Modules) to manage and version all our dependencies. Please make sure that you reflect dependency changes in your pull requests appropriately (e.g. `go get`, `go mod tidy` or other commands). Where possible it is better to raise a separate pull request with just dependency changes as it's easier to review such PR(s). +##### Changes that should not have a `CHANGELOG` entry -### Cosmetic changes, code formatting, and typos +* Documentation updates +* Testing updates +* Code refactoring -In general we do not accept PRs containing only the following changes: +##### Changes that may have a `CHANGELOG` entry - - Correcting spelling or typos - - Code formatting, including whitespace - - Other cosmetic changes that do not affect functionality - -While we appreciate the effort that goes into preparing PRs, there is always a tradeoff between benefit and cost. The costs involved in accepting such contributions include the time taken for thorough review, the noise created in the git history, and the increased number of GitHub notifications that maintainers must attend to. +* Dependency updates: If the update contains relevant bug fixes or enhancements that affect developers, + those should be called out. -#### Exceptions +##### Changes that should have a `CHANGELOG` entry -We belive that one should "leave the campsite cleaner than you found it", so you are welcome to clean up cosmetic issues in the neighbourhood when submitting a patch that makes functional changes or fixes. +* Major features +* Bug fixes +* Enhancements +* Deprecations +* Breaking changes and removals \ No newline at end of file diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md index 1d41099a..a1dbdbb4 100644 --- a/.github/SUPPORT.md +++ b/.github/SUPPORT.md @@ -1,5 +1,6 @@ # Support -Terraform is a mature project with a growing community. There are active, dedicated people willing to help you through various mediums. - -Take a look at those mediums listed at https://www.terraform.io/community.html +* Project [README](../README.md) +* Official [Documentation](https://registry.terraform.io/providers/hashicorp/local/latest/docs) +* Providers [Discuss forums](https://discuss.hashicorp.com/c/terraform-providers/31) +* Terraform [Community](https://www.terraform.io/community.html) page diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 276302bd..159152ae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,14 +1,28 @@ name: Release on: - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+*' + workflow_dispatch: + inputs: + versionNumber: + description: 'Release version number' + type: string + required: true + +env: + CI_COMMIT_AUTHOR: hc-github-team-tf-provider-devex + CI_COMMIT_EMAIL: github-team-tf-provider-devex@hashicorp.com permissions: contents: write jobs: + changelog-version: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.changelog-version.outputs.version }} + steps: + - id: changelog-version + run: echo "version=$(echo "${{ inputs.versionNumber }}" | cut -c 2-)" >> "$GITHUB_OUTPUT" go-version: runs-on: ubuntu-latest outputs: @@ -16,23 +30,71 @@ jobs: steps: - uses: actions/checkout@v3 - id: go-version - run: echo "::set-output name=version::$(cat ./.go-version)" + run: echo "version=$(cat ./.go-version)" >> "$GITHUB_OUTPUT" + changelog: + needs: changelog-version + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Batch changes + uses: miniscruff/changie-action@v0 + with: + version: latest + args: batch ${{ needs.changelog-version.outputs.version }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Merge changes + uses: miniscruff/changie-action@v0 + with: + version: latest + args: merge + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Git push changelog + run: | + git config --global user.name "${{ env.CI_COMMIT_AUTHOR }}" + git config --global user.email "${{ env.CI_COMMIT_EMAIL }}" + git add . + git commit -a -m "Update changelog" + git push + release-tag: + needs: changelog + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Git push release tag + run: | + git config --global user.name "${{ env.CI_COMMIT_AUTHOR }}" + git config --global user.email "${{ env.CI_COMMIT_EMAIL }}" + git pull + git tag "${{ inputs.versionNumber }}" + git push origin "${{ inputs.versionNumber }}" release-notes: + needs: [ changelog-version, changelog, release-tag ] runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: + ref: ${{ inputs.versionNumber }} fetch-depth: 0 - name: Generate Release Notes - run: sed -n -e "1{/# /d;}" -e "2{/^$/d;}" -e "/# $(git describe --abbrev=0 --exclude="$(git describe --abbrev=0 --match='v*.*.*' --tags)" --match='v*.*.*' --tags | tr -d v)/q;p" CHANGELOG.md > release-notes.txt + run: | + cd .changes + sed -e "1{/# /d;}" -e "2{/^$/d;}" ${{ needs.changelog-version.outputs.version }}.md > release-notes.txt - uses: actions/upload-artifact@v3 with: name: release-notes - path: release-notes.txt + path: ./.changes/release-notes.txt retention-days: 1 terraform-provider-release: name: 'Terraform Provider Release' - needs: [go-version, release-notes] + needs: [ go-version, release-notes ] uses: hashicorp/ghaction-terraform-provider-release/.github/workflows/hashicorp.yml@v2 secrets: hc-releases-key-prod: '${{ secrets.HC_RELEASES_KEY_PROD }}' @@ -47,5 +109,5 @@ jobs: with: release-notes: true setup-go-version: '${{ needs.go-version.outputs.version }}' - # Product Version (e.g. v1.2.3 or github.ref_name) - product-version: '${{ github.ref_name }}' + # Product Version (e.g. v1.2.3) + product-version: '${{ inputs.versionNumber }}' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 42e775e7..361c41de 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,5 @@ -name: Tests +name: Test + on: pull_request: branches: [ main ] @@ -12,66 +13,76 @@ on: - 'README.md' - 'CHANGELOG.md' - 'website/*' + jobs: + build: name: Build runs-on: ubuntu-latest timeout-minutes: 5 - steps: - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: '1.18' - id: go + strategy: + matrix: + go-version: [ '1.18', '1.19' ] + + steps: - - name: Check out code into the Go module directory - uses: actions/checkout@v3 + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go-version }} - - name: Go fmt - run: | - make fmt + - name: Run linters + uses: golangci/golangci-lint-action@v3 + with: + version: latest - - name: Go vet - run: | - make vet + - name: Generate + run: make generate - - name: Build - run: | - go build -v . + - name: Confirm no diff + run: | + git diff --compact-summary --exit-code || \ + (echo "*** Unexpected differences after code generation. Run 'make generate' and commit."; exit 1) + - name: Build + run: make build -# run acceptance tests in a matrix with Terraform core versions test: - name: Matrix Test + name: 'Acc. Tests (OS: ${{ matrix.os }} / TF: ${{ matrix.terraform }})' needs: build runs-on: ${{ matrix.os }} timeout-minutes: 15 + strategy: fail-fast: false matrix: - os: [macos-latest, windows-latest, ubuntu-latest] + os: + - macos-latest + - windows-latest + - ubuntu-latest terraform: - - '0.12.31' - - '0.13.7' - - '0.14.11' - - '0.15.5' - - '1.0.1' + - '0.12.*' + - '0.13.*' + - '0.14.*' + - '0.15.*' + - '1.0.*' + - '1.1.*' + - '1.2.*' + - '1.3.*' + steps: - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: '1.18' - id: go - - - name: Check out code into the Go module directory - uses: actions/checkout@v3 - - - name: TF acceptance tests - timeout-minutes: 10 - env: - TF_ACC: "1" - TF_ACC_TERRAFORM_VERSION: ${{ matrix.terraform }} - run: | - go test -v -cover ./internal/provider/ + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version-file: 'go.mod' + + - name: Setup Terraform ${{ matrix.terraform }} + uses: hashicorp/setup-terraform@v2 + with: + terraform_version: ${{ matrix.terraform }} + terraform_wrapper: false + + - name: Run acceptance test + run: make testacc \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..9e785d9e --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,21 @@ +linters: + # Default linters enabled + # See: https://golangci-lint.run/usage/linters/#enabled-by-default-linters + + # Additional linters enabled + enable: + - durationcheck + - exportloopref + - godot + - gofmt + - makezero + - misspell + - nilerr + - predeclared + - tenv + - unconvert + - unparam + +issues: + max-issues-per-linter: 0 + max-same-issues: 0 diff --git a/CHANGELOG.md b/CHANGELOG.md index ceabab27..85600bc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,12 @@ NOTES: -* resource/local_file: Update docs to prevent confusion that exactly one of the arguments `content`, +* resource/local_file: Update docs to prevent confusion that exactly one of the arguments `content`, `sensitive_content`, `content_base64`, and `source` needs to be specified ([#123](https://github.com/hashicorp/terraform-provider-local/pull/123)). -* resource/local_sensitive_file: Update docs to prevent confusion that exactly one of the arguments `content`, +* resource/local_sensitive_file: Update docs to prevent confusion that exactly one of the arguments `content`, `content_base64`, and `source` needs to be specified ([#123](https://github.com/hashicorp/terraform-provider-local/pull/123)). - + * No functional changes from 2.2.2. ## 2.2.2 (March 11, 2022) @@ -126,3 +126,5 @@ NOTES: * Same functionality as that of Terraform 0.9.8. Repacked as part of [Provider Splitout](https://www.hashicorp.com/blog/upcoming-provider-changes-in-terraform-0-10/) + + diff --git a/GNUmakefile b/GNUmakefile index 93b43435..b1699407 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -1,45 +1,25 @@ -TEST?=$$(go list ./...) -GOFMT_FILES?=$$(find . -name '*.go') -PKG_NAME=local - default: build -build: fmtcheck - go install +build: + go build -v ./... -test: fmtcheck - go test -i $(TEST) || exit 1 - echo $(TEST) | \ - xargs -t -n4 go test $(TESTARGS) -timeout=30s -parallel=4 +install: build + go install -v ./... -testacc: fmtcheck - TF_ACC=1 go test $(TEST) -v $(TESTARGS) -timeout 120m +# See https://golangci-lint.run/ +lint: + golangci-lint run -vet: - @echo "go vet ." - @go vet $$(go list ./...) ; if [ $$? -eq 1 ]; then \ - echo ""; \ - echo "Vet found suspicious constructs. Please check the reported constructs"; \ - echo "and fix them if necessary before submitting the code for review."; \ - exit 1; \ - fi +generate: + go generate ./... fmt: - gofmt -w $(GOFMT_FILES) - -fmtcheck: - @sh -c "'$(CURDIR)/scripts/gofmtcheck.sh'" - -errcheck: - @sh -c "'$(CURDIR)/scripts/errcheck.sh'" + gofmt -s -w -e . +test: + go test -v -cover -timeout=120s -parallel=4 ./... -test-compile: - @if [ "$(TEST)" = "./..." ]; then \ - echo "ERROR: Set TEST to a specific package. For example,"; \ - echo " make test-compile TEST=./$(PKG_NAME)"; \ - exit 1; \ - fi - go test -c $(TEST) $(TESTARGS) +testacc: + TF_ACC=1 go test -v -cover -timeout 120m ./... -.PHONY: build test testacc vet fmt fmtcheck errcheck test-compile +.PHONY: build install lint generate fmt test testacc \ No newline at end of file diff --git a/README.md b/README.md index 56767d17..b9df5f1b 100644 --- a/README.md +++ b/README.md @@ -1,68 +1,122 @@ -Terraform Provider -================== +# Terraform Provider: Local -- Website: https://www.terraform.io -- [![Gitter chat](https://badges.gitter.im/hashicorp-terraform/Lobby.png)](https://gitter.im/hashicorp-terraform/Lobby) -- Mailing list: [Google Groups](http://groups.google.com/group/terraform-tool) +The Local provider is used to manage local resources, such as files. - -Maintainers ------------ +**Note** Terraform primarily deals with remote resources which are able +to outlive a single Terraform run, and so local resources can sometimes violate +its assumptions. The resources here are best used with care, since depending +on local state can make it hard to apply the same Terraform configuration on +many different local systems where the local resources may not be universally +available. See specific notes in each resource for more information. -This provider plugin is maintained by the Terraform team at [HashiCorp](https://www.hashicorp.com/). -Requirements ------------- +## Documentation, questions and discussions -- [Terraform](https://www.terraform.io/downloads.html) 0.12.x -- [Go](https://golang.org/doc/install) 1.18 (to build the provider plugin) +Official documentation on how to use this provider can be found on the +[Terraform Registry](https://registry.terraform.io/providers/hashicorp/local/latest/docs). +In case of specific questions or discussions, please use the +HashiCorp [Terraform Providers Discuss forums](https://discuss.hashicorp.com/c/terraform-providers/31), +in accordance with HashiCorp [Community Guidelines](https://www.hashicorp.com/community-guidelines). -Building The Provider ---------------------- +We also provide: -Clone repository to: `$GOPATH/src/github.com/terraform-providers/terraform-provider-local` +* [Support](.github/SUPPORT.md) page for help when using the provider +* [Contributing](.github/CONTRIBUTING.md) guidelines in case you want to help this project -```sh -$ mkdir -p $GOPATH/src/github.com/terraform-providers; cd $GOPATH/src/github.com/terraform-providers -$ git clone git@github.com:terraform-providers/terraform-provider-local -``` +The remainder of this document will focus on the development aspects of the provider. -Enter the provider directory and build the provider +## Compatibility -```sh -$ cd $GOPATH/src/github.com/terraform-providers/terraform-provider-local -$ make build -``` +Compatibility table between this provider, the [Terraform Plugin Protocol](https://www.terraform.io/plugin/how-terraform-works#terraform-plugin-protocol) +version it implements, and Terraform: -Using the provider ----------------------- -## Fill in for each provider +| TLS Provider | Terraform Plugin Protocol | Terraform | +|:------------:|:-------------------------:|:---------:| +| `>= 2.x` | `5` | `>= 0.12` | +| `>= 1.1.x` | `4` and `5` | `<= 0.12` | +| `>= 0.x` | `4` | `<= 0.11` | -Developing the Provider ---------------------------- +Details can be found querying the [Registry API](https://www.terraform.io/internals/provider-registry-protocol#list-available-versions) +that return all the details about which version are currently available for a particular provider. +[Here](https://registry.terraform.io/v1/providers/hashicorp/local/versions) are the details for Local (JSON response). -If you wish to work on the provider, you'll first need [Go](http://www.golang.org) installed on your machine (version 1.18+ is *required*). You'll also need to correctly setup a [GOPATH](http://golang.org/doc/code.html#GOPATH), as well as adding `$GOPATH/bin` to your `$PATH`. +## Requirements -To compile the provider, run `make build`. This will build the provider and put the provider binary in the `$GOPATH/bin` directory. +* [Terraform](https://www.terraform.io/downloads) +* [Go](https://go.dev/doc/install) (1.18) +* [GNU Make](https://www.gnu.org/software/make/) +* [golangci-lint](https://golangci-lint.run/usage/install/#local-installation) (optional) -```sh -$ make bin -... -$ $GOPATH/bin/terraform-provider-local -... -``` +## Development -In order to test the provider, you can simply run `make test`. +### Building -```sh -$ make test -``` +1. `git clone` this repository and `cd` into its directory +2. `make` will trigger the Golang build + +The provided `GNUmakefile` defines additional commands generally useful during development, +like for running tests, generating documentation, code formatting and linting. +Taking a look at it's content is recommended. + +### Testing + +In order to test the provider, you can run + +* `make test` to run provider tests +* `make testacc` to run provider acceptance tests + +It's important to note that acceptance tests (`testacc`) will actually spawn +`terraform` and the provider. Read more about they work on the +[official page](https://www.terraform.io/plugin/sdkv2/testing/acceptance-tests). + +### Generating documentation + +This provider uses [terraform-plugin-docs](https://github.com/hashicorp/terraform-plugin-docs/) +to generate documentation and store it in the `docs/` directory. +Once a release is cut, the Terraform Registry will download the documentation from `docs/` +and associate it with the release version. Read more about how this works on the +[official page](https://www.terraform.io/registry/providers/docs). + +Use `make generate` to ensure the documentation is regenerated with any changes. -In order to run the full suite of Acceptance tests, run `make testacc`. +### Using a development build -*Note:* Acceptance tests create real resources, and often cost money to run. +If [running tests and acceptance tests](#testing) isn't enough, it's possible to set up a local terraform configuration +to use a development builds of the provider. This can be achieved by leveraging the Terraform CLI +[configuration file development overrides](https://www.terraform.io/cli/config/config-file#development-overrides-for-provider-developers). -```sh -$ make testacc +First, use `make install` to place a fresh development build of the provider in your +[`${GOBIN}`](https://pkg.go.dev/cmd/go#hdr-Compile_and_install_packages_and_dependencies) +(defaults to `${GOPATH}/bin` or `${HOME}/go/bin` if `${GOPATH}` is not set). Repeat +this every time you make changes to the provider locally. + +Then, setup your environment following [these instructions](https://www.terraform.io/plugin/debugging#terraform-cli-development-overrides) +to make your local terraform use your local build. + +### Testing GitHub Actions + +This project uses [GitHub Actions](https://docs.github.com/en/actions/automating-builds-and-tests) to realize its CI. + +Sometimes it might be helpful to locally reproduce the behaviour of those actions, +and for this we use [act](https://github.com/nektos/act). Once installed, you can _simulate_ the actions executed +when opening a PR with: + +```shell +# List of workflows for the 'pull_request' action +$ act -l pull_request + +# Execute the workflows associated with the `pull_request' action +$ act pull_request ``` + +## Releasing + +The release process is automated via GitHub Actions, and it's defined in the Workflow +[release.yml](./.github/workflows/release.yml). + +Each release is cut by pushing a [semantically versioned](https://semver.org/) tag to the default branch. + +## License + +[Mozilla Public License v2.0](./LICENSE) diff --git a/docs/data-sources/file.md b/docs/data-sources/file.md new file mode 100644 index 00000000..bf43ca5a --- /dev/null +++ b/docs/data-sources/file.md @@ -0,0 +1,38 @@ +--- +page_title: "local_file Data Source - terraform-provider-local" +subcategory: "" +description: |- + Reads a file from the local filesystem. +--- + +# local_file (Data Source) + +Reads a file from the local filesystem. + +## Example Usage + +```terraform +data "local_file" "foo" { + filename = "${path.module}/foo.bar" +} + +resource "aws_s3_object" "shared_zip" { + bucket = "my-bucket" + key = "my-key" + content = data.local_file.foo.content +} +``` + + +## Schema + +### Required + +- `filename` (String) Path to the file that will be read. The data source will return an error if the file does not exist. + +### Read-Only + +- `content` (String) Raw content of the file that was read, as UTF-8 encoded string. Files that do not contain UTF-8 text will have invalid UTF-8 sequences in `content` + replaced with the Unicode replacement character. +- `content_base64` (String) Base64 encoded version of the file content (use this when dealing with binary data). +- `id` (String) The hexadecimal encoding of the checksum of the file content. \ No newline at end of file diff --git a/docs/data-sources/sensitive_file.md b/docs/data-sources/sensitive_file.md new file mode 100644 index 00000000..a727606a --- /dev/null +++ b/docs/data-sources/sensitive_file.md @@ -0,0 +1,41 @@ +--- +page_title: "local_sensitive_file Data Source - terraform-provider-local" +subcategory: "" +description: |- + Reads a file that contains sensitive data, from the local filesystem. +--- + +# local_sensitive_file (Data Source) + +Reads a file that contains sensitive data, from the local filesystem. + +The attributes exposed by this data source are marked as +[sensitive](https://learn.hashicorp.com/tutorials/terraform/sensitive-variables). + +## Example Usage + +```terraform +data "local_sensitive_file" "foo" { + filename = "${path.module}/foo.bar" +} + +resource "aws_s3_object" "shared_zip" { + bucket = "my-bucket" + key = "my-key" + content = data.local_sensitive_file.foo.content +} +``` + + +## Schema + +### Required + +- `filename` (String) Path to the file that will be read. The data source will return an error if the file does not exist. + +### Read-Only + +- `content` (String, Sensitive) Raw content of the file that was read, as UTF-8 encoded string. Files that do not contain UTF-8 text will have invalid UTF-8 sequences in `content` + replaced with the Unicode replacement character. +- `content_base64` (String, Sensitive) Base64 encoded version of the file content (use this when dealing with binary data). +- `id` (String) The hexadecimal encoding of the checksum of the file content \ No newline at end of file diff --git a/website/docs/index.html.markdown b/docs/index.md similarity index 97% rename from website/docs/index.html.markdown rename to docs/index.md index 71069eb3..8cde602a 100644 --- a/website/docs/index.html.markdown +++ b/docs/index.md @@ -1,5 +1,4 @@ --- -layout: "local" page_title: "Provider: Local" description: |- The Local provider is used to manage local resources, such as files. @@ -16,4 +15,4 @@ to outlive a single Terraform run, and so local resources can sometimes violate its assumptions. The resources here are best used with care, since depending on local state can make it hard to apply the same Terraform configuration on many different local systems where the local resources may not be universally -available. See specific notes in each resource for more information. +available. See specific notes in each resource for more information. \ No newline at end of file diff --git a/docs/resources/file.md b/docs/resources/file.md new file mode 100644 index 00000000..d9094f46 --- /dev/null +++ b/docs/resources/file.md @@ -0,0 +1,70 @@ +--- +page_title: "local_file Resource - terraform-provider-local" +subcategory: "" +description: |- + Generates a local file with the given content. +--- + +# local_file (Resource) + +Generates a local file with the given content. + +~> **Note about resource behaviour** +When working with local files, Terraform will detect the resource +as having been deleted each time a configuration is applied on a new machine +where the file is not present and will generate a diff to re-create it. This +may cause "noise" in diffs in environments where configurations are routinely +applied by many different users or within automation systems. + +~> **Note about file content** +File content must be specified with _exactly_ one of the arguments `content`, +`sensitive_content` (Deprecated), `content_base64`, or `source`. + +-> If the file content is sensitive, use the +[`local_sensitive_file`](./sensitive_file.html) resource instead. + +## Example Usage + +```terraform +resource "local_file" "foo" { + content = "foo!" + filename = "${path.module}/foo.bar" +} +``` + + +## Schema + +### Required + +- `filename` (String) The path to the file that will be created. + Missing parent directories will be created. + If the file already exists, it will be overridden with the given content. + +### Optional + +- `content` (String) Content to store in the file, expected to be a UTF-8 encoded string. + Conflicts with `sensitive_content`, `content_base64` and `source`. + Exactly one of these four arguments must be specified. +- `content_base64` (String) Content to store in the file, expected to be binary encoded as base64 string. + Conflicts with `content`, `sensitive_content` and `source`. + Exactly one of these four arguments must be specified. +- `directory_permission` (String) Permissions to set for directories created (before umask), expressed as string in + [numeric notation](https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation). + Default value is `"0777"`. +- `file_permission` (String) Permissions to set for the output file (before umask), expressed as string in + [numeric notation](https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation). + Default value is `"0777"`. +- `sensitive_content` (String, Sensitive, Deprecated) Sensitive content to store in the file, expected to be an UTF-8 encoded string. + Will not be displayed in diffs. + Conflicts with `content`, `content_base64` and `source`. + Exactly one of these four arguments must be specified. + If in need to use _sensitive_ content, please use the [`local_sensitive_file`](./sensitive_file.html) + resource instead. +- `source` (String) Path to file to use as source for the one we are creating. + Conflicts with `content`, `sensitive_content` and `content_base64`. + Exactly one of these four arguments must be specified. + +### Read-Only + +- `id` (String) The hexadecimal encoding of the checksum of the file content \ No newline at end of file diff --git a/docs/resources/sensitive_file.md b/docs/resources/sensitive_file.md new file mode 100644 index 00000000..1f19a57f --- /dev/null +++ b/docs/resources/sensitive_file.md @@ -0,0 +1,64 @@ +--- +page_title: "local_sensitive_file Resource - terraform-provider-local" +subcategory: "" +description: |- + Generates a local file with the given sensitive content. +--- + +# local_sensitive_file (Resource) + +Generates a local file with the given sensitive content. + +The arguments accepted by this resource are marked as +[sensitive](https://learn.hashicorp.com/tutorials/terraform/sensitive-variables). + +~> **Note about resource behaviour** +When working with local files, Terraform will detect the resource +as having been deleted each time a configuration is applied on a new machine +where the file is not present and will generate a diff to re-create it. This +may cause "noise" in diffs in environments where configurations are routinely +applied by many different users or within automation systems. + +~> **Note about file content** +File content must be specified with _exactly_ one of the arguments `content`, +`content_base64`, or `source`. + +## Example Usage + +```terraform +resource "local_sensitive_file" "foo" { + content = "foo!" + filename = "${path.module}/foo.bar" +} +``` + + +## Schema + +### Required + +- `filename` (String) The path to the file that will be created. + Missing parent directories will be created. + If the file already exists, it will be overridden with the given content. + +### Optional + +- `content` (String, Sensitive) Sensitive Content to store in the file, expected to be a UTF-8 encoded string. + Conflicts with `content_base64` and `source`. + Exactly one of these three arguments must be specified. +- `content_base64` (String, Sensitive) Sensitive Content to store in the file, expected to be binary encoded as base64 string. + Conflicts with `content` and `source`. + Exactly one of these three arguments must be specified. +- `directory_permission` (String) Permissions to set for directories created (before umask), expressed as string in + [numeric notation](https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation). + Default value is `"0700"`. +- `file_permission` (String) Permissions to set for the output file (before umask), expressed as string in + [numeric notation](https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation). + Default value is `"0700"`. +- `source` (String) Path to file to use as source for the one we are creating. + Conflicts with `content` and `content_base64`. + Exactly one of these three arguments must be specified. + +### Read-Only + +- `id` (String) The hexadecimal encoding of the checksum of the file content \ No newline at end of file diff --git a/examples/data-sources/data-source-file.tf b/examples/data-sources/data-source-file.tf new file mode 100644 index 00000000..2d21546d --- /dev/null +++ b/examples/data-sources/data-source-file.tf @@ -0,0 +1,9 @@ +data "local_file" "foo" { + filename = "${path.module}/foo.bar" +} + +resource "aws_s3_object" "shared_zip" { + bucket = "my-bucket" + key = "my-key" + content = data.local_file.foo.content +} \ No newline at end of file diff --git a/examples/data-sources/data-source-sensitive-file.tf b/examples/data-sources/data-source-sensitive-file.tf new file mode 100644 index 00000000..53ffa42d --- /dev/null +++ b/examples/data-sources/data-source-sensitive-file.tf @@ -0,0 +1,9 @@ +data "local_sensitive_file" "foo" { + filename = "${path.module}/foo.bar" +} + +resource "aws_s3_object" "shared_zip" { + bucket = "my-bucket" + key = "my-key" + content = data.local_sensitive_file.foo.content +} \ No newline at end of file diff --git a/examples/resources/resource-file.tf b/examples/resources/resource-file.tf new file mode 100644 index 00000000..d017cde2 --- /dev/null +++ b/examples/resources/resource-file.tf @@ -0,0 +1,4 @@ +resource "local_file" "foo" { + content = "foo!" + filename = "${path.module}/foo.bar" +} \ No newline at end of file diff --git a/examples/resources/resource-sensitive-file.tf b/examples/resources/resource-sensitive-file.tf new file mode 100644 index 00000000..29f9f389 --- /dev/null +++ b/examples/resources/resource-sensitive-file.tf @@ -0,0 +1,4 @@ +resource "local_sensitive_file" "foo" { + content = "foo!" + filename = "${path.module}/foo.bar" +} \ No newline at end of file diff --git a/go.mod b/go.mod index dddd0a38..a38fd3dd 100644 --- a/go.mod +++ b/go.mod @@ -2,22 +2,34 @@ module github.com/terraform-providers/terraform-provider-local go 1.18 -require github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.0 +require ( + github.com/hashicorp/terraform-plugin-docs v0.13.0 + github.com/hashicorp/terraform-plugin-framework v1.0.0 + github.com/hashicorp/terraform-plugin-framework-validators v0.8.0 + github.com/hashicorp/terraform-plugin-go v0.14.2 + github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.0 +) require ( + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.1.1 // indirect + github.com/Masterminds/sprig/v3 v3.2.2 // indirect github.com/agext/levenshtein v1.2.2 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect + github.com/armon/go-radix v1.0.0 // indirect + github.com/bgentry/speakeasy v0.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.13.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.9 // indirect - github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect github.com/hashicorp/go-hclog v1.2.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.4.4 // indirect + github.com/hashicorp/go-plugin v1.4.6 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/hc-install v0.4.0 // indirect @@ -25,29 +37,35 @@ require ( github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.17.3 // indirect github.com/hashicorp/terraform-json v0.14.0 // indirect - github.com/hashicorp/terraform-plugin-go v0.14.0 // indirect github.com/hashicorp/terraform-plugin-log v0.7.0 // indirect - github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c // indirect + github.com/hashicorp/terraform-registry-address v0.1.0 // indirect github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect + github.com/huandu/xstrings v1.3.2 // indirect + github.com/imdario/mergo v0.3.13 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mitchellh/cli v1.1.4 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-wordwrap v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/oklog/run v1.0.0 // indirect + github.com/posener/complete v1.2.3 // indirect + github.com/russross/blackfriday v1.6.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/spf13/cast v1.5.0 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect - github.com/vmihailenco/tagparser v0.1.1 // indirect + github.com/vmihailenco/tagparser v0.1.2 // indirect github.com/zclconf/go-cty v1.11.0 // indirect - golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167 // indirect - golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect - golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect - golang.org/x/text v0.3.7 // indirect - google.golang.org/appengine v1.6.6 // indirect + golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect + golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect + golang.org/x/text v0.4.0 // indirect + google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20200711021454-869866162049 // indirect - google.golang.org/grpc v1.48.0 // indirect + google.golang.org/grpc v1.51.0 // indirect google.golang.org/protobuf v1.28.1 // indirect ) diff --git a/go.sum b/go.sum index 16223341..ad9665ff 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,14 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig/v3 v3.2.0/go.mod h1:tWhwTbUTndesPNeF0C900vKoq283u6zp4APT9vaF3SI= +github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= +github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= @@ -11,38 +19,32 @@ github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFUye+ZcSR6opIgz9Co7WcDx6ZcY+RjfFHoA0I= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= @@ -59,7 +61,6 @@ github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -67,8 +68,6 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= @@ -76,16 +75,17 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= @@ -96,10 +96,11 @@ github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUK github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= github.com/hashicorp/go-hclog v1.2.1 h1:YQsLlGDJgwhXFpucSPyVbCBviQtjlHv3jLTlp8YmtEw= github.com/hashicorp/go-hclog v1.2.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ= -github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= +github.com/hashicorp/go-plugin v1.4.6 h1:MDV3UrKQBM3du3G7MApDGvOsMYy3JQJ4exhSoKBAeVA= +github.com/hashicorp/go-plugin v1.4.6/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -117,20 +118,31 @@ github.com/hashicorp/terraform-exec v0.17.3 h1:MX14Kvnka/oWGmIkyuyvL6POx25ZmKrjl github.com/hashicorp/terraform-exec v0.17.3/go.mod h1:+NELG0EqQekJzhvikkeQsOAZpsw0cv/03rbeQJqscAI= github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e17dKDpqV7s= github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM= -github.com/hashicorp/terraform-plugin-go v0.14.0 h1:ttnSlS8bz3ZPYbMb84DpcPhY4F5DsQtcAS7cHo8uvP4= -github.com/hashicorp/terraform-plugin-go v0.14.0/go.mod h1:2nNCBeRLaenyQEi78xrGrs9hMbulveqG/zDMQSvVJTE= +github.com/hashicorp/terraform-plugin-docs v0.13.0 h1:6e+VIWsVGb6jYJewfzq2ok2smPzZrt1Wlm9koLeKazY= +github.com/hashicorp/terraform-plugin-docs v0.13.0/go.mod h1:W0oCmHAjIlTHBbvtppWHe8fLfZ2BznQbuv8+UD8OucQ= +github.com/hashicorp/terraform-plugin-framework v1.0.0 h1:0Mls4TrMTrDysBUby/UmlbcTOMM+n5JBDyB5k+XkGWg= +github.com/hashicorp/terraform-plugin-framework v1.0.0/go.mod h1:FV97t2BZOARkL7NNlsc/N25c84MyeSSz72uPp7Vq1lg= +github.com/hashicorp/terraform-plugin-framework-validators v0.8.0 h1:hKCuQMjD7W7reAoWn6GLkNwrDNjY9RCBWQZOJxe5LlQ= +github.com/hashicorp/terraform-plugin-framework-validators v0.8.0/go.mod h1:qkrZ542jRiCwwl3ZN/3eTKhGJ4HIBkSxGXnjJoAWtxo= +github.com/hashicorp/terraform-plugin-go v0.14.2 h1:rhsVEOGCnY04msNymSvbUsXfRLKh9znXZmHlf5e8mhE= +github.com/hashicorp/terraform-plugin-go v0.14.2/go.mod h1:Q12UjumPNGiFsZffxOsA40Tlz1WVXt2Evh865Zj0+UA= github.com/hashicorp/terraform-plugin-log v0.7.0 h1:SDxJUyT8TwN4l5b5/VkiTIaQgY6R+Y2BQ0sRZftGKQs= github.com/hashicorp/terraform-plugin-log v0.7.0/go.mod h1:p4R1jWBXRTvL4odmEkFfDdhUjHf9zcs/BCoNHAc7IK4= github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.0 h1:FtCLTiTcykdsURXPt/ku7fYXm3y19nbzbZcUxHx9RbI= github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.0/go.mod h1:80wf5oad1tW+oLnbXS4UTYmDCrl7BuN1Q+IA91X1a4Y= -github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c h1:D8aRO6+mTqHfLsK/BC3j5OAoogv1WLRWzY1AaTo3rBg= -github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c/go.mod h1:Wn3Na71knbXc1G8Lh+yu/dQWWJeFQEpDeJMtWMtlmNI= +github.com/hashicorp/terraform-registry-address v0.1.0 h1:W6JkV9wbum+m516rCl5/NjKxCyTVaaUBbzYcMzBDO3U= +github.com/hashicorp/terraform-registry-address v0.1.0/go.mod h1:EnyO2jYO6j29DTHbJcm00E5nQTFeTtyZH3H5ycydQ5A= github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0= github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= @@ -139,8 +151,8 @@ github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgy github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -148,12 +160,17 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mitchellh/cli v1.1.4 h1:qj8czE26AU4PbiaPXK5uVmMSM+V5BYsFBiM9HhGRLUA= +github.com/mitchellh/cli v1.1.4/go.mod h1:vTLESy5mRhKOs9KDp0/RATawxP1UqBmdrpVRMnpcvKQ= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -164,28 +181,40 @@ github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9 github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= +github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= @@ -194,8 +223,9 @@ github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaU github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= -github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc= +github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= @@ -204,15 +234,15 @@ github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uU github.com/zclconf/go-cty v1.11.0 h1:726SxLdi2SDnjY+BStqB9J1hNp4+2WlzyXLuimibIe0= github.com/zclconf/go-cty v1.11.0/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167 h1:O8uGbHCqlTp2P6QJSLmCojM4mN6UemYv8K+dCnmHmu0= -golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -227,16 +257,13 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -250,64 +277,54 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200711021454-869866162049 h1:YFTFpQhgvrLrmxtiIncJxFXeCyq84ixuKWVCaCAi9Oc= google.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= -google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -319,10 +336,10 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/localtypes/file_permission_type.go b/internal/localtypes/file_permission_type.go new file mode 100644 index 00000000..8c36ad83 --- /dev/null +++ b/internal/localtypes/file_permission_type.go @@ -0,0 +1,67 @@ +package localtypes + +import ( + "context" + "strconv" + + "github.com/hashicorp/terraform-plugin-framework/attr/xattr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +var ( + _ basetypes.StringTypable = FilePermissionType{} + _ xattr.TypeWithValidate = FilePermissionType{} +) + +type FilePermissionType struct { + basetypes.StringType +} + +func NewFilePermissionType() FilePermissionType { + return FilePermissionType{StringType: types.StringType} +} + +// Validate checks that the given input string is a valid file permission string, +// expressed in numeric notation. +// See: https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation +func (f FilePermissionType) Validate(ctx context.Context, value tftypes.Value, path path.Path) diag.Diagnostics { + var diags diag.Diagnostics + + if value.IsNull() { + return diags + } + + if !value.IsKnown() { + return diags + } + + var fp string + err := value.As(&fp) + if err != nil { + diags.Append(diag.NewAttributeErrorDiagnostic(path, + "Invalid File Permission String Value", + "An unexpected error occurred while converting the file permission to a string value"+ + "Error: "+err.Error())) + return diags + } + + if len(fp) < 3 || len(fp) > 4 { + diags.Append(diag.NewAttributeErrorDiagnostic(path, + "Invalid File Permission String Value", + "bad mode permission: string length should be 3 or 4 digits: "+fp)) + return diags + } + + fileMode, err := strconv.ParseInt(fp, 8, 64) + if err != nil || fileMode > 0777 || fileMode < 0 { + diags.Append(diag.NewAttributeErrorDiagnostic(path, + "Invalid File Permission String Value", + "bad mode permission: string must be expressed in octal numeric notation: "+fp)) + return diags + } + return diags +} diff --git a/internal/provider/validators_test.go b/internal/localtypes/file_permission_type_test.go similarity index 57% rename from internal/provider/validators_test.go rename to internal/localtypes/file_permission_type_test.go index 479aa24f..1537791e 100644 --- a/internal/provider/validators_test.go +++ b/internal/localtypes/file_permission_type_test.go @@ -1,11 +1,18 @@ -package provider +package localtypes import ( + "context" "regexp" "testing" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-go/tftypes" ) -func TestValidateNoTrailingSlash(t *testing.T) { +func TestFilePermissionTypeValidator(t *testing.T) { + t.Parallel() + testCases := []struct { val string expectedErr *regexp.Regexp @@ -34,10 +41,10 @@ func TestValidateNoTrailingSlash(t *testing.T) { }, } - matchErr := func(errs []error, r *regexp.Regexp) bool { + matchErr := func(diags diag.Diagnostics, r *regexp.Regexp) bool { // err must match one provided - for _, err := range errs { - if r.MatchString(err.Error()) { + for _, err := range diags { + if r.MatchString(err.Detail()) { return true } } @@ -46,18 +53,18 @@ func TestValidateNoTrailingSlash(t *testing.T) { } for i, tc := range testCases { - _, errs := validateModePermission(tc.val, "test_property") + diags := NewFilePermissionType().Validate(context.Background(), tftypes.NewValue(tftypes.String, tc.val), path.Empty()) - if len(errs) == 0 && tc.expectedErr == nil { + if !diags.HasError() && tc.expectedErr == nil { continue } - if len(errs) != 0 && tc.expectedErr == nil { - t.Fatalf("expected test case %d to produce no errors, got %v", i, errs) + if diags.HasError() && tc.expectedErr == nil { + t.Fatalf("expected test case %d to produce no errors, got %v", i, diags.Errors()) } - if !matchErr(errs, tc.expectedErr) { - t.Fatalf("expected test case %d to produce error matching \"%s\", got %v", i, tc.expectedErr, errs) + if !matchErr(diags, tc.expectedErr) { + t.Fatalf("expected test case %d to produce error matching \"%s\", got %v", i, tc.expectedErr, diags.Errors()) } } } diff --git a/internal/modifiers/stringmodifier/string_default.go b/internal/modifiers/stringmodifier/string_default.go new file mode 100644 index 00000000..b76e9aeb --- /dev/null +++ b/internal/modifiers/stringmodifier/string_default.go @@ -0,0 +1,41 @@ +package stringmodifier + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// stringDefaultModifier is a plan modifier that sets a default value for a +// types.StringType attribute when it is not configured. The attribute must be +// marked as Optional and Computed. When setting the state during the resource +// Create, Read, or Update methods, this default value must also be included or +// the Terraform CLI will generate an error. +type stringDefaultModifier struct { + Default string +} + +func (m stringDefaultModifier) Description(ctx context.Context) string { + return fmt.Sprintf("If value is not configured, defaults to %s", m.Default) +} + +func (m stringDefaultModifier) MarkdownDescription(ctx context.Context) string { + return fmt.Sprintf("If value is not configured, defaults to `%s`", m.Default) +} + +func (m stringDefaultModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) { + // If the value is known, do not set default value. + if !req.PlanValue.IsUnknown() { + return + } + + resp.PlanValue = types.StringValue(m.Default) +} + +func StringDefault(defaultValue string) planmodifier.String { + return stringDefaultModifier{ + Default: defaultValue, + } +} diff --git a/internal/provider/data_source_local_file.go b/internal/provider/data_source_local_file.go index 9c4a3423..de24e2f4 100644 --- a/internal/provider/data_source_local_file.go +++ b/internal/provider/data_source_local_file.go @@ -1,56 +1,96 @@ package provider import ( + "context" "crypto/sha1" "encoding/base64" "encoding/hex" - "io/ioutil" + "fmt" + "os" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" ) -func dataSourceLocalFile() *schema.Resource { - return &schema.Resource{ - Read: dataSourceLocalFileRead, +var ( + _ datasource.DataSource = (*localFileDataSource)(nil) +) - Description: "Reads a file from the local filesystem.", +func NewLocalFileDataSource() datasource.DataSource { + return &localFileDataSource{} +} - Schema: map[string]*schema.Schema{ - "filename": { - Type: schema.TypeString, +type localFileDataSource struct{} + +func (n *localFileDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Reads a file from the local filesystem.", + Attributes: map[string]schema.Attribute{ + "filename": schema.StringAttribute{ Description: "Path to the file that will be read. The data source will return an error if the file does not exist.", Required: true, - ForceNew: true, }, - "content": { - Type: schema.TypeString, - Description: "Raw content of the file that was read, as UTF-8 encoded string.", - Computed: true, + "content": schema.StringAttribute{ + Description: "Raw content of the file that was read, as UTF-8 encoded string. " + + "Files that do not contain UTF-8 text will have invalid UTF-8 sequences in `content`\n replaced with the Unicode replacement character. ", + Computed: true, }, - "content_base64": { - Type: schema.TypeString, + "content_base64": schema.StringAttribute{ Description: "Base64 encoded version of the file content (use this when dealing with binary data).", Computed: true, }, + "id": schema.StringAttribute{ + Description: "The hexadecimal encoding of the checksum of the file content.", + Computed: true, + }, }, } } -func dataSourceLocalFileRead(d *schema.ResourceData, _ interface{}) error { +func (n *localFileDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_file" +} + +func (n *localFileDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + + var config localFileDataSourceModelV0 + + diags := req.Config.Get(ctx, &config) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + // Read the entire file content - path := d.Get("filename").(string) - content, err := ioutil.ReadFile(path) + filepath := config.Filename.ValueString() + content, err := os.ReadFile(filepath) + if err != nil { - return err + resp.Diagnostics.AddError( + "Read local file data source error", + "The file at given path cannot be read.\n\n+"+ + fmt.Sprintf("Original Error: %s", err), + ) + return } - // Set the content both as UTF-8 string, and as base64 encoded string - d.Set("content", string(content)) - d.Set("content_base64", base64.StdEncoding.EncodeToString(content)) - - // Use the hexadecimal encoding of the checksum of the file content as ID + //calculate the checksum of file content checksum := sha1.Sum(content) - d.SetId(hex.EncodeToString(checksum[:])) - return nil + state := localFileDataSourceModelV0{ + Filename: config.Filename, + Content: types.StringValue(string(content)), + ContentBase64: types.StringValue(base64.StdEncoding.EncodeToString(content)), + ID: types.StringValue(hex.EncodeToString(checksum[:])), + } + diags = resp.State.Set(ctx, state) + resp.Diagnostics.Append(diags...) +} + +type localFileDataSourceModelV0 struct { + Filename types.String `tfsdk:"filename"` + Content types.String `tfsdk:"content"` + ContentBase64 types.String `tfsdk:"content_base64"` + ID types.String `tfsdk:"id"` } diff --git a/internal/provider/data_source_local_file_test.go b/internal/provider/data_source_local_file_test.go index d478f58e..756f8793 100644 --- a/internal/provider/data_source_local_file_test.go +++ b/internal/provider/data_source_local_file_test.go @@ -17,7 +17,7 @@ func TestLocalFileDataSource(t *testing.T) { ` resource.UnitTest(t, resource.TestCase{ - Providers: testProviders, + ProtoV5ProviderFactories: protoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: config, diff --git a/internal/provider/data_source_local_sensitive_file.go b/internal/provider/data_source_local_sensitive_file.go index 4c8017cf..12927d0f 100644 --- a/internal/provider/data_source_local_sensitive_file.go +++ b/internal/provider/data_source_local_sensitive_file.go @@ -1,42 +1,61 @@ package provider import ( - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" ) -func dataSourceLocalSensitiveFile() *schema.Resource { - return &schema.Resource{ - Read: dataSourceLocalSensitiveFileRead, +var ( + _ datasource.DataSource = (*localSensitiveFileDataSource)(nil) +) - Description: "Reads a file that contains sensitive data, from the local filesystem.", +func NewLocalSensitiveFileDataSourceWithSchema() datasource.DataSource { + return &localSensitiveFileDataSource{} +} + +func NewLocalSensitiveFileDataSource() datasource.DataSource { + return &localSensitiveFileDataSource{} +} - Schema: map[string]*schema.Schema{ - "filename": { - Type: schema.TypeString, +type localSensitiveFileDataSource struct{} + +func (n *localSensitiveFileDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_sensitive_file" +} + +func (n *localSensitiveFileDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Reads a file that contains sensitive data, from the local filesystem.", + Attributes: map[string]schema.Attribute{ + "filename": schema.StringAttribute{ Description: "Path to the file that will be read. The data source will return an error if the file does not exist.", Required: true, - ForceNew: true, }, - "content": { - Type: schema.TypeString, - Description: "Raw content of the file that was read, as UTF-8 encoded string.", + "content": schema.StringAttribute{ + Description: "Raw content of the file that was read, as UTF-8 encoded string. " + + "Files that do not contain UTF-8 text will have invalid UTF-8 sequences in `content`\n replaced with the Unicode replacement character.", + Sensitive: true, + Computed: true, + }, + "content_base64": schema.StringAttribute{ + Description: "Base64 encoded version of the file content (use this when dealing with binary data).", Sensitive: true, Computed: true, }, - "content_base64": { - Type: schema.TypeString, - Description: "Base64 encoded version of the file raw content (use this when dealing with binary data).", - Sensitive: true, + "id": schema.StringAttribute{ + Description: "The hexadecimal encoding of the checksum of the file content", Computed: true, }, }, } } -func dataSourceLocalSensitiveFileRead(d *schema.ResourceData, m interface{}) error { +func (n *localSensitiveFileDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // NOTE: We can use the read-method for the data source `local_file` as-is, because // all this data source does, is adding "Sensitive: true" to the schema of the property. // // The values and the property names are meant to be kept the same between data sources. - return dataSourceLocalFileRead(d, m) + NewLocalFileDataSource().Read(ctx, req, resp) } diff --git a/internal/provider/data_source_local_sensitive_file_test.go b/internal/provider/data_source_local_sensitive_file_test.go index 27b2ac44..58780e9a 100644 --- a/internal/provider/data_source_local_sensitive_file_test.go +++ b/internal/provider/data_source_local_sensitive_file_test.go @@ -1,9 +1,11 @@ package provider import ( + "context" "encoding/base64" "testing" + "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) @@ -17,7 +19,7 @@ func TestLocalFileSensitiveDataSource(t *testing.T) { ` resource.UnitTest(t, resource.TestCase{ - Providers: testProviders, + ProtoV5ProviderFactories: protoV5ProviderFactories(), Steps: []resource.TestStep{ { Config: config, @@ -31,13 +33,15 @@ func TestLocalFileSensitiveDataSource(t *testing.T) { } func TestLocalFileSensitiveDataSourceCheckSensitiveAttributes(t *testing.T) { - dsSchema := dataSourceLocalSensitiveFile() + dataSource := NewLocalSensitiveFileDataSourceWithSchema() + schemaResponse := datasource.SchemaResponse{} - if !dsSchema.Schema["content"].Sensitive { + dataSource.Schema(context.Background(), datasource.SchemaRequest{}, &schemaResponse) + if !schemaResponse.Schema.Attributes["content"].IsSensitive() { t.Errorf("attribute 'content' should be marked as 'Sensitive'") } - if !dsSchema.Schema["content_base64"].Sensitive { + if !schemaResponse.Schema.Attributes["content_base64"].IsSensitive() { t.Errorf("attribute 'content_base64' should be marked as 'Sensitive'") } } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 49079595..3339a592 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1,19 +1,46 @@ package provider import ( - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/provider" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +var ( + _ provider.Provider = (*localProvider)(nil) ) -func New() *schema.Provider { - return &schema.Provider{ - Schema: map[string]*schema.Schema{}, - ResourcesMap: map[string]*schema.Resource{ - "local_file": resourceLocalFile(), - "local_sensitive_file": resourceLocalSensitiveFile(), - }, - DataSourcesMap: map[string]*schema.Resource{ - "local_file": dataSourceLocalFile(), - "local_sensitive_file": dataSourceLocalSensitiveFile(), - }, +func New() provider.Provider { + return &localProvider{} +} + +type localProvider struct{} + +func (p *localProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "local" +} + +func (p *localProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + +} + +func (p *localProvider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + NewLocalFileDataSource, + NewLocalSensitiveFileDataSource, + } +} + +func (p *localProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + NewLocalFileResource, + NewLocalSensitiveFileResource, } } + +func (p *localProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{} +} diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index fa23c0ad..44c4e4b2 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -3,28 +3,105 @@ package provider import ( "fmt" "os" - "testing" + "path" + "runtime" + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) -var testProviders = map[string]*schema.Provider{ - "local": New(), +func protoV5ProviderFactories() map[string]func() (tfprotov5.ProviderServer, error) { + return map[string]func() (tfprotov5.ProviderServer, error){ + "local": providerserver.NewProtocol5WithError(New()), + } } -func TestProvider(t *testing.T) { - if err := New().InternalValidate(); err != nil { - t.Fatalf("err: %s", err) +func providerVersion233() map[string]resource.ExternalProvider { + return map[string]resource.ExternalProvider{ + "local": { + VersionConstraint: "2.2.3", + Source: "hashicorp/local", + }, } } func checkFileDeleted(shouldNotExistFile string) resource.TestCheckFunc { - return func(*terraform.State) error { + return func(s *terraform.State) error { if _, err := os.Stat(shouldNotExistFile); os.IsNotExist(err) { return nil } return fmt.Errorf("file %s was not deleted", shouldNotExistFile) } } + +func checkFileCreation(resourceName, path string) resource.TestCheckFunc { + return func(s *terraform.State) error { + resultContent, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("Error occurred while reading file at path: %s\n, error: %s\n", path, err) + } + + resource.TestCheckResourceAttr(resourceName, "content", string(resultContent)) + + return nil + } +} + +func checkFilePermissions(destinationFilePath string) resource.TestCheckFunc { + return func(s *terraform.State) error { + filePermission := os.FileMode(0600) + fileInfo, err := os.Stat(destinationFilePath) + if err != nil { + return fmt.Errorf("Error occurred while retrieving file info at path: %s\n, error: %s\n", + destinationFilePath, err) + } + + if fileInfo.Mode() != filePermission { + return fmt.Errorf( + "File permission.\nexpected:%s\ngot: %s\n", + filePermission, fileInfo.Mode()) + } + + return nil + } +} + +func checkDirectoryPermissions(destinationFilePath string) resource.TestCheckFunc { + return func(s *terraform.State) error { + directoryPermission := os.FileMode(0700) + dirInfo, _ := os.Stat(path.Dir(destinationFilePath)) + // we have to use FileMode.Perm() here, otherwise directory bit causes issues + if dirInfo.Mode().Perm() != directoryPermission.Perm() { + return fmt.Errorf( + "Directory permission. \nexpected:%s\ngot: %s\n", + directoryPermission, dirInfo.Mode().Perm()) + } + + return nil + } +} + +func createSourceFile(sourceFilePath, sourceContent string) error { + return os.WriteFile(sourceFilePath, []byte(sourceContent), 0644) +} + +func checkDirExists(destinationFilePath string, isDirExist *bool) func() { + return func() { + // if directory already existed prior to check, skip check + if _, err := os.Stat(path.Dir(destinationFilePath)); !os.IsNotExist(err) { + *isDirExist = true + } + } +} + +func skipTestsWindows() func() (bool, error) { + return func() (bool, error) { + if runtime.GOOS == "windows" { + // skip all checks if windows + return true, nil + } + return false, nil + } +} diff --git a/internal/provider/resource_local_file.go b/internal/provider/resource_local_file.go index 4f69a71a..03553b1d 100644 --- a/internal/provider/resource_local_file.go +++ b/internal/provider/resource_local_file.go @@ -1,160 +1,286 @@ package provider import ( + "context" "crypto/sha1" "encoding/base64" "encoding/hex" - "io/ioutil" + "fmt" "os" - "path" + "path/filepath" "strconv" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/terraform-providers/terraform-provider-local/internal/localtypes" + "github.com/terraform-providers/terraform-provider-local/internal/modifiers/stringmodifier" ) -func resourceLocalFile() *schema.Resource { - return &schema.Resource{ - Create: resourceLocalFileCreate, - Read: resourceLocalFileRead, - Delete: resourceLocalFileDelete, +var ( + _ resource.Resource = (*localFileResource)(nil) +) - Description: "Generates a local file with the given content.", +func NewLocalFileResource() resource.Resource { + return &localFileResource{} +} - Schema: map[string]*schema.Schema{ - "content": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ExactlyOneOf: []string{"content", "sensitive_content", "content_base64", "source"}, - Description: "Content to store in the file, expected to be an UTF-8 encoded string.", +type localFileResource struct{} + +func (n *localFileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Generates a local file with the given content.", + Attributes: map[string]schema.Attribute{ + "filename": schema.StringAttribute{ + Description: "The path to the file that will be created.\n " + + "Missing parent directories will be created.\n " + + "If the file already exists, it will be overridden with the given content.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, }, - "sensitive_content": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Sensitive: true, - ExactlyOneOf: []string{"content", "sensitive_content", "content_base64", "source"}, - Description: "Sensitive content to store in the file, expected to be an UTF-8 encoded string.", - Deprecated: "Use the `local_sensitive_file` resource instead.", + "content": schema.StringAttribute{ + Description: "Content to store in the file, expected to be a UTF-8 encoded string.\n " + + "Conflicts with `sensitive_content`, `content_base64` and `source`.\n " + + "Exactly one of these four arguments must be specified.", + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf( + path.MatchRoot("sensitive_content"), + path.MatchRoot("content_base64"), + path.MatchRoot("source")), + }, }, - "content_base64": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ExactlyOneOf: []string{"content", "sensitive_content", "content_base64", "source"}, - Description: "Content to store in the file, expected to be binary encoded as base64 string.", + "content_base64": schema.StringAttribute{ + Description: "Content to store in the file, expected to be binary encoded as base64 string.\n " + + "Conflicts with `content`, `sensitive_content` and `source`.\n " + + "Exactly one of these four arguments must be specified.", + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf( + path.MatchRoot("content"), + path.MatchRoot("sensitive_content"), + path.MatchRoot("source")), + }, }, - "source": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ExactlyOneOf: []string{"content", "sensitive_content", "content_base64", "source"}, - Description: "Path to file to use as source for the one we are creating.", + "source": schema.StringAttribute{ + Description: "Path to file to use as source for the one we are creating.\n " + + "Conflicts with `content`, `sensitive_content` and `content_base64`.\n " + + "Exactly one of these four arguments must be specified.", + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf( + path.MatchRoot("content"), + path.MatchRoot("sensitive_content"), + path.MatchRoot("content_base64")), + }, }, - "filename": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: ` - The path to the file that will be created. - Missing parent directories will be created. - If the file already exists, it will be overridden with the given content. - `, + "file_permission": schema.StringAttribute{ + CustomType: localtypes.NewFilePermissionType(), + Description: "Permissions to set for the output file (before umask), expressed as string in\n " + + "[numeric notation](https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation).\n " + + "Default value is `\"0777\"`.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + stringmodifier.StringDefault("0777"), + }, + }, + "directory_permission": schema.StringAttribute{ + CustomType: localtypes.NewFilePermissionType(), + Description: "Permissions to set for directories created (before umask), expressed as string in\n " + + "[numeric notation](https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation).\n " + + "Default value is `\"0777\"`.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + stringmodifier.StringDefault("0777"), + }, }, - "file_permission": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: "0777", - ValidateFunc: validateModePermission, - Description: "Permissions to set for the output file (in numeric notation).", + "id": schema.StringAttribute{ + Description: "The hexadecimal encoding of the checksum of the file content", + Computed: true, }, - "directory_permission": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: "0777", - ValidateFunc: validateModePermission, - Description: "Permissions to set for directories created (in numeric notation).", + "sensitive_content": schema.StringAttribute{ + DeprecationMessage: "Use the `local_sensitive_file` resource instead", + Description: "Sensitive content to store in the file, expected to be an UTF-8 encoded string.\n " + + "Will not be displayed in diffs.\n " + + "Conflicts with `content`, `content_base64` and `source`.\n " + + "Exactly one of these four arguments must be specified.\n " + + "If in need to use _sensitive_ content, please use the [`local_sensitive_file`](./sensitive_file.html)\n " + + "resource instead.", + Sensitive: true, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf( + path.MatchRoot("content"), + path.MatchRoot("content_base64"), + path.MatchRoot("source")), + }, }, }, } } -func resourceLocalFileRead(d *schema.ResourceData, _ interface{}) error { - // If the output file doesn't exist, mark the resource for creation. - outputPath := d.Get("filename").(string) - if _, err := os.Stat(outputPath); os.IsNotExist(err) { - d.SetId("") - return nil +func (n *localFileResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_file" +} + +func (n *localFileResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan localFileResourceModelV0 + var filePerm, dirPerm string + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } - // Verify that the content of the destination file matches the content we - // expect. Otherwise, the file might have been modified externally, and we - // must reconcile. - outputContent, err := ioutil.ReadFile(outputPath) + content, err := parseLocalFileContent(plan) if err != nil { - return err + resp.Diagnostics.AddError( + "Create local file error", + "An unexpected error occurred while parsing local file content\n\n+"+ + fmt.Sprintf("Original Error: %s", err), + ) + return } - outputChecksum := sha1.Sum(outputContent) - if hex.EncodeToString(outputChecksum[:]) != d.Id() { - d.SetId("") - return nil + destination := plan.Filename.ValueString() + + destinationDir := filepath.Dir(destination) + if _, err := os.Stat(destinationDir); err != nil { + dirPerm = plan.DirectoryPermission.ValueString() + dirMode, _ := strconv.ParseInt(dirPerm, 8, 64) + if err := os.MkdirAll(destinationDir, os.FileMode(dirMode)); err != nil { + resp.Diagnostics.AddError( + "Create local file error", + "An unexpected error occurred while creating file directory\n\n+"+ + fmt.Sprintf("Original Error: %s", err), + ) + return + } + } + + filePerm = plan.FilePermission.ValueString() + + fileMode, _ := strconv.ParseInt(filePerm, 8, 64) + + if err := os.WriteFile(destination, content, os.FileMode(fileMode)); err != nil { + resp.Diagnostics.AddError( + "Create local file error", + "An unexpected error occurred while writing the file\n\n+"+ + fmt.Sprintf("Original Error: %s", err), + ) + return } - return nil + checksum := sha1.Sum(content) + + plan.ID = types.StringValue(hex.EncodeToString(checksum[:])) + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) } -func resourceLocalFileContent(d *schema.ResourceData) ([]byte, error) { - if sensitiveContent, ok := d.GetOk("sensitive_content"); ok { - return []byte(sensitiveContent.(string)), nil +func (n *localFileResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state localFileResourceModelV0 + + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } - if contentBase64, ok := d.GetOk("content_base64"); ok { - return base64.StdEncoding.DecodeString(contentBase64.(string)) + + // If the output file doesn't exist, mark the resource for creation. + outputPath := state.Filename.ValueString() + if _, err := os.Stat(outputPath); os.IsNotExist(err) { + resp.State.RemoveResource(ctx) + return } - if sourceFile, ok := d.GetOk("source"); ok { - sourceFileContent := sourceFile.(string) - return ioutil.ReadFile(sourceFileContent) + // Verify that the content of the destination file matches the content we + // expect. Otherwise, the file might have been modified externally, and we + // must reconcile. + outputContent, err := os.ReadFile(outputPath) + if err != nil { + resp.Diagnostics.AddError( + "Read local file error", + "An unexpected error occurred while reading the file\n\n+"+ + fmt.Sprintf("Original Error: %s", err), + ) + return } - content := d.Get("content") - return []byte(content.(string)), nil + outputChecksum := sha1.Sum(outputContent) + if hex.EncodeToString(outputChecksum[:]) != state.ID.ValueString() { + resp.State.RemoveResource(ctx) + return + } } -func resourceLocalFileCreate(d *schema.ResourceData, _ interface{}) error { - content, err := resourceLocalFileContent(d) - if err != nil { - return err - } +func (n *localFileResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan localFileResourceModelV0 - destination := d.Get("filename").(string) + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - destinationDir := path.Dir(destination) - if _, err := os.Stat(destinationDir); err != nil { - dirPerm := d.Get("directory_permission").(string) - dirMode, _ := strconv.ParseInt(dirPerm, 8, 64) - if err := os.MkdirAll(destinationDir, os.FileMode(dirMode)); err != nil { - return err - } + if resp.Diagnostics.HasError() { + return } - filePerm := d.Get("file_permission").(string) + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} - fileMode, _ := strconv.ParseInt(filePerm, 8, 64) +func (n *localFileResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var filename string + req.State.GetAttribute(ctx, path.Root("filename"), &filename) + os.Remove(filename) +} - if err := ioutil.WriteFile(destination, content, os.FileMode(fileMode)); err != nil { - return err +func parseLocalFileContent(plan localFileResourceModelV0) ([]byte, error) { + if !plan.SensitiveContent.IsNull() && !plan.SensitiveContent.IsUnknown() { + return []byte(plan.SensitiveContent.ValueString()), nil + } + if !plan.ContentBase64.IsNull() && !plan.ContentBase64.IsUnknown() { + return base64.StdEncoding.DecodeString(plan.ContentBase64.ValueString()) } - checksum := sha1.Sum(content) - d.SetId(hex.EncodeToString(checksum[:])) + if !plan.Source.IsNull() && !plan.Source.IsUnknown() { + sourceFileContent := plan.Source.ValueString() + return os.ReadFile(sourceFileContent) + } - return nil + content := plan.Content.ValueString() + return []byte(content), nil } -func resourceLocalFileDelete(d *schema.ResourceData, _ interface{}) error { - os.Remove(d.Get("filename").(string)) - return nil +type localFileResourceModelV0 struct { + Filename types.String `tfsdk:"filename"` + Content types.String `tfsdk:"content"` + ContentBase64 types.String `tfsdk:"content_base64"` + Source types.String `tfsdk:"source"` + FilePermission types.String `tfsdk:"file_permission"` + DirectoryPermission types.String `tfsdk:"directory_permission"` + ID types.String `tfsdk:"id"` + SensitiveContent types.String `tfsdk:"sensitive_content"` } diff --git a/internal/provider/resource_local_file_test.go b/internal/provider/resource_local_file_test.go index ba4d61ed..adceef49 100644 --- a/internal/provider/resource_local_file_test.go +++ b/internal/provider/resource_local_file_test.go @@ -2,184 +2,301 @@ package provider import ( "fmt" - "io/ioutil" "os" - "path" "path/filepath" - "runtime" + "regexp" "strings" "testing" r "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) func TestLocalFile_Basic(t *testing.T) { f := filepath.Join(t.TempDir(), "local_file") f = strings.ReplaceAll(f, `\`, `\\`) - var cases = []struct { - path string - content string - config string - }{ - { - f, - "This is some content", fmt.Sprintf(` - resource "local_file" "file" { - content = "This is some content" - filename = "%s" - }`, f, - ), + r.UnitTest(t, r.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []r.TestStep{ + { + Config: testAccConfigLocalFileContent("This is some content", f), + Check: checkFileCreation("local_file_resource.test", f), + }, + { + Config: testAccConfigLocalFileSensitiveContent("This is some sensitive content", f), + Check: checkFileCreation("local_file_resource.test", f), + }, + { + Config: testAccConfigLocalFileEncodedBase64Content("VGhpcyBpcyBzb21lIGJhc2U2NCBjb250ZW50", f), + Check: checkFileCreation("local_file_resource.test", f), + }, + { + Config: testAccConfigLocalFileDecodedBase64Content("This is some base64 content", f), + Check: checkFileCreation("local_file_resource.test", f), + }, }, - { - f, - "This is some sensitive content", fmt.Sprintf(` - resource "local_file" "file" { - sensitive_content = "This is some sensitive content" - filename = "%s" - }`, f, - ), + CheckDestroy: checkFileDeleted(f), + }) +} + +func TestLocalFile_Source(t *testing.T) { + sourceDirPath := t.TempDir() + sourceFilePath := filepath.Join(sourceDirPath, "source_file") + sourceFilePath = strings.ReplaceAll(sourceFilePath, `\`, `\\`) + // create a local file that will be used as the "source" file + if err := createSourceFile(sourceFilePath, "local file content"); err != nil { + t.Fatal(err) + } + + destinationDirPath := t.TempDir() + destinationFilePath := filepath.Join(destinationDirPath, "new_file") + destinationFilePath = strings.ReplaceAll(destinationFilePath, `\`, `\\`) + + r.UnitTest(t, r.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []r.TestStep{ + { + Config: testAccConfigLocalSourceFile(sourceFilePath, destinationFilePath), + Check: checkFileCreation("local_file_resource.test", destinationFilePath), + }, + }, + CheckDestroy: checkFileDeleted(destinationFilePath), + }) +} + +func TestLocalFile_Permissions(t *testing.T) { + destinationDirPath := t.TempDir() + destinationFilePath := filepath.Join(destinationDirPath, "local_file") + destinationFilePath = strings.ReplaceAll(destinationFilePath, `\`, `\\`) + isDirExist := false + + r.UnitTest(t, r.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []r.TestStep{ + { + PreConfig: checkDirExists(destinationDirPath, &isDirExist), + SkipFunc: skipTestsWindows(), + Config: fmt.Sprintf(` + resource "local_file" "file" { + content = "This is some content" + filename = "%s" + file_permission = "0600" + directory_permission = "0700" + }`, destinationFilePath, + ), + Check: r.ComposeTestCheckFunc( + checkFilePermissions(destinationFilePath), + checkDirectoryPermissions(destinationFilePath), + ), + }, + }, + ErrorCheck: func(err error) error { + if match, _ := regexp.MatchString("Directory permission.", err.Error()); match && isDirExist { + return nil + } + return err }, - { - f, - "This is some base64 content", fmt.Sprintf(` + CheckDestroy: checkFileDeleted(destinationFilePath), + }) +} + +func TestLocalFile_Validators(t *testing.T) { + f := filepath.Join(t.TempDir(), "local_file") + f = strings.ReplaceAll(f, `\`, `\\`) + + r.UnitTest(t, r.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + CheckDestroy: nil, + Steps: []r.TestStep{ + { + Config: fmt.Sprintf(` resource "local_file" "file" { - content_base64 = "VGhpcyBpcyBzb21lIGJhc2U2NCBjb250ZW50" filename = "%s" - }`, f, - ), - }, - { - f, - "This is some base64 content", fmt.Sprintf(` + }`, f), + ExpectError: regexp.MustCompile(`.*Error: Invalid Attribute Combination`), + }, + { + Config: fmt.Sprintf(` resource "local_file" "file" { - content_base64 = base64encode("This is some base64 content") + content = "content" + sensitive_content = "sensitive_content" filename = "%s" - }`, f, - ), + }`, f), + ExpectError: regexp.MustCompile(`.*Error: Invalid Attribute Combination`), + }, }, - } + }) +} - for i, tt := range cases { - t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { - r.UnitTest(t, r.TestCase{ - Providers: testProviders, - Steps: []r.TestStep{ - { - Config: tt.config, - Check: func(s *terraform.State) error { - content, err := ioutil.ReadFile(tt.path) - if err != nil { - return fmt.Errorf("config:\n%s\n,got: %s\n", tt.config, err) - } - if string(content) != tt.content { - return fmt.Errorf("config:\n%s\ngot:\n%s\nwant:\n%s\n", tt.config, content, tt.content) - } - return nil - }, - }, - }, - CheckDestroy: checkFileDeleted(tt.path), - }) - }) - } +func TestLocalFile_Upgrade(t *testing.T) { + f := filepath.Join(t.TempDir(), "local_file") + f = strings.ReplaceAll(f, `\`, `\\`) + + r.Test(t, r.TestCase{ + Steps: []r.TestStep{ + { + ExternalProviders: providerVersion233(), + Config: testAccConfigLocalFileContent("This is some content", f), + Check: checkFileCreation("local_file_resource.test", f), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: testAccConfigLocalFileContent("This is some content", f), + PlanOnly: true, + }, + { + ExternalProviders: providerVersion233(), + Config: testAccConfigLocalFileSensitiveContent("This is some sensitive content", f), + Check: checkFileCreation("local_file_resource.test", f), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: testAccConfigLocalFileSensitiveContent("This is some sensitive content", f), + PlanOnly: true, + }, + { + ExternalProviders: providerVersion233(), + Config: testAccConfigLocalFileEncodedBase64Content("VGhpcyBpcyBzb21lIGJhc2U2NCBjb250ZW50", f), + Check: checkFileCreation("local_file_resource.test", f), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: testAccConfigLocalFileEncodedBase64Content("VGhpcyBpcyBzb21lIGJhc2U2NCBjb250ZW50", f), + PlanOnly: true, + }, + { + ExternalProviders: providerVersion233(), + Config: testAccConfigLocalFileDecodedBase64Content("This is some base64 content", f), + Check: checkFileCreation("local_file_resource.test", f), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: testAccConfigLocalFileDecodedBase64Content("This is some base64 content", f), + PlanOnly: true, + }, + }, + CheckDestroy: checkFileDeleted(f), + }) } -func TestLocalFile_source(t *testing.T) { +func TestLocalFile_Source_Upgrade(t *testing.T) { // create a local file that will be used as the "source" file - source_content := "local file content" - if err := ioutil.WriteFile("source_file", []byte(source_content), 0644); err != nil { + if err := os.WriteFile("./testdata/source_file", []byte("sourceContent"), 0644); err != nil { t.Fatal(err) } - defer os.Remove("source_file") - - config := ` - resource "local_file" "file" { - source = "source_file" - filename = "new_file" - } - ` + defer os.Remove("./testdata/source_file") - r.UnitTest(t, r.TestCase{ - Providers: testProviders, + r.Test(t, r.TestCase{ Steps: []r.TestStep{ { - Config: config, - Check: func(s *terraform.State) error { - content, err := ioutil.ReadFile("new_file") - if err != nil { - return fmt.Errorf("config:\n%s\n,got: %s\n", config, err) + ExternalProviders: providerVersion233(), + Config: ` + resource "local_file" "file" { + source = "./testdata/source_file" + filename = "./testdata/new_file" } - if string(content) != source_content { - return fmt.Errorf("config:\n%s\ngot:\n%s\nwant:\n%s\n", config, content, source_content) + `, + Check: checkFileCreation("local_file_resource.test", "./testdata/new_file"), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: ` + resource "local_file" "file" { + source = "./testdata/source_file" + filename = "./testdata/new_file" } - return nil - }, + `, + PlanOnly: true, }, }, CheckDestroy: checkFileDeleted("new_file"), }) } -func TestLocalFile_Permissions(t *testing.T) { +func TestLocalFile_Permissions_Upgrade(t *testing.T) { destinationDirPath := t.TempDir() destinationFilePath := filepath.Join(destinationDirPath, "local_file") destinationFilePath = strings.ReplaceAll(destinationFilePath, `\`, `\\`) - filePermission := os.FileMode(0600) - directoryPermission := os.FileMode(0700) - skipDirCheck := false - config := fmt.Sprintf(` - resource "local_file" "file" { - content = "This is some content" - filename = "%s" - file_permission = "0600" - directory_permission = "0700" - }`, destinationFilePath, - ) + isDirExist := false - r.UnitTest(t, r.TestCase{ - Providers: testProviders, + r.Test(t, r.TestCase{ Steps: []r.TestStep{ { - Config: config, - PreConfig: func() { - // if directory already existed prior to check, skip check - if _, err := os.Stat(path.Dir(destinationFilePath)); !os.IsNotExist(err) { - skipDirCheck = true - } - }, - Check: func(s *terraform.State) error { - if runtime.GOOS == "windows" { - // skip all checks if windows - return nil - } - - fileInfo, err := os.Stat(destinationFilePath) - if err != nil { - return fmt.Errorf("config:\n%s\ngot:%s\n", config, err) - } - - if fileInfo.Mode() != filePermission { - return fmt.Errorf( - "File permission.\nconfig:\n%s\nexpected:%s\ngot: %s\n", - config, filePermission, fileInfo.Mode()) - } - - if !skipDirCheck { - dirInfo, _ := os.Stat(path.Dir(destinationFilePath)) - // we have to use FileMode.Perm() here, otherwise directory bit causes issues - if dirInfo.Mode().Perm() != directoryPermission.Perm() { - return fmt.Errorf( - "Directory permission.\nconfig:\n%s\nexpected:%s\ngot: %s\n", - config, directoryPermission, dirInfo.Mode().Perm()) - } - } - - return nil - }, + ExternalProviders: providerVersion233(), + SkipFunc: skipTestsWindows(), + PreConfig: checkDirExists(destinationDirPath, &isDirExist), + Config: fmt.Sprintf(` + resource "local_file" "file" { + content = "This is some content" + filename = "%s" + file_permission = "0600" + directory_permission = "0700" + }`, destinationFilePath, + ), + Check: r.ComposeTestCheckFunc( + checkFilePermissions(destinationFilePath), + checkDirectoryPermissions(destinationFilePath)), }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + SkipFunc: skipTestsWindows(), + Config: fmt.Sprintf(` + resource "local_file" "file" { + content = "This is some content" + filename = "%s" + file_permission = "0600" + directory_permission = "0700" + }`, destinationFilePath, + ), + PlanOnly: true, + }, + }, + ErrorCheck: func(err error) error { + if match, _ := regexp.MatchString("Directory permission.", err.Error()); match && isDirExist { + return nil + } + return err }, CheckDestroy: checkFileDeleted(destinationFilePath), }) } + +func testAccConfigLocalSourceFile(source, filename string) string { + return fmt.Sprintf(` + resource "local_file" "file" { + source = %[1]q + filename = %[2]q + }`, source, filename) +} + +func testAccConfigLocalFileContent(content, filename string) string { + return fmt.Sprintf(` + resource "local_file" "file" { + content = %[1]q + filename = %[2]q + }`, content, filename) +} + +func testAccConfigLocalFileSensitiveContent(content, filename string) string { + return fmt.Sprintf(` + resource "local_file" "file" { + sensitive_content = %[1]q + filename = %[2]q + }`, content, filename) +} + +func testAccConfigLocalFileEncodedBase64Content(content, filename string) string { + return fmt.Sprintf(` + resource "local_file" "file" { + content_base64 = %[1]q + filename = %[2]q + }`, content, filename) +} + +func testAccConfigLocalFileDecodedBase64Content(content, filename string) string { + return fmt.Sprintf(` + resource "local_file" "file" { + content_base64 = base64encode(%[1]q) + filename = %[2]q + }`, content, filename) +} diff --git a/internal/provider/resource_local_sensitive_file.go b/internal/provider/resource_local_sensitive_file.go index b42ccf4e..8ed77e1b 100644 --- a/internal/provider/resource_local_sensitive_file.go +++ b/internal/provider/resource_local_sensitive_file.go @@ -1,79 +1,262 @@ package provider import ( - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "context" + "crypto/sha1" + "encoding/base64" + "encoding/hex" + "fmt" + "os" + "path/filepath" + "strconv" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/terraform-providers/terraform-provider-local/internal/localtypes" + "github.com/terraform-providers/terraform-provider-local/internal/modifiers/stringmodifier" +) + +var ( + _ resource.Resource = (*localSensitiveFileResource)(nil) ) -func resourceLocalSensitiveFile() *schema.Resource { - return &schema.Resource{ - Create: resourceLocalSensitiveFileCreate, - Read: resourceLocalSensitiveFileRead, - Delete: resourceLocalSensitiveFileDelete, +func NewLocalSensitiveFileResource() resource.Resource { + return &localSensitiveFileResource{} +} - Description: "Generates a local file with the given sensitive content.", +type localSensitiveFileResource struct{} - Schema: map[string]*schema.Schema{ - "content": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Sensitive: true, - ExactlyOneOf: []string{"content", "content_base64", "source"}, - Description: "Sensitive content to store in the file, expected to be an UTF-8 encoded string.", +func (n *localSensitiveFileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Generates a local file with the given sensitive content.", + Attributes: map[string]schema.Attribute{ + "filename": schema.StringAttribute{ + Description: "The path to the file that will be created.\n " + + "Missing parent directories will be created.\n " + + "If the file already exists, it will be overridden with the given content.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, }, - "content_base64": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Sensitive: true, - ExactlyOneOf: []string{"content", "content_base64", "source"}, - Description: "Sensitive content to store in the file, expected to be binary encoded as base64 string.", + "content": schema.StringAttribute{ + Description: "Sensitive Content to store in the file, expected to be a UTF-8 encoded string.\n " + + "Conflicts with `content_base64` and `source`.\n " + + "Exactly one of these three arguments must be specified.", + Sensitive: true, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf( + path.MatchRoot("content_base64"), + path.MatchRoot("source")), + }, }, - "source": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ExactlyOneOf: []string{"content", "content_base64", "source"}, - Description: "Path to file to use as source for the one we are creating.", + "content_base64": schema.StringAttribute{ + Description: "Sensitive Content to store in the file, expected to be binary encoded as base64 string.\n " + + "Conflicts with `content` and `source`.\n " + + "Exactly one of these three arguments must be specified.", + Sensitive: true, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf( + path.MatchRoot("content"), + path.MatchRoot("source")), + }, }, - "filename": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: ` - The path to the file that will be created. - Missing parent directories will be created. - If the file already exists, it will be overridden with the given content. - `, + "source": schema.StringAttribute{ + Description: "Path to file to use as source for the one we are creating.\n " + + "Conflicts with `content` and `content_base64`.\n " + + "Exactly one of these three arguments must be specified.", + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf( + path.MatchRoot("content"), + path.MatchRoot("content_base64")), + }, }, - "file_permission": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: "0700", - ValidateFunc: validateModePermission, - Description: "Permissions to set for the output file (in numeric notation).", + "file_permission": schema.StringAttribute{ + CustomType: localtypes.NewFilePermissionType(), + Description: "Permissions to set for the output file (before umask), expressed as string in\n " + + "[numeric notation](https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation).\n " + + "Default value is `\"0700\"`.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + stringmodifier.StringDefault("0700"), + }, }, - "directory_permission": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: "0700", - ValidateFunc: validateModePermission, - Description: "Permissions to set for directories created (in numeric notation).", + "directory_permission": schema.StringAttribute{ + CustomType: localtypes.NewFilePermissionType(), + Description: "Permissions to set for directories created (before umask), expressed as string in\n " + + "[numeric notation](https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation).\n " + + "Default value is `\"0700\"`.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + stringmodifier.StringDefault("0700"), + }, + }, + "id": schema.StringAttribute{ + Description: "The hexadecimal encoding of the checksum of the file content", + Computed: true, }, }, } } -func resourceLocalSensitiveFileRead(d *schema.ResourceData, m interface{}) error { - return resourceLocalFileRead(d, m) +func (n *localSensitiveFileResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_sensitive_file" +} + +func (n *localSensitiveFileResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan localSensitiveFileResourceModelV0 + + var filePerm, dirPerm string + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + content, err := parseLocalSensitiveFileContent(plan) + if err != nil { + resp.Diagnostics.AddError( + "Create local sensitive file error", + "An unexpected error occurred while parsing local file content\n\n+"+ + fmt.Sprintf("Original Error: %s", err), + ) + return + } + + destination := plan.Filename.ValueString() + + destinationDir := filepath.Dir(destination) + if _, err := os.Stat(destinationDir); err != nil { + dirPerm = plan.DirectoryPermission.ValueString() + dirMode, _ := strconv.ParseInt(dirPerm, 8, 64) + if err := os.MkdirAll(destinationDir, os.FileMode(dirMode)); err != nil { + resp.Diagnostics.AddError( + "Create local sensitive file error", + "An unexpected error occurred while creating file directory\n\n+"+ + fmt.Sprintf("Original Error: %s", err), + ) + return + } + } + + filePerm = plan.FilePermission.ValueString() + + fileMode, _ := strconv.ParseInt(filePerm, 8, 64) + + if err := os.WriteFile(destination, content, os.FileMode(fileMode)); err != nil { + resp.Diagnostics.AddError( + "Create local sensitive file error", + "An unexpected error occurred while writing the file\n\n+"+ + fmt.Sprintf("Original Error: %s", err), + ) + return + } + + checksum := sha1.Sum(content) + + plan.ID = types.StringValue(hex.EncodeToString(checksum[:])) + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +func (n *localSensitiveFileResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state localSensitiveFileResourceModelV0 + + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // If the output file doesn't exist, mark the resource for creation. + outputPath := state.Filename.ValueString() + if _, err := os.Stat(outputPath); os.IsNotExist(err) { + resp.State.RemoveResource(ctx) + return + } + + // Verify that the content of the destination file matches the content we + // expect. Otherwise, the file might have been modified externally, and we + // must reconcile. + outputContent, err := os.ReadFile(outputPath) + if err != nil { + resp.Diagnostics.AddError( + "Read local sensitive file error", + "An unexpected error occurred while reading the file\n\n+"+ + fmt.Sprintf("Original Error: %s", err), + ) + return + } + + outputChecksum := sha1.Sum(outputContent) + if hex.EncodeToString(outputChecksum[:]) != state.ID.ValueString() { + resp.State.RemoveResource(ctx) + return + } +} + +func (n *localSensitiveFileResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan localSensitiveFileResourceModelV0 + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (n *localSensitiveFileResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var filename string + req.State.GetAttribute(ctx, path.Root("filename"), &filename) + os.Remove(filename) } -func resourceLocalSensitiveFileCreate(d *schema.ResourceData, m interface{}) error { - return resourceLocalFileCreate(d, m) +func parseLocalSensitiveFileContent(plan localSensitiveFileResourceModelV0) ([]byte, error) { + if !plan.ContentBase64.IsNull() && !plan.ContentBase64.IsUnknown() { + return base64.StdEncoding.DecodeString(plan.ContentBase64.ValueString()) + } + + if !plan.Source.IsNull() && !plan.Source.IsUnknown() { + sourceFileContent := plan.Source.ValueString() + return os.ReadFile(sourceFileContent) + } + + content := plan.Content.ValueString() + return []byte(content), nil } -func resourceLocalSensitiveFileDelete(d *schema.ResourceData, m interface{}) error { - return resourceLocalFileDelete(d, m) +type localSensitiveFileResourceModelV0 struct { + Filename types.String `tfsdk:"filename"` + Content types.String `tfsdk:"content"` + ContentBase64 types.String `tfsdk:"content_base64"` + Source types.String `tfsdk:"source"` + FilePermission types.String `tfsdk:"file_permission"` + DirectoryPermission types.String `tfsdk:"directory_permission"` + ID types.String `tfsdk:"id"` } diff --git a/internal/provider/resource_local_sensitive_file_test.go b/internal/provider/resource_local_sensitive_file_test.go index 78f30cb1..1a99b518 100644 --- a/internal/provider/resource_local_sensitive_file_test.go +++ b/internal/provider/resource_local_sensitive_file_test.go @@ -2,114 +2,61 @@ package provider import ( "fmt" - "io/ioutil" "os" - "path" "path/filepath" - "runtime" + "regexp" "strings" "testing" r "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) func TestLocalSensitiveFile_Basic(t *testing.T) { f := filepath.Join(t.TempDir(), "local_sensitive_file") f = strings.ReplaceAll(f, `\`, `\\`) - var cases = []struct { - path string - content string - config string - }{ - { - f, - "This is some sensitive content", fmt.Sprintf(` - resource "local_sensitive_file" "file" { - content = "This is some sensitive content" - filename = "%s" - }`, f, - ), - }, - { - f, - "This is some sensitive base64 content", fmt.Sprintf(` - resource "local_sensitive_file" "file" { - content_base64 = "VGhpcyBpcyBzb21lIHNlbnNpdGl2ZSBiYXNlNjQgY29udGVudA==" - filename = "%s" - }`, f, - ), - }, - { - f, - "This is some sensitive base64 content", fmt.Sprintf(` - resource "local_sensitive_file" "file" { - content_base64 = base64encode("This is some sensitive base64 content") - filename = "%s" - }`, f, - ), + r.UnitTest(t, r.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []r.TestStep{ + { + Config: testAccConfigLocalSensitiveFileContent("This is some sensitive content", f), + Check: checkFileCreation("local_sensitive_file_resource.test", f), + }, + { + Config: testAccConfigLocalSensitiveFileEncodedBase64Content("VGhpcyBpcyBzb21lIGJhc2U2NCBjb250ZW50", f), + Check: checkFileCreation("local_sensitive_file_resource.test", f), + }, + { + Config: testAccConfigLocalSensitiveFileDecodedBase64Content("This is some base64 content", f), + Check: checkFileCreation("local_sensitive_file_resource.test", f), + }, }, - } - - for i, tt := range cases { - t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { - r.UnitTest(t, r.TestCase{ - Providers: testProviders, - Steps: []r.TestStep{ - { - Config: tt.config, - Check: func(s *terraform.State) error { - content, err := ioutil.ReadFile(tt.path) - if err != nil { - return fmt.Errorf("config:\n%s\n,got: %s\n", tt.config, err) - } - if string(content) != tt.content { - return fmt.Errorf("config:\n%s\ngot:\n%s\nwant:\n%s\n", tt.config, content, tt.content) - } - return nil - }, - }, - }, - CheckDestroy: checkFileDeleted(tt.path), - }) - }) - } + CheckDestroy: checkFileDeleted(f), + }) } func TestLocalSensitiveFile_source(t *testing.T) { + sourceDirPath := t.TempDir() + sourceFilePath := filepath.Join(sourceDirPath, "source_file") + sourceFilePath = strings.ReplaceAll(sourceFilePath, `\`, `\\`) // create a local file that will be used as the "source" file - source_content := "local file content" - if err := ioutil.WriteFile("source_file", []byte(source_content), 0644); err != nil { + if err := createSourceFile(sourceFilePath, "local file content"); err != nil { t.Fatal(err) } - defer os.Remove("source_file") - config := ` - resource "local_sensitive_file" "file" { - source = "source_file" - filename = "new_file" - } - ` + destinationDirPath := t.TempDir() + destinationFilePath := filepath.Join(destinationDirPath, "new_file") + destinationFilePath = strings.ReplaceAll(destinationFilePath, `\`, `\\`) r.UnitTest(t, r.TestCase{ - Providers: testProviders, + ProtoV5ProviderFactories: protoV5ProviderFactories(), Steps: []r.TestStep{ { - Config: config, - Check: func(s *terraform.State) error { - content, err := ioutil.ReadFile("new_file") - if err != nil { - return fmt.Errorf("config:\n%s\n,got: %s\n", config, err) - } - if string(content) != source_content { - return fmt.Errorf("config:\n%s\ngot:\n%s\nwant:\n%s\n", config, content, source_content) - } - return nil - }, + Config: testAccConfigLocalSensitiveSourceFile(sourceFilePath, destinationFilePath), + Check: checkFileCreation("local_sensitive_file_resource.test", destinationFilePath), }, }, - CheckDestroy: checkFileDeleted("new_file"), + CheckDestroy: checkFileDeleted(destinationFilePath), }) } @@ -117,60 +64,218 @@ func TestLocalSensitiveFile_Permissions(t *testing.T) { destinationDirPath := t.TempDir() destinationFilePath := filepath.Join(destinationDirPath, "local_sensitive_file") destinationFilePath = strings.ReplaceAll(destinationFilePath, `\`, `\\`) - filePermission := os.FileMode(0600) - directoryPermission := os.FileMode(0700) - skipDirCheck := false - config := fmt.Sprintf(` - resource "local_sensitive_file" "file" { - content = "This is some content" - filename = "%s" - file_permission = "0600" - directory_permission = "0700" - }`, destinationFilePath, - ) + isDirExist := false r.UnitTest(t, r.TestCase{ - Providers: testProviders, + ProtoV5ProviderFactories: protoV5ProviderFactories(), Steps: []r.TestStep{ { - Config: config, - PreConfig: func() { - // if directory already existed prior to check, skip check - if _, err := os.Stat(path.Dir(destinationFilePath)); !os.IsNotExist(err) { - skipDirCheck = true - } - }, - Check: func(s *terraform.State) error { - if runtime.GOOS == "windows" { - // skip all checks if windows - return nil - } + PreConfig: checkDirExists(destinationDirPath, &isDirExist), + SkipFunc: skipTestsWindows(), + Config: fmt.Sprintf(` + resource "local_sensitive_file" "file" { + content = "This is some content" + filename = "%s" + file_permission = "0600" + directory_permission = "0700" + }`, destinationFilePath, + ), + Check: r.ComposeTestCheckFunc( + checkFilePermissions(destinationFilePath), + checkDirectoryPermissions(destinationFilePath), + ), + }, + }, + ErrorCheck: func(err error) error { + if match, _ := regexp.MatchString("Directory permission.", err.Error()); match && isDirExist { + return nil + } + return err + }, + CheckDestroy: checkFileDeleted(destinationFilePath), + }) +} - fileInfo, err := os.Stat(destinationFilePath) - if err != nil { - return fmt.Errorf("config:\n%s\ngot:%s\n", config, err) - } +func TestLocalSensitiveFile_Validators(t *testing.T) { + f := filepath.Join(t.TempDir(), "local_file") + f = strings.ReplaceAll(f, `\`, `\\`) - if fileInfo.Mode() != filePermission { - return fmt.Errorf( - "File permission.\nconfig:\n%s\nexpected:%s\ngot: %s\n", - config, filePermission, fileInfo.Mode()) - } + r.UnitTest(t, r.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + CheckDestroy: nil, + Steps: []r.TestStep{ + { + Config: fmt.Sprintf(` + resource "local_sensitive_file" "file" { + filename = "%s" + }`, f), + ExpectError: regexp.MustCompile(`.*Error: Invalid Attribute Combination`), + }, + { + Config: fmt.Sprintf(` + resource "local_sensitive_file" "file" { + content = "content" + content_base64 = "VGhpcyBpcyBzb21lIGJhc2U2NCBjb250ZW50" + filename = "%s" + }`, f), + ExpectError: regexp.MustCompile(`.*Error: Invalid Attribute Combination`), + }, + }, + }) +} + +func TestLocalSensitiveFile_Upgrade(t *testing.T) { + f := filepath.Join(t.TempDir(), "local_sensitive_file") + f = strings.ReplaceAll(f, `\`, `\\`) - if !skipDirCheck { - dirInfo, _ := os.Stat(path.Dir(destinationFilePath)) - // we have to use FileMode.Perm() here, otherwise directory bit causes issues - if dirInfo.Mode().Perm() != directoryPermission.Perm() { - return fmt.Errorf( - "Directory permission.\nconfig:\n%s\nexpected:%s\ngot: %s\n", - config, directoryPermission, dirInfo.Mode().Perm()) - } + r.Test(t, r.TestCase{ + Steps: []r.TestStep{ + { + ExternalProviders: providerVersion233(), + Config: testAccConfigLocalSensitiveFileContent("This is some content", f), + Check: checkFileCreation("local_sensitive_file_resource.test", f), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: testAccConfigLocalSensitiveFileContent("This is some content", f), + PlanOnly: true, + }, + { + ExternalProviders: providerVersion233(), + Config: testAccConfigLocalSensitiveFileEncodedBase64Content("VGhpcyBpcyBzb21lIGJhc2U2NCBjb250ZW50", f), + Check: checkFileCreation("local_sensitive_file_resource.test", f), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: testAccConfigLocalSensitiveFileEncodedBase64Content("VGhpcyBpcyBzb21lIGJhc2U2NCBjb250ZW50", f), + PlanOnly: true, + }, + { + ExternalProviders: providerVersion233(), + Config: testAccConfigLocalSensitiveFileDecodedBase64Content("This is some base64 content", f), + Check: checkFileCreation("local_sensitive_file_resource.test", f), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: testAccConfigLocalSensitiveFileDecodedBase64Content("This is some base64 content", f), + PlanOnly: true, + }, + }, + CheckDestroy: checkFileDeleted(f), + }) +} + +func TestLocalSensitiveFile_Source_Upgrade(t *testing.T) { + // create a local file that will be used as the "source" file + if err := os.WriteFile("./testdata/source_file", []byte("sourceContent"), 0644); err != nil { + t.Fatal(err) + } + defer os.Remove("./testdata/source_file") + + r.Test(t, r.TestCase{ + Steps: []r.TestStep{ + { + ExternalProviders: providerVersion233(), + Config: ` + resource "local_sensitive_file" "file" { + source = "./testdata/source_file" + filename = "./testdata/new_file" + } + `, + Check: checkFileCreation("local_sensitive_file_resource.test", "./testdata/new_file"), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: ` + resource "local_sensitive_file" "file" { + source = "./testdata/source_file" + filename = "./testdata/new_file" } + `, + PlanOnly: true, + }, + }, + CheckDestroy: checkFileDeleted("new_file"), + }) +} - return nil - }, +func TestLocalSensitiveFile_Permissions_Upgrade(t *testing.T) { + destinationDirPath := t.TempDir() + destinationFilePath := filepath.Join(destinationDirPath, "local_sensitive_file") + destinationFilePath = strings.ReplaceAll(destinationFilePath, `\`, `\\`) + isDirExist := false + + r.Test(t, r.TestCase{ + Steps: []r.TestStep{ + { + ExternalProviders: providerVersion233(), + SkipFunc: skipTestsWindows(), + PreConfig: checkDirExists(destinationDirPath, &isDirExist), + Config: fmt.Sprintf(` + resource "local_sensitive_file" "file" { + content = "This is some content" + filename = "%s" + file_permission = "0600" + directory_permission = "0700" + }`, destinationFilePath, + ), + Check: r.ComposeTestCheckFunc( + checkFilePermissions(destinationFilePath), + checkDirectoryPermissions(destinationFilePath), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + SkipFunc: skipTestsWindows(), + Config: fmt.Sprintf(` + resource "local_sensitive_file" "file" { + content = "This is some content" + filename = "%s" + file_permission = "0600" + directory_permission = "0700" + }`, destinationFilePath, + ), + PlanOnly: true, }, }, + ErrorCheck: func(err error) error { + if match, _ := regexp.MatchString("Directory permission.", err.Error()); match && isDirExist { + return nil + } + return err + }, CheckDestroy: checkFileDeleted(destinationFilePath), }) } + +func testAccConfigLocalSensitiveSourceFile(source, filename string) string { + return fmt.Sprintf(` + resource "local_sensitive_file" "file" { + source = %[1]q + filename = %[2]q + }`, source, filename) +} + +func testAccConfigLocalSensitiveFileContent(content, filename string) string { + return fmt.Sprintf(` + resource "local_sensitive_file" "file" { + content = %[1]q + filename = %[2]q + }`, content, filename) +} + +func testAccConfigLocalSensitiveFileEncodedBase64Content(content, filename string) string { + return fmt.Sprintf(` + resource "local_sensitive_file" "file" { + content_base64 = %[1]q + filename = %[2]q + }`, content, filename) +} + +func testAccConfigLocalSensitiveFileDecodedBase64Content(content, filename string) string { + return fmt.Sprintf(` + resource "local_sensitive_file" "file" { + content_base64 = base64encode(%[1]q) + filename = %[2]q + }`, content, filename) +} diff --git a/internal/provider/validator.go b/internal/provider/validator.go deleted file mode 100644 index 6bb33554..00000000 --- a/internal/provider/validator.go +++ /dev/null @@ -1,30 +0,0 @@ -package provider - -import ( - "fmt" - "strconv" -) - -// validateModePermission checks that the given input string is a valid file permission string, -// expressed in numeric notation. -// See: https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation -func validateModePermission(i interface{}, k string) (s []string, es []error) { - v, ok := i.(string) - if !ok { - es = append(es, fmt.Errorf("bad mode permission: expected type of '%s' to be string", k)) - return - } - - if len(v) < 3 || len(v) > 4 { - es = append(es, fmt.Errorf("bad mode permission: string length should be 3 or 4 digits: %s", v)) - return - } - - fileMode, err := strconv.ParseInt(v, 8, 64) - if err != nil || fileMode > 0777 || fileMode < 0 { - es = append(es, fmt.Errorf("bad mode permission: string must be expressed in octal numeric notation: %s", v)) - return - } - - return -} diff --git a/main.go b/main.go index f012069d..4217bd35 100644 --- a/main.go +++ b/main.go @@ -1,11 +1,30 @@ package main import ( - "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" + "context" + "flag" + "log" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/terraform-providers/terraform-provider-local/internal/provider" ) +// Generate the Terraform provider documentation using `tfplugindocs`: +//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs + func main() { - plugin.Serve(&plugin.ServeOpts{ - ProviderFunc: provider.New}) + var debug bool + + flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") + flag.Parse() + + err := providerserver.Serve(context.Background(), provider.New, providerserver.ServeOpts{ + Address: "registry.terraform.io/hashicorp/local", + Debug: debug, + ProtocolVersion: 5, + }) + if err != nil { + log.Fatal(err) + } } diff --git a/templates/data-sources/file.md.tmpl b/templates/data-sources/file.md.tmpl new file mode 100644 index 00000000..6f4cd09f --- /dev/null +++ b/templates/data-sources/file.md.tmpl @@ -0,0 +1,16 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example Usage + +{{ tffile "examples/data-sources/data-source-file.tf" }} + +{{ .SchemaMarkdown | trimspace }} \ No newline at end of file diff --git a/templates/data-sources/sensitive_file.md.tmpl b/templates/data-sources/sensitive_file.md.tmpl new file mode 100644 index 00000000..8618d544 --- /dev/null +++ b/templates/data-sources/sensitive_file.md.tmpl @@ -0,0 +1,19 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +The attributes exposed by this data source are marked as +[sensitive](https://learn.hashicorp.com/tutorials/terraform/sensitive-variables). + +## Example Usage + +{{ tffile "examples/data-sources/data-source-sensitive-file.tf" }} + +{{ .SchemaMarkdown | trimspace }} \ No newline at end of file diff --git a/templates/index.md.tmpl b/templates/index.md.tmpl new file mode 100644 index 00000000..8cde602a --- /dev/null +++ b/templates/index.md.tmpl @@ -0,0 +1,18 @@ +--- +page_title: "Provider: Local" +description: |- + The Local provider is used to manage local resources, such as files. +--- + +# Local Provider + +The Local provider is used to manage local resources, such as files. + +Use the navigation to the left to read about the available resources. + +~> **Note** Terraform primarily deals with remote resources which are able +to outlive a single Terraform run, and so local resources can sometimes violate +its assumptions. The resources here are best used with care, since depending +on local state can make it hard to apply the same Terraform configuration on +many different local systems where the local resources may not be universally +available. See specific notes in each resource for more information. \ No newline at end of file diff --git a/templates/resources/file.md.tmpl b/templates/resources/file.md.tmpl new file mode 100644 index 00000000..d2c24a7a --- /dev/null +++ b/templates/resources/file.md.tmpl @@ -0,0 +1,30 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +~> **Note about resource behaviour** +When working with local files, Terraform will detect the resource +as having been deleted each time a configuration is applied on a new machine +where the file is not present and will generate a diff to re-create it. This +may cause "noise" in diffs in environments where configurations are routinely +applied by many different users or within automation systems. + +~> **Note about file content** +File content must be specified with _exactly_ one of the arguments `content`, +`sensitive_content` (Deprecated), `content_base64`, or `source`. + +-> If the file content is sensitive, use the +[`local_sensitive_file`](./sensitive_file.html) resource instead. + +## Example Usage + +{{ tffile "examples/resources/resource-file.tf" }} + +{{ .SchemaMarkdown | trimspace }} \ No newline at end of file diff --git a/templates/resources/sensitive_file.md.tmpl b/templates/resources/sensitive_file.md.tmpl new file mode 100644 index 00000000..cd59e708 --- /dev/null +++ b/templates/resources/sensitive_file.md.tmpl @@ -0,0 +1,30 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +The arguments accepted by this resource are marked as +[sensitive](https://learn.hashicorp.com/tutorials/terraform/sensitive-variables). + +~> **Note about resource behaviour** +When working with local files, Terraform will detect the resource +as having been deleted each time a configuration is applied on a new machine +where the file is not present and will generate a diff to re-create it. This +may cause "noise" in diffs in environments where configurations are routinely +applied by many different users or within automation systems. + +~> **Note about file content** +File content must be specified with _exactly_ one of the arguments `content`, +`content_base64`, or `source`. + +## Example Usage + +{{ tffile "examples/resources/resource-sensitive-file.tf" }} + +{{ .SchemaMarkdown | trimspace }} \ No newline at end of file diff --git a/tools/tools.go b/tools/tools.go new file mode 100644 index 00000000..8abc7c8d --- /dev/null +++ b/tools/tools.go @@ -0,0 +1,9 @@ +//go:build tools +// +build tools + +package tools + +import ( + // document generation + _ "github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs" +) diff --git a/website/docs/d/file.html.md b/website/docs/d/file.html.md deleted file mode 100644 index 55bda9bc..00000000 --- a/website/docs/d/file.html.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -layout: "local" -page_title: "Local: local_file" -description: |- - Reads a file from the local filesystem. ---- - -# local_file - -`local_file` reads a file from the local filesystem. - -## Example Usage - -```hcl -data "local_file" "foo" { - filename = "${path.module}/foo.bar" -} - -resource "aws_s3_bucket_object" "shared_zip" { - bucket = "my-bucket" - key = "my-key" - content = data.local_file.foo.content -} -``` - -## Argument Reference - -The following arguments are supported: - -* `filename` - (Required) Path to the file that will be read. - The data source will return an error if the file does not exist. - -## Attributes Exported - -The following attribute is exported: - -* `content` - Raw content of the file that was read, assumed by Terraform to be UTF-8 encoded string. - Files that do not contain UTF-8 text will have invalid UTF-8 sequences in `content` - replaced with the Unicode replacement character. - -* `content_base64` - Base64 encoded version of the file content. - Use this when dealing with binary data. diff --git a/website/docs/d/sensitive_file.html.md b/website/docs/d/sensitive_file.html.md deleted file mode 100644 index f1003e33..00000000 --- a/website/docs/d/sensitive_file.html.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -layout: "local" -page_title: "Local: local_sensitive_file" -description: |- - Reads a file that contains sensitive data, from the local filesystem. ---- - -# local_sensitive_file - -`local_sensitive_file` reads a file that contains sensitive data, from the local filesystem. -The attributes exposed by this data source are marked as -[sensitive](https://learn.hashicorp.com/tutorials/terraform/sensitive-variables). - -## Example Usage - -```hcl -data "local_sensitive_file" "foo" { - filename = "${path.module}/foo.bar" -} - -resource "aws_s3_bucket_object" "shared_zip" { - bucket = "my-bucket" - key = "my-key" - content = data.local_sensitive_file.foo.content -} -``` - -## Argument Reference - -The following arguments are supported: - -* `filename` - (Required) Path to the file that will be read. - The data source will return an error if the file does not exist. - -## Attributes Exported - -The following attribute is exported: - -* `content` - Raw content of the file that was read, assumed by Terraform to be UTF-8 encoded string. - Files that do not contain UTF-8 text will have invalid UTF-8 sequences in `content` - replaced with the Unicode replacement character. - -* `content_base64` - Base64 encoded version of the file content. - Use this when dealing with binary data. diff --git a/website/docs/r/file.html.md b/website/docs/r/file.html.md deleted file mode 100644 index 8e876fe9..00000000 --- a/website/docs/r/file.html.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -layout: "local" -page_title: "Local: local_file" -description: |- - Generates a local file from content. ---- - -# local_file - -Generates a local file with the given content. - -~> **Note about resource behaviour** -When working with local files, Terraform will detect the resource -as having been deleted each time a configuration is applied on a new machine -where the file is not present and will generate a diff to re-create it. This -may cause "noise" in diffs in environments where configurations are routinely -applied by many different users or within automation systems. - -~> **Note about file content** -File content must be specified with _exactly_ one of the arguments `content`, -`sensitive_content` (Deprecated), `content_base64`, or `source`. - --> If the file content is sensitive, use the -[`local_sensitive_file`](./sensitive_file.html) resource instead. - -## Example Usage - -```hcl -resource "local_file" "foo" { - content = "foo!" - filename = "${path.module}/foo.bar" -} -``` - -## Argument Reference - -The following arguments are supported: - -* `filename` - (Required) The path to the file that will be created. - Missing parent directories will be created. - If the file already exists, it will be overridden with the given content. - -* `content` - (Optional) Content to store in the file, expected to be an UTF-8 encoded string. - Conflicts with `sensitive_content`, `content_base64` and `source`. - Exactly one of these four arguments must be specified. - -* `sensitive_content` - (Optional - Deprecated) Sensitive content to store in the file, expected to be an UTF-8 encoded string. - Will not be displayed in diffs. - Conflicts with `content`, `content_base64` and `source`. - Exactly one of these four arguments must be specified. - If in need to use _sensitive_ content, please use the [`local_sensitive_file`](./sensitive_file.html) - resource instead. - -* `content_base64` - (Optional) Content to store in the file, expected to be binary encoded as base64 string. - Conflicts with `content`, `sensitive_content` and `source`. - Exactly one of these four arguments must be specified. - -* `source` - (Optional) Path to file to use as source for the one we are creating. - Conflicts with `content`, `sensitive_content` and `content_base64`. - Exactly one of these four arguments must be specified. - -* `file_permission` - (Optional) Permissions to set for the output file, expressed as string in - [numeric notation](https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation). - Default value is `"0777"`. - -* `directory_permission` - (Optional) Permissions to set for directories created, expressed as string in - [numeric notation](https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation). - Default value is `"0777"`. diff --git a/website/docs/r/sensitive_file.html.md b/website/docs/r/sensitive_file.html.md deleted file mode 100644 index 40e0efb5..00000000 --- a/website/docs/r/sensitive_file.html.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -layout: "local" -page_title: "Local: local_sensitive_file" -description: |- - Generates a local file with the given sensitive content. ---- - -# local_sensitive_file - -Generates a local file with the given sensitive content. -The arguments accepted by this resource are marked as -[sensitive](https://learn.hashicorp.com/tutorials/terraform/sensitive-variables). - -~> **Note about resource behaviour** -When working with local files, Terraform will detect the resource -as having been deleted each time a configuration is applied on a new machine -where the file is not present and will generate a diff to re-create it. This -may cause "noise" in diffs in environments where configurations are routinely -applied by many different users or within automation systems. - -~> **Note about file content** -File content must be specified with _exactly_ one of the arguments `content`, -`content_base64`, or `source`. - -## Example Usage - -```hcl -resource "local_sensitive_file" "foo" { - content = "foo!" - filename = "${path.module}/foo.bar" -} -``` - -## Argument Reference - -The following arguments are supported: - -* `filename` - (Required) The path to the file that will be created. - Missing parent directories will be created. - If the file already exists, it will be overridden with the given content. - -* `content` - (Optional) Sensitive content to store in the file, expected to be an UTF-8 encoded string. - Conflicts with `content_base64` and `source`. - Exactly one of these three arguments must be specified. - -* `content_base64` - (Optional) Sensitive content to store in the file, expected to be binary encoded as base64 string. - Conflicts with `content` and `source`. - Exactly one of these three arguments must be specified. - -* `source` - (Optional) Path to file to use as source for the one we are creating. - Conflicts with `content` and `content_base64`. - Exactly one of these three arguments must be specified. - -* `file_permission` - (Optional) Permissions to set for the output file, expressed as string in - [numeric notation](https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation). - Default value is `"0700"`. - -* `directory_permission` - (Optional) Permissions to set for directories created, expressed as string in - [numeric notation](https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation). - Default value is `"0700"`.