├── .github ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── dependabot.yml └── workflows │ ├── codeql.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── CHANGELOG.md ├── GNUmakefile ├── LICENSE ├── README.md ├── docs ├── functions │ └── mergo.md └── index.md ├── examples ├── README.md └── provider │ └── provider.tf ├── go.mod ├── go.sum ├── internal ├── helpers │ ├── decode.go │ ├── decode_test.go │ ├── encode.go │ ├── encode_test.go │ └── mergo.go └── provider │ ├── mergo_function.go │ ├── mergo_function_test.go │ ├── provider.go │ └── provider_test.go ├── main.go ├── terraform-registry-manifest.json └── tools └── tools.go /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @hashicorp/terraform-devex 2 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | HashiCorp Community Guidelines apply to you when interacting with the community here on GitHub and contributing code. 4 | 5 | Please read the full text at https://www.hashicorp.com/community-guidelines 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # See GitHub's documentation for more information on this file: 2 | # https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates 3 | version: 2 4 | updates: 5 | - package-ecosystem: gomod 6 | directory: / 7 | schedule: 8 | interval: weekly 9 | - package-ecosystem: github-actions 10 | directory: / 11 | schedule: 12 | interval: weekly 13 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: ["main"] 9 | schedule: 10 | - cron: "47 23 * * 5" 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | permissions: 17 | actions: read 18 | contents: read 19 | security-events: write 20 | 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v4 24 | 25 | - name: Initialize CodeQL 26 | uses: github/codeql-action/init@v3 27 | with: 28 | languages: go 29 | 30 | - name: Autobuild 31 | uses: github/codeql-action/autobuild@v3 32 | 33 | - name: Perform CodeQL Analysis 34 | uses: github/codeql-action/analyze@v3 35 | with: 36 | category: "/language:go" 37 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # Terraform Provider release workflow. 2 | name: Release 3 | 4 | # This GitHub action creates a release when a tag that matches the pattern 5 | # "v*" (e.g. v0.1.0) is created. 6 | on: 7 | push: 8 | tags: 9 | - "v*" 10 | 11 | # Releases need permissions to read and write the repository contents. 12 | # GitHub considers creating releases and uploading assets as writing contents. 13 | permissions: 14 | contents: write 15 | 16 | jobs: 17 | goreleaser: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 21 | with: 22 | # Allow goreleaser to access older tag information. 23 | fetch-depth: 0 24 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 25 | with: 26 | go-version-file: "go.mod" 27 | cache: true 28 | - name: Import GPG key 29 | uses: crazy-max/ghaction-import-gpg@e89d40939c28e39f97cf32126055eeae86ba74ec # v6.3.0 30 | id: import_gpg 31 | with: 32 | gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} 33 | passphrase: ${{ secrets.PASSPHRASE }} 34 | - name: Run GoReleaser 35 | uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 36 | with: 37 | args: release --clean 38 | env: 39 | # GitHub sets the GITHUB_TOKEN secret automatically. 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} 42 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # Terraform Provider testing workflow. 2 | name: Tests 3 | 4 | # This GitHub action runs your tests for each pull request and push. 5 | # Optionally, you can turn it on using a schedule for regular testing. 6 | on: 7 | pull_request: 8 | paths-ignore: 9 | - "README.md" 10 | push: 11 | paths-ignore: 12 | - "README.md" 13 | 14 | # Testing only needs permissions to read the repository contents. 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | # Ensure project builds before running testing matrix 20 | build: 21 | name: Build 22 | runs-on: ubuntu-latest 23 | timeout-minutes: 5 24 | steps: 25 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 26 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 27 | with: 28 | go-version-file: "go.mod" 29 | cache: true 30 | - run: go mod download 31 | - run: go build -v . 32 | - name: Run linters 33 | uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 34 | with: 35 | version: latest 36 | 37 | generate: 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 41 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 42 | with: 43 | go-version-file: "go.mod" 44 | cache: true 45 | - uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 46 | with: 47 | terraform_version: 1.9.* 48 | terraform_wrapper: false 49 | - run: go generate ./... 50 | - name: git diff 51 | run: | 52 | git diff --compact-summary --exit-code || \ 53 | (echo; echo "Unexpected difference in directories after code generation. Run 'go generate ./...' command and commit."; exit 1) 54 | 55 | # Run acceptance tests in a matrix with Terraform CLI versions 56 | test: 57 | name: Terraform Provider Acceptance Tests 58 | needs: build 59 | runs-on: ubuntu-latest 60 | timeout-minutes: 15 61 | strategy: 62 | fail-fast: false 63 | matrix: 64 | # list whatever Terraform versions here you would like to support 65 | terraform: 66 | - "1.8.*" 67 | - "1.9.*" 68 | - "1.10.*" 69 | - "1.11.*" 70 | steps: 71 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 72 | - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 73 | with: 74 | go-version-file: "go.mod" 75 | cache: true 76 | - uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 77 | with: 78 | terraform_version: ${{ matrix.terraform }} 79 | terraform_wrapper: false 80 | - run: go mod download 81 | - env: 82 | TF_ACC: "1" 83 | run: go test -v -cover ./internal/provider/ 84 | timeout-minutes: 10 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.dll 2 | *.exe 3 | .DS_Store 4 | example.tf 5 | terraform.tfplan 6 | terraform.tfstate 7 | bin/ 8 | dist/ 9 | modules-dev/ 10 | /pkg/ 11 | website/.vagrant 12 | website/.bundle 13 | website/build 14 | website/node_modules 15 | .vagrant/ 16 | *.backup 17 | ./*.tfstate 18 | .terraform/ 19 | *.log 20 | *.bak 21 | *~ 22 | .*.swp 23 | .idea 24 | *.iml 25 | *.test 26 | *.iml 27 | 28 | website/vendor 29 | 30 | # Test exclusions 31 | !command/test-fixtures/**/*.tfstate 32 | !command/test-fixtures/**/.terraform/ 33 | 34 | # Keep windows files with windows line endings 35 | *.winfile eol=crlf 36 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | default: none 4 | enable: 5 | - copyloopvar 6 | - durationcheck 7 | - errcheck 8 | - forcetypeassert 9 | - godot 10 | - govet 11 | - ineffassign 12 | - makezero 13 | - misspell 14 | - nilerr 15 | - predeclared 16 | - staticcheck 17 | - unconvert 18 | - unparam 19 | - unused 20 | - usetesting 21 | exclusions: 22 | generated: lax 23 | presets: 24 | - comments 25 | - common-false-positives 26 | - legacy 27 | - std-error-handling 28 | paths: 29 | - third_party$ 30 | - builtin$ 31 | - examples$ 32 | issues: 33 | max-same-issues: 0 34 | formatters: 35 | enable: 36 | - gofmt 37 | exclusions: 38 | generated: lax 39 | paths: 40 | - third_party$ 41 | - builtin$ 42 | - examples$ 43 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | before: 4 | hooks: 5 | - go mod tidy 6 | builds: 7 | - env: 8 | - CGO_ENABLED=0 9 | mod_timestamp: "{{ .CommitTimestamp }}" 10 | flags: 11 | - -trimpath 12 | ldflags: 13 | - "-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}" 14 | goos: 15 | - freebsd 16 | - windows 17 | - linux 18 | - darwin 19 | goarch: 20 | - amd64 21 | - "386" 22 | - arm 23 | - arm64 24 | ignore: 25 | - goos: darwin 26 | goarch: "386" 27 | binary: "{{ .ProjectName }}_v{{ .Version }}" 28 | archives: 29 | - formats: [zip] 30 | name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" 31 | checksum: 32 | extra_files: 33 | - glob: "terraform-registry-manifest.json" 34 | name_template: "{{ .ProjectName }}_{{ .Version }}_manifest.json" 35 | name_template: "{{ .ProjectName }}_{{ .Version }}_SHA256SUMS" 36 | algorithm: sha256 37 | signs: 38 | - artifacts: checksum 39 | args: 40 | # if you are using this in a GitHub action or some other automated pipeline, you 41 | # need to pass the batch flag to indicate its not interactive. 42 | - "--batch" 43 | - "--local-user" 44 | - "{{ .Env.GPG_FINGERPRINT }}" # set this environment variable for your signing key 45 | - "--output" 46 | - "${signature}" 47 | - "--detach-sign" 48 | - "${artifact}" 49 | release: 50 | extra_files: 51 | - glob: "terraform-registry-manifest.json" 52 | name_template: "{{ .ProjectName }}_{{ .Version }}_manifest.json" 53 | # If you want to manually examine the release before its live, uncomment this line: 54 | # draft: true 55 | changelog: 56 | disable: true 57 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [1.0.0] - 2025-03-20 6 | 7 | ### 🚀 Features 8 | 9 | - v1.0.0 release 10 | 11 | ### 💼 Other 12 | 13 | - *(deps)* Bump golangci/golangci-lint-action from 6.5.0 to 6.5.1 (#74) 14 | 15 | ## [0.4.2] - 2025-03-09 16 | 17 | ### 💼 Other 18 | 19 | - *(deps)* Bump github.com/hashicorp/terraform-plugin-docs from 0.20.1 to 0.21.0 (#73) 20 | 21 | ### 🚜 Refactor 22 | 23 | - Enhance argument validation (#72) 24 | 25 | ## [0.4.1] - 2025-03-02 26 | 27 | ### 🐛 Bug Fixes 28 | 29 | - Handle typed nulls (#71) 30 | 31 | ## [0.4.0] - 2025-03-02 32 | 33 | ### 🚀 Features 34 | 35 | - Allow (and ignore) nulls (#70) 36 | 37 | ### 💼 Other 38 | 39 | - *(deps)* Bump golangci/golangci-lint-action from 6.2.0 to 6.3.1 (#65) 40 | 41 | ## [0.3.3] - 2025-01-31 42 | 43 | ### 💼 Other 44 | 45 | - *(deps)* Bump github.com/hashicorp/terraform-plugin-testing from 1.10.0 to 1.11.0 (#56) 46 | - *(deps)* Bump github.com/stretchr/testify from 1.9.0 to 1.10.0 (#57) 47 | - *(deps)* Bump github.com/hashicorp/terraform-plugin-docs from 0.20.0 to 0.20.1 (#58) 48 | - *(deps)* Bump golang.org/x/crypto from 0.29.0 to 0.31.0 in the go_modules group (#59) 49 | - *(deps)* Bump actions/setup-go from 5.1.0 to 5.2.0 (#60) 50 | - *(deps)* Bump golangci/golangci-lint-action from 6.1.1 to 6.2.0 (#61) 51 | - *(deps)* Bump golang.org/x/net from 0.32.0 to 0.33.0 in the go_modules group (#62) 52 | - *(deps)* Bump actions/setup-go from 5.2.0 to 5.3.0 (#63) 53 | - *(deps)* Bump github.com/hashicorp/terraform-plugin-go from 0.25.0 to 0.26.0 (#64) 54 | 55 | ## [0.3.2] - 2024-11-10 56 | 57 | ### 💼 Other 58 | 59 | - *(deps)* Bump github.com/hashicorp/terraform-plugin-go from 0.24.0 to 0.25.0 (#51) 60 | - *(deps)* Bump github.com/hashicorp/terraform-plugin-framework from 1.12.0 to 1.13.0 (#52) 61 | - *(deps)* Bump goreleaser/goreleaser-action from 6.0.0 to 6.1.0 (#54) 62 | - *(deps)* Bump github.com/stretchr/testify from 1.8.3 to 1.9.0 (#55) 63 | 64 | ### 📚 Documentation 65 | 66 | - Improve example usage 67 | 68 | ### 🧪 Testing 69 | 70 | - Add basic encode/decode tests 71 | 72 | ### ⚙️ Miscellaneous Tasks 73 | 74 | - Add CodeQL scans and badges 75 | 76 | ## [0.3.1] - 2024-10-30 77 | 78 | ### 🐛 Bug Fixes 79 | 80 | - OpenTofu crash with unknown value (#50) 81 | 82 | ### 💼 Other 83 | 84 | - *(deps)* Bump actions/checkout from 4.2.1 to 4.2.2 (#46) 85 | - *(deps)* Bump actions/setup-go from 5.0.2 to 5.1.0 (#47) 86 | - *(deps)* Bump crazy-max/ghaction-import-gpg from 6.1.0 to 6.2.0 (#49) 87 | 88 | ### 📚 Documentation 89 | 90 | - Document mergo configuration options 91 | 92 | ## [0.3.0] - 2024-10-19 93 | 94 | ### 💼 Other 95 | 96 | - *(deps)* Bump golangci/golangci-lint-action from 6.1.0 to 6.1.1 (#43) 97 | - *(deps)* Bump actions/checkout from 4.2.0 to 4.2.1 (#44) 98 | 99 | ## [0.2.3] - 2024-09-28 100 | 101 | ### 💼 Other 102 | 103 | - *(deps)* Bump google.golang.org/grpc in the go_modules group 104 | - *(deps)* Bump github.com/hashicorp/terraform-plugin-testing 105 | - *(deps)* Bump github.com/hashicorp/terraform-plugin-framework 106 | - *(deps)* Bump actions/setup-go from 5.0.1 to 5.0.2 107 | - *(deps)* Bump golangci/golangci-lint-action from 6.0.1 to 6.1.0 108 | - *(deps)* Bump github.com/hashicorp/terraform-plugin-testing 109 | - *(deps)* Bump github.com/hashicorp/terraform-plugin-framework 110 | - *(deps)* Bump dario.cat/mergo from 1.0.0 to 1.0.1 111 | - *(deps)* Bump hashicorp/setup-terraform from 3.1.1 to 3.1.2 112 | - *(deps)* Bump actions/checkout from 4.1.7 to 4.2.0 (#42) 113 | - *(deps)* Bump all 114 | 115 | ## [0.2.2] - 2024-06-13 116 | 117 | ### 💼 Other 118 | 119 | - *(deps)* Bump golangci/golangci-lint-action from 5.1.0 to 5.3.0 120 | - *(deps)* Bump golangci/golangci-lint-action from 5.3.0 to 6.0.0 121 | - *(deps)* Bump actions/checkout from 4.1.4 to 4.1.5 122 | - *(deps)* Bump golangci/golangci-lint-action from 6.0.0 to 6.0.1 123 | - *(deps)* Bump hashicorp/setup-terraform from 3.1.0 to 3.1.1 124 | - *(deps)* Bump github.com/hashicorp/terraform-plugin-go 125 | - *(deps)* Bump goreleaser/goreleaser-action from 5.0.0 to 5.1.0 126 | - *(deps)* Bump actions/checkout from 4.1.5 to 4.1.6 127 | - *(deps)* Bump github.com/hashicorp/go-version from 1.6.0 to 1.7.0 128 | - *(deps)* Bump github.com/hashicorp/terraform-plugin-docs 129 | - *(deps)* Bump github.com/hashicorp/terraform-plugin-framework 130 | - *(deps)* Bump github.com/hashicorp/terraform-plugin-docs 131 | - *(deps)* Bump actions/checkout from 4.1.6 to 4.1.7 132 | - *(deps)* Bump goreleaser/goreleaser-action from 5.1.0 to 6.0.0 133 | - Update goreleaser spec 134 | - *(deps)* Bump all 135 | 136 | ## [0.2.1] - 2024-05-04 137 | 138 | ### 🐛 Bug Fixes 139 | 140 | - Build/tests 141 | 142 | ## [0.2.0] - 2024-05-04 143 | 144 | ### 🚀 Features 145 | 146 | - *(mergo)* Direct translation of terraform values 147 | 148 | 149 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | default: testacc 2 | 3 | # Run acceptance tests 4 | .PHONY: testacc 5 | testacc: 6 | TF_ACC=1 go test ./... -v $(TESTARGS) -timeout 120m 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 Robin Breathe 2 | 3 | Mozilla Public License Version 2.0 4 | ================================== 5 | 6 | 1. Definitions 7 | -------------- 8 | 9 | 1.1. "Contributor" 10 | means each individual or legal entity that creates, contributes to 11 | the creation of, or owns Covered Software. 12 | 13 | 1.2. "Contributor Version" 14 | means the combination of the Contributions of others (if any) used 15 | by a Contributor and that particular Contributor's Contribution. 16 | 17 | 1.3. "Contribution" 18 | means Covered Software of a particular Contributor. 19 | 20 | 1.4. "Covered Software" 21 | means Source Code Form to which the initial Contributor has attached 22 | the notice in Exhibit A, the Executable Form of such Source Code 23 | Form, and Modifications of such Source Code Form, in each case 24 | including portions thereof. 25 | 26 | 1.5. "Incompatible With Secondary Licenses" 27 | means 28 | 29 | (a) that the initial Contributor has attached the notice described 30 | in Exhibit B to the Covered Software; or 31 | 32 | (b) that the Covered Software was made available under the terms of 33 | version 1.1 or earlier of the License, but not also under the 34 | terms of a Secondary License. 35 | 36 | 1.6. "Executable Form" 37 | means any form of the work other than Source Code Form. 38 | 39 | 1.7. "Larger Work" 40 | means a work that combines Covered Software with other material, in 41 | a separate file or files, that is not Covered Software. 42 | 43 | 1.8. "License" 44 | means this document. 45 | 46 | 1.9. "Licensable" 47 | means having the right to grant, to the maximum extent possible, 48 | whether at the time of the initial grant or subsequently, any and 49 | all of the rights conveyed by this License. 50 | 51 | 1.10. "Modifications" 52 | means any of the following: 53 | 54 | (a) any file in Source Code Form that results from an addition to, 55 | deletion from, or modification of the contents of Covered 56 | Software; or 57 | 58 | (b) any new file in Source Code Form that contains any Covered 59 | Software. 60 | 61 | 1.11. "Patent Claims" of a Contributor 62 | means any patent claim(s), including without limitation, method, 63 | process, and apparatus claims, in any patent Licensable by such 64 | Contributor that would be infringed, but for the grant of the 65 | License, by the making, using, selling, offering for sale, having 66 | made, import, or transfer of either its Contributions or its 67 | Contributor Version. 68 | 69 | 1.12. "Secondary License" 70 | means either the GNU General Public License, Version 2.0, the GNU 71 | Lesser General Public License, Version 2.1, the GNU Affero General 72 | Public License, Version 3.0, or any later versions of those 73 | licenses. 74 | 75 | 1.13. "Source Code Form" 76 | means the form of the work preferred for making modifications. 77 | 78 | 1.14. "You" (or "Your") 79 | means an individual or a legal entity exercising rights under this 80 | License. For legal entities, "You" includes any entity that 81 | controls, is controlled by, or is under common control with You. For 82 | purposes of this definition, "control" means (a) the power, direct 83 | or indirect, to cause the direction or management of such entity, 84 | whether by contract or otherwise, or (b) ownership of more than 85 | fifty percent (50%) of the outstanding shares or beneficial 86 | ownership of such entity. 87 | 88 | 2. License Grants and Conditions 89 | -------------------------------- 90 | 91 | 2.1. Grants 92 | 93 | Each Contributor hereby grants You a world-wide, royalty-free, 94 | non-exclusive license: 95 | 96 | (a) under intellectual property rights (other than patent or trademark) 97 | Licensable by such Contributor to use, reproduce, make available, 98 | modify, display, perform, distribute, and otherwise exploit its 99 | Contributions, either on an unmodified basis, with Modifications, or 100 | as part of a Larger Work; and 101 | 102 | (b) under Patent Claims of such Contributor to make, use, sell, offer 103 | for sale, have made, import, and otherwise transfer either its 104 | Contributions or its Contributor Version. 105 | 106 | 2.2. Effective Date 107 | 108 | The licenses granted in Section 2.1 with respect to any Contribution 109 | become effective for each Contribution on the date the Contributor first 110 | distributes such Contribution. 111 | 112 | 2.3. Limitations on Grant Scope 113 | 114 | The licenses granted in this Section 2 are the only rights granted under 115 | this License. No additional rights or licenses will be implied from the 116 | distribution or licensing of Covered Software under this License. 117 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 118 | Contributor: 119 | 120 | (a) for any code that a Contributor has removed from Covered Software; 121 | or 122 | 123 | (b) for infringements caused by: (i) Your and any other third party's 124 | modifications of Covered Software, or (ii) the combination of its 125 | Contributions with other software (except as part of its Contributor 126 | Version); or 127 | 128 | (c) under Patent Claims infringed by Covered Software in the absence of 129 | its Contributions. 130 | 131 | This License does not grant any rights in the trademarks, service marks, 132 | or logos of any Contributor (except as may be necessary to comply with 133 | the notice requirements in Section 3.4). 134 | 135 | 2.4. Subsequent Licenses 136 | 137 | No Contributor makes additional grants as a result of Your choice to 138 | distribute the Covered Software under a subsequent version of this 139 | License (see Section 10.2) or under the terms of a Secondary License (if 140 | permitted under the terms of Section 3.3). 141 | 142 | 2.5. Representation 143 | 144 | Each Contributor represents that the Contributor believes its 145 | Contributions are its original creation(s) or it has sufficient rights 146 | to grant the rights to its Contributions conveyed by this License. 147 | 148 | 2.6. Fair Use 149 | 150 | This License is not intended to limit any rights You have under 151 | applicable copyright doctrines of fair use, fair dealing, or other 152 | equivalents. 153 | 154 | 2.7. Conditions 155 | 156 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 157 | in Section 2.1. 158 | 159 | 3. Responsibilities 160 | ------------------- 161 | 162 | 3.1. Distribution of Source Form 163 | 164 | All distribution of Covered Software in Source Code Form, including any 165 | Modifications that You create or to which You contribute, must be under 166 | the terms of this License. You must inform recipients that the Source 167 | Code Form of the Covered Software is governed by the terms of this 168 | License, and how they can obtain a copy of this License. You may not 169 | attempt to alter or restrict the recipients' rights in the Source Code 170 | Form. 171 | 172 | 3.2. Distribution of Executable Form 173 | 174 | If You distribute Covered Software in Executable Form then: 175 | 176 | (a) such Covered Software must also be made available in Source Code 177 | Form, as described in Section 3.1, and You must inform recipients of 178 | the Executable Form how they can obtain a copy of such Source Code 179 | Form by reasonable means in a timely manner, at a charge no more 180 | than the cost of distribution to the recipient; and 181 | 182 | (b) You may distribute such Executable Form under the terms of this 183 | License, or sublicense it under different terms, provided that the 184 | license for the Executable Form does not attempt to limit or alter 185 | the recipients' rights in the Source Code Form under this License. 186 | 187 | 3.3. Distribution of a Larger Work 188 | 189 | You may create and distribute a Larger Work under terms of Your choice, 190 | provided that You also comply with the requirements of this License for 191 | the Covered Software. If the Larger Work is a combination of Covered 192 | Software with a work governed by one or more Secondary Licenses, and the 193 | Covered Software is not Incompatible With Secondary Licenses, this 194 | License permits You to additionally distribute such Covered Software 195 | under the terms of such Secondary License(s), so that the recipient of 196 | the Larger Work may, at their option, further distribute the Covered 197 | Software under the terms of either this License or such Secondary 198 | License(s). 199 | 200 | 3.4. Notices 201 | 202 | You may not remove or alter the substance of any license notices 203 | (including copyright notices, patent notices, disclaimers of warranty, 204 | or limitations of liability) contained within the Source Code Form of 205 | the Covered Software, except that You may alter any license notices to 206 | the extent required to remedy known factual inaccuracies. 207 | 208 | 3.5. Application of Additional Terms 209 | 210 | You may choose to offer, and to charge a fee for, warranty, support, 211 | indemnity or liability obligations to one or more recipients of Covered 212 | Software. However, You may do so only on Your own behalf, and not on 213 | behalf of any Contributor. You must make it absolutely clear that any 214 | such warranty, support, indemnity, or liability obligation is offered by 215 | You alone, and You hereby agree to indemnify every Contributor for any 216 | liability incurred by such Contributor as a result of warranty, support, 217 | indemnity or liability terms You offer. You may include additional 218 | disclaimers of warranty and limitations of liability specific to any 219 | jurisdiction. 220 | 221 | 4. Inability to Comply Due to Statute or Regulation 222 | --------------------------------------------------- 223 | 224 | If it is impossible for You to comply with any of the terms of this 225 | License with respect to some or all of the Covered Software due to 226 | statute, judicial order, or regulation then You must: (a) comply with 227 | the terms of this License to the maximum extent possible; and (b) 228 | describe the limitations and the code they affect. Such description must 229 | be placed in a text file included with all distributions of the Covered 230 | Software under this License. Except to the extent prohibited by statute 231 | or regulation, such description must be sufficiently detailed for a 232 | recipient of ordinary skill to be able to understand it. 233 | 234 | 5. Termination 235 | -------------- 236 | 237 | 5.1. The rights granted under this License will terminate automatically 238 | if You fail to comply with any of its terms. However, if You become 239 | compliant, then the rights granted under this License from a particular 240 | Contributor are reinstated (a) provisionally, unless and until such 241 | Contributor explicitly and finally terminates Your grants, and (b) on an 242 | ongoing basis, if such Contributor fails to notify You of the 243 | non-compliance by some reasonable means prior to 60 days after You have 244 | come back into compliance. Moreover, Your grants from a particular 245 | Contributor are reinstated on an ongoing basis if such Contributor 246 | notifies You of the non-compliance by some reasonable means, this is the 247 | first time You have received notice of non-compliance with this License 248 | from such Contributor, and You become compliant prior to 30 days after 249 | Your receipt of the notice. 250 | 251 | 5.2. If You initiate litigation against any entity by asserting a patent 252 | infringement claim (excluding declaratory judgment actions, 253 | counter-claims, and cross-claims) alleging that a Contributor Version 254 | directly or indirectly infringes any patent, then the rights granted to 255 | You by any and all Contributors for the Covered Software under Section 256 | 2.1 of this License shall terminate. 257 | 258 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 259 | end user license agreements (excluding distributors and resellers) which 260 | have been validly granted by You or Your distributors under this License 261 | prior to termination shall survive termination. 262 | 263 | ************************************************************************ 264 | * * 265 | * 6. Disclaimer of Warranty * 266 | * ------------------------- * 267 | * * 268 | * Covered Software is provided under this License on an "as is" * 269 | * basis, without warranty of any kind, either expressed, implied, or * 270 | * statutory, including, without limitation, warranties that the * 271 | * Covered Software is free of defects, merchantable, fit for a * 272 | * particular purpose or non-infringing. The entire risk as to the * 273 | * quality and performance of the Covered Software is with You. * 274 | * Should any Covered Software prove defective in any respect, You * 275 | * (not any Contributor) assume the cost of any necessary servicing, * 276 | * repair, or correction. This disclaimer of warranty constitutes an * 277 | * essential part of this License. No use of any Covered Software is * 278 | * authorized under this License except under this disclaimer. * 279 | * * 280 | ************************************************************************ 281 | 282 | ************************************************************************ 283 | * * 284 | * 7. Limitation of Liability * 285 | * -------------------------- * 286 | * * 287 | * Under no circumstances and under no legal theory, whether tort * 288 | * (including negligence), contract, or otherwise, shall any * 289 | * Contributor, or anyone who distributes Covered Software as * 290 | * permitted above, be liable to You for any direct, indirect, * 291 | * special, incidental, or consequential damages of any character * 292 | * including, without limitation, damages for lost profits, loss of * 293 | * goodwill, work stoppage, computer failure or malfunction, or any * 294 | * and all other commercial damages or losses, even if such party * 295 | * shall have been informed of the possibility of such damages. This * 296 | * limitation of liability shall not apply to liability for death or * 297 | * personal injury resulting from such party's negligence to the * 298 | * extent applicable law prohibits such limitation. Some * 299 | * jurisdictions do not allow the exclusion or limitation of * 300 | * incidental or consequential damages, so this exclusion and * 301 | * limitation may not apply to You. * 302 | * * 303 | ************************************************************************ 304 | 305 | 8. Litigation 306 | ------------- 307 | 308 | Any litigation relating to this License may be brought only in the 309 | courts of a jurisdiction where the defendant maintains its principal 310 | place of business and such litigation shall be governed by laws of that 311 | jurisdiction, without reference to its conflict-of-law provisions. 312 | Nothing in this Section shall prevent a party's ability to bring 313 | cross-claims or counter-claims. 314 | 315 | 9. Miscellaneous 316 | ---------------- 317 | 318 | This License represents the complete agreement concerning the subject 319 | matter hereof. If any provision of this License is held to be 320 | unenforceable, such provision shall be reformed only to the extent 321 | necessary to make it enforceable. Any law or regulation which provides 322 | that the language of a contract shall be construed against the drafter 323 | shall not be used to construe this License against a Contributor. 324 | 325 | 10. Versions of the License 326 | --------------------------- 327 | 328 | 10.1. New Versions 329 | 330 | Mozilla Foundation is the license steward. Except as provided in Section 331 | 10.3, no one other than the license steward has the right to modify or 332 | publish new versions of this License. Each version will be given a 333 | distinguishing version number. 334 | 335 | 10.2. Effect of New Versions 336 | 337 | You may distribute the Covered Software under the terms of the version 338 | of the License under which You originally received the Covered Software, 339 | or under the terms of any subsequent version published by the license 340 | steward. 341 | 342 | 10.3. Modified Versions 343 | 344 | If you create software not governed by this License, and you want to 345 | create a new license for such software, you may create and use a 346 | modified version of this License if you rename the license and remove 347 | any references to the name of the license steward (except to note that 348 | such modified license differs from this License). 349 | 350 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 351 | Licenses 352 | 353 | If You choose to distribute Source Code Form that is Incompatible With 354 | Secondary Licenses under the terms of this version of the License, the 355 | notice described in Exhibit B of this License must be attached. 356 | 357 | Exhibit A - Source Code Form License Notice 358 | ------------------------------------------- 359 | 360 | This Source Code Form is subject to the terms of the Mozilla Public 361 | License, v. 2.0. If a copy of the MPL was not distributed with this 362 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 363 | 364 | If it is not possible or desirable to put the notice in a particular 365 | file, then You may include the notice in a location (such as a LICENSE 366 | file in a relevant directory) where a recipient would be likely to look 367 | for such a notice. 368 | 369 | You may add additional accurate notices of copyright ownership. 370 | 371 | Exhibit B - "Incompatible With Secondary Licenses" Notice 372 | --------------------------------------------------------- 373 | 374 | This Source Code Form is "Incompatible With Secondary Licenses", as 375 | defined by the Mozilla Public License, v. 2.0. 376 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CodeQL](https://github.com/isometry/terraform-provider-deepmerge/actions/workflows/codeql.yml/badge.svg)](https://github.com/isometry/terraform-provider-deepmerge/actions/workflows/codeql.yml) 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/isometry/terraform-provider-deepmerge)](https://goreportcard.com/report/github.com/isometry/terraform-provider-deepmerge) 3 | 4 | # Terraform Provider Deepmerge 5 | 6 | Deepmerge functions for Terraform 1.8+ and OpenTofu 1.7+. 7 | 8 | ## Requirements 9 | 10 | - [Terraform](https://developer.hashicorp.com/terraform/downloads) >= 1.8 11 | - [OpenTofu](https://opentofu.org/docs/intro/install/) >= 1.7 12 | 13 | ## Using the provider 14 | 15 | ```hcl 16 | terraform { 17 | required_providers { 18 | deepmerge = { 19 | source = "isometry/deepmerge" 20 | version = "1.0.0" 21 | } 22 | } 23 | } 24 | 25 | provider "deepmerge" {} 26 | 27 | output "example" { 28 | value = provider::deepmerge::mergo(local.map1, local.map2, local.map3) 29 | } 30 | ``` 31 | 32 | ## Developing the Provider 33 | 34 | If you wish to work on the provider, you'll first need [Go](http://www.golang.org) installed on your machine (see [Requirements](#requirements) above). 35 | 36 | To compile the provider, run `go install`. This will build the provider and put the provider binary in the `$GOPATH/bin` directory. 37 | 38 | To generate or update documentation, run `go generate`. 39 | 40 | In order to run the full suite of Acceptance tests, run `make testacc`. 41 | 42 | *Note:* Acceptance tests create real resources, and often cost money to run. 43 | 44 | ```shell 45 | make testacc 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/functions/mergo.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mergo function - terraform-provider-deepmerge" 4 | subcategory: "" 5 | description: |- 6 | Deepmerge of maps with mergo semantics 7 | --- 8 | 9 | # function: mergo 10 | 11 | `mergo` takes an arbitrary number of maps or objects, and returns a single map or object that contains a recursively merged set of elements from all arguments. 12 | 13 | By default, values in later arguments override those in earlier arguments, in accordance with standard `mergo` semantics. The merge behaviour can be adjusted by passing additional string arguments to the function: 14 | 15 | * `"override"` or `"replace"` (default): New values override existing values. 16 | * `"no_override"`: New values do not override existing values. 17 | * `"no_null_override"`: Explicit null values do not override existing values. 18 | * `"append"` or `"append_lists"`: Append list values instead of replacing them. 19 | 20 | 21 | 22 | ## Signature 23 | 24 | 25 | ```text 26 | mergo(maps dynamic...) dynamic 27 | ``` 28 | 29 | ## Arguments 30 | 31 | 32 | 33 | 34 | 1. `maps` (Variadic, Dynamic, Nullable) Maps to merge 35 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "deepmerge Provider" 4 | subcategory: "" 5 | description: |- 6 | Deepmerge functions 7 | --- 8 | 9 | # deepmerge Provider 10 | 11 | Deepmerge functions 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | terraform { 17 | required_providers { 18 | deepmerge = { 19 | source = "isometry/deepmerge" 20 | } 21 | } 22 | } 23 | 24 | provider "deepmerge" {} 25 | 26 | locals { 27 | map1 = { 28 | a = { 29 | x = [1, 2, 3] 30 | y = false 31 | } 32 | b = { 33 | s = "hello, world" 34 | n = 17 35 | } 36 | } 37 | map2 = { 38 | a = { x = [4, 5, 6] } 39 | b = { n = 42 } 40 | } 41 | 42 | merged = provider::deepmerge::mergo(local.map1, local.map2) 43 | } 44 | ``` 45 | 46 | 47 | ## Schema 48 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | This directory contains examples that are mostly used for documentation, but can also be run/tested manually via the Terraform CLI. 4 | 5 | The document generation tool looks for files in the following locations by default. All other *.tf files besides the ones mentioned below are ignored by the documentation tool. This is useful for creating examples that can run and/or ar testable even if some parts are not relevant for the documentation. 6 | 7 | * **provider/provider.tf** example file for the provider index page 8 | * **data-sources/`full data source name`/data-source.tf** example file for the named data source page 9 | * **resources/`full resource name`/resource.tf** example file for the named data source page 10 | -------------------------------------------------------------------------------- /examples/provider/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | deepmerge = { 4 | source = "isometry/deepmerge" 5 | } 6 | } 7 | } 8 | 9 | provider "deepmerge" {} 10 | 11 | locals { 12 | map1 = { 13 | a = { 14 | x = [1, 2, 3] 15 | y = false 16 | } 17 | b = { 18 | s = "hello, world" 19 | n = 17 20 | } 21 | } 22 | map2 = { 23 | a = { x = [4, 5, 6] } 24 | b = { n = 42 } 25 | } 26 | 27 | merged = provider::deepmerge::mergo(local.map1, local.map2) 28 | } 29 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/isometry/terraform-provider-deepmerge 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.0 6 | 7 | require ( 8 | dario.cat/mergo v1.0.2 9 | github.com/hashicorp/go-version v1.7.0 10 | github.com/hashicorp/terraform-plugin-docs v0.21.0 11 | github.com/hashicorp/terraform-plugin-framework v1.15.0 12 | github.com/hashicorp/terraform-plugin-go v0.27.0 13 | github.com/hashicorp/terraform-plugin-testing v1.13.1 14 | github.com/stretchr/testify v1.10.0 15 | ) 16 | 17 | require ( 18 | github.com/BurntSushi/toml v1.2.1 // indirect 19 | github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect 20 | github.com/Masterminds/goutils v1.1.1 // indirect 21 | github.com/Masterminds/semver/v3 v3.2.0 // indirect 22 | github.com/Masterminds/sprig/v3 v3.2.3 // indirect 23 | github.com/ProtonMail/go-crypto v1.1.6 // indirect 24 | github.com/agext/levenshtein v1.2.2 // indirect 25 | github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect 26 | github.com/armon/go-radix v1.0.0 // indirect 27 | github.com/bgentry/speakeasy v0.1.0 // indirect 28 | github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect 29 | github.com/cloudflare/circl v1.6.0 // indirect 30 | github.com/davecgh/go-spew v1.1.1 // indirect 31 | github.com/fatih/color v1.18.0 // indirect 32 | github.com/golang/protobuf v1.5.4 // indirect 33 | github.com/google/go-cmp v0.7.0 // indirect 34 | github.com/google/uuid v1.6.0 // indirect 35 | github.com/hashicorp/cli v1.1.7 // indirect 36 | github.com/hashicorp/errwrap v1.1.0 // indirect 37 | github.com/hashicorp/go-checkpoint v0.5.0 // indirect 38 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 39 | github.com/hashicorp/go-cty v1.5.0 // indirect 40 | github.com/hashicorp/go-hclog v1.6.3 // indirect 41 | github.com/hashicorp/go-multierror v1.1.1 // indirect 42 | github.com/hashicorp/go-plugin v1.6.3 // indirect 43 | github.com/hashicorp/go-retryablehttp v0.7.7 // indirect 44 | github.com/hashicorp/go-uuid v1.0.3 // indirect 45 | github.com/hashicorp/hc-install v0.9.2 // indirect 46 | github.com/hashicorp/hcl/v2 v2.23.0 // indirect 47 | github.com/hashicorp/logutils v1.0.0 // indirect 48 | github.com/hashicorp/terraform-exec v0.23.0 // indirect 49 | github.com/hashicorp/terraform-json v0.25.0 // indirect 50 | github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect 51 | github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 // indirect 52 | github.com/hashicorp/terraform-registry-address v0.2.5 // indirect 53 | github.com/hashicorp/terraform-svchost v0.1.1 // indirect 54 | github.com/hashicorp/yamux v0.1.2 // indirect 55 | github.com/huandu/xstrings v1.3.3 // indirect 56 | github.com/imdario/mergo v0.3.15 // indirect 57 | github.com/mattn/go-colorable v0.1.14 // indirect 58 | github.com/mattn/go-isatty v0.0.20 // indirect 59 | github.com/mattn/go-runewidth v0.0.9 // indirect 60 | github.com/mitchellh/copystructure v1.2.0 // indirect 61 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 62 | github.com/mitchellh/go-wordwrap v1.0.0 // indirect 63 | github.com/mitchellh/mapstructure v1.5.0 // indirect 64 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 65 | github.com/oklog/run v1.1.0 // indirect 66 | github.com/pmezard/go-difflib v1.0.0 // indirect 67 | github.com/posener/complete v1.2.3 // indirect 68 | github.com/shopspring/decimal v1.3.1 // indirect 69 | github.com/spf13/cast v1.5.0 // indirect 70 | github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect 71 | github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect 72 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 73 | github.com/yuin/goldmark v1.7.7 // indirect 74 | github.com/yuin/goldmark-meta v1.1.0 // indirect 75 | github.com/zclconf/go-cty v1.16.2 // indirect 76 | go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect 77 | golang.org/x/crypto v0.38.0 // indirect 78 | golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 // indirect 79 | golang.org/x/mod v0.24.0 // indirect 80 | golang.org/x/net v0.40.0 // indirect 81 | golang.org/x/sync v0.14.0 // indirect 82 | golang.org/x/sys v0.33.0 // indirect 83 | golang.org/x/text v0.25.0 // indirect 84 | golang.org/x/tools v0.22.0 // indirect 85 | google.golang.org/appengine v1.6.8 // indirect 86 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 // indirect 87 | google.golang.org/grpc v1.72.1 // indirect 88 | google.golang.org/protobuf v1.36.6 // indirect 89 | gopkg.in/yaml.v2 v2.3.0 // indirect 90 | gopkg.in/yaml.v3 v3.0.1 // indirect 91 | ) 92 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= 2 | dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= 3 | github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= 4 | github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 5 | github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0= 6 | github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc= 7 | github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= 8 | github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= 9 | github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= 10 | github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= 11 | github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= 12 | github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= 13 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= 14 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 15 | github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= 16 | github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= 17 | github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= 18 | github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= 19 | github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= 20 | github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= 21 | github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= 22 | github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= 23 | github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 24 | github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= 25 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 26 | github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= 27 | github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= 28 | github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= 29 | github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= 30 | github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= 31 | github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= 32 | github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= 33 | github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= 34 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 35 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 36 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 37 | github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= 38 | github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 39 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 40 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 41 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 42 | github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= 43 | github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= 44 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= 45 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= 46 | github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= 47 | github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= 48 | github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60= 49 | github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k= 50 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 51 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 52 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 53 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 54 | github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= 55 | github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 56 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= 57 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= 58 | github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 59 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 60 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 61 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 62 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 63 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 64 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 65 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 66 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 67 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 68 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 69 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 70 | github.com/hashicorp/cli v1.1.7 h1:/fZJ+hNdwfTSfsxMBa9WWMlfjUZbX8/LnUxgAd7lCVU= 71 | github.com/hashicorp/cli v1.1.7/go.mod h1:e6Mfpga9OCT1vqzFuoGZiiF/KaG9CbUfO5s3ghU3YgU= 72 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 73 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 74 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 75 | github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= 76 | github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= 77 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 78 | github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 79 | github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 80 | github.com/hashicorp/go-cty v1.5.0 h1:EkQ/v+dDNUqnuVpmS5fPqyY71NXVgT5gf32+57xY8g0= 81 | github.com/hashicorp/go-cty v1.5.0/go.mod h1:lFUCG5kd8exDobgSfyj4ONE/dc822kiYMguVKdHGMLM= 82 | github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= 83 | github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= 84 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 85 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 86 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 87 | github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= 88 | github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= 89 | github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= 90 | github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= 91 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 92 | github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= 93 | github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 94 | github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= 95 | github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 96 | github.com/hashicorp/hc-install v0.9.2 h1:v80EtNX4fCVHqzL9Lg/2xkp62bbvQMnvPQ0G+OmtO24= 97 | github.com/hashicorp/hc-install v0.9.2/go.mod h1:XUqBQNnuT4RsxoxiM9ZaUk0NX8hi2h+Lb6/c0OZnC/I= 98 | github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos= 99 | github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= 100 | github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= 101 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 102 | github.com/hashicorp/terraform-exec v0.23.0 h1:MUiBM1s0CNlRFsCLJuM5wXZrzA3MnPYEsiXmzATMW/I= 103 | github.com/hashicorp/terraform-exec v0.23.0/go.mod h1:mA+qnx1R8eePycfwKkCRk3Wy65mwInvlpAeOwmA7vlY= 104 | github.com/hashicorp/terraform-json v0.25.0 h1:rmNqc/CIfcWawGiwXmRuiXJKEiJu1ntGoxseG1hLhoQ= 105 | github.com/hashicorp/terraform-json v0.25.0/go.mod h1:sMKS8fiRDX4rVlR6EJUMudg1WcanxCMoWwTLkgZP/vc= 106 | github.com/hashicorp/terraform-plugin-docs v0.21.0 h1:yoyA/Y719z9WdFJAhpUkI1jRbKP/nteVNBaI3hW7iQ8= 107 | github.com/hashicorp/terraform-plugin-docs v0.21.0/go.mod h1:J4Wott1J2XBKZPp/NkQv7LMShJYOcrqhQ2myXBcu64s= 108 | github.com/hashicorp/terraform-plugin-framework v1.15.0 h1:LQ2rsOfmDLxcn5EeIwdXFtr03FVsNktbbBci8cOKdb4= 109 | github.com/hashicorp/terraform-plugin-framework v1.15.0/go.mod h1:hxrNI/GY32KPISpWqlCoTLM9JZsGH3CyYlir09bD/fI= 110 | github.com/hashicorp/terraform-plugin-go v0.27.0 h1:ujykws/fWIdsi6oTUT5Or4ukvEan4aN9lY+LOxVP8EE= 111 | github.com/hashicorp/terraform-plugin-go v0.27.0/go.mod h1:FDa2Bb3uumkTGSkTFpWSOwWJDwA7bf3vdP3ltLDTH6o= 112 | github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= 113 | github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= 114 | github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 h1:NFPMacTrY/IdcIcnUB+7hsore1ZaRWU9cnB6jFoBnIM= 115 | github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0/go.mod h1:QYmYnLfsosrxjCnGY1p9c7Zj6n9thnEE+7RObeYs3fA= 116 | github.com/hashicorp/terraform-plugin-testing v1.13.1 h1:0nhSm8lngGTggqXptU4vunFI0S2XjLAhJg3RylC5aLw= 117 | github.com/hashicorp/terraform-plugin-testing v1.13.1/go.mod h1:b/hl6YZLm9fjeud/3goqh/gdqhZXbRfbHMkEiY9dZwc= 118 | github.com/hashicorp/terraform-registry-address v0.2.5 h1:2GTftHqmUhVOeuu9CW3kwDkRe4pcBDq0uuK5VJngU1M= 119 | github.com/hashicorp/terraform-registry-address v0.2.5/go.mod h1:PpzXWINwB5kuVS5CA7m1+eO2f1jKb5ZDIxrOPfpnGkg= 120 | github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= 121 | github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= 122 | github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= 123 | github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= 124 | github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= 125 | github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 126 | github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 127 | github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= 128 | github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= 129 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 130 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 131 | github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= 132 | github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= 133 | github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= 134 | github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 135 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 136 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 137 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 138 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 139 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 140 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 141 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 142 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 143 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 144 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 145 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 146 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 147 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 148 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 149 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 150 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= 151 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 152 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 153 | github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= 154 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= 155 | github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= 156 | github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= 157 | github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= 158 | github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 159 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 160 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 161 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 162 | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= 163 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 164 | github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= 165 | github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= 166 | github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= 167 | github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= 168 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 169 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 170 | github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= 171 | github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= 172 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 173 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 174 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= 175 | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= 176 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 177 | github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= 178 | github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 179 | github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= 180 | github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= 181 | github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 182 | github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= 183 | github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= 184 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 185 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 186 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 187 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 188 | github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= 189 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 190 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 191 | github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= 192 | github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= 193 | github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= 194 | github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= 195 | github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= 196 | github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= 197 | github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= 198 | github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= 199 | github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= 200 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 201 | github.com/yuin/goldmark v1.7.7 h1:5m9rrB1sW3JUMToKFQfb+FGt1U7r57IHu5GrYrG2nqU= 202 | github.com/yuin/goldmark v1.7.7/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= 203 | github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= 204 | github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= 205 | github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70= 206 | github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= 207 | github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= 208 | github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= 209 | go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw= 210 | go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU= 211 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 212 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 213 | go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= 214 | go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= 215 | go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= 216 | go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= 217 | go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= 218 | go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= 219 | go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= 220 | go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= 221 | go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= 222 | go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= 223 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 224 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 225 | golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= 226 | golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= 227 | golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= 228 | golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 h1:EDuYyU/MkFXllv9QF9819VlI9a4tzGuCbhG0ExK9o1U= 229 | golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= 230 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 231 | golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= 232 | golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 233 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 234 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 235 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 236 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 237 | golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= 238 | golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= 239 | golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= 240 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 241 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 242 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 243 | golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= 244 | golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 245 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 246 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 247 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 248 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 249 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 250 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 251 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 252 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 253 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 254 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 255 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 256 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 257 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 258 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 259 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 260 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 261 | golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= 262 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 263 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 264 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 265 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 266 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 267 | golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= 268 | golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 269 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 270 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 271 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 272 | golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= 273 | golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= 274 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 275 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 276 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 277 | google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= 278 | google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= 279 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 h1:IqsN8hx+lWLqlN+Sc3DoMy/watjofWiU8sRFgQ8fhKM= 280 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= 281 | google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= 282 | google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= 283 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 284 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 285 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 286 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 287 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 288 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 289 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 290 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 291 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 292 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 293 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 294 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 295 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 296 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 297 | -------------------------------------------------------------------------------- /internal/helpers/decode.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | // Originally copied from https://github.com/hashicorp/terraform-provider-kubernetes/blob/main/internal/framework/provider/functions/encode.go 5 | 6 | package helpers 7 | 8 | import ( 9 | "context" 10 | "fmt" 11 | "math/big" 12 | 13 | "github.com/hashicorp/terraform-plugin-framework/attr" 14 | "github.com/hashicorp/terraform-plugin-framework/diag" 15 | "github.com/hashicorp/terraform-plugin-framework/types" 16 | ) 17 | 18 | func DecodeMapping(ctx context.Context, m map[string]any) (attr.Value, diag.Diagnostics) { 19 | vm := make(map[string]attr.Value, len(m)) 20 | tm := make(map[string]attr.Type, len(m)) 21 | 22 | for k, v := range m { 23 | vv, diags := DecodeScalar(ctx, v) 24 | if diags.HasError() { 25 | return nil, diags 26 | } 27 | 28 | vm[k] = vv 29 | tm[k] = vv.Type(ctx) 30 | } 31 | 32 | return types.ObjectValue(tm, vm) 33 | } 34 | 35 | func DecodeSequence(ctx context.Context, s []any) (attr.Value, diag.Diagnostics) { 36 | vl := make([]attr.Value, len(s)) 37 | tl := make([]attr.Type, len(s)) 38 | 39 | for i, v := range s { 40 | vv, diags := DecodeScalar(ctx, v) 41 | if diags.HasError() { 42 | return nil, diags 43 | } 44 | 45 | vl[i] = vv 46 | tl[i] = vv.Type(ctx) 47 | } 48 | 49 | return types.TupleValue(tl, vl) 50 | } 51 | 52 | func DecodeScalar(ctx context.Context, m any) (value attr.Value, diags diag.Diagnostics) { 53 | switch v := m.(type) { 54 | case nil: 55 | value = types.DynamicNull() 56 | 57 | case float64: 58 | value = types.NumberValue(big.NewFloat(float64(v))) 59 | 60 | case bool: 61 | value = types.BoolValue(v) 62 | 63 | case string: 64 | value = types.StringValue(v) 65 | 66 | case []any: 67 | return DecodeSequence(ctx, v) 68 | 69 | case map[string]any: 70 | return DecodeMapping(ctx, v) 71 | 72 | default: 73 | diags.Append(diag.NewErrorDiagnostic("failed to decode", fmt.Sprintf("unexpected type: %T for value %#v", v, v))) 74 | } 75 | 76 | return 77 | } 78 | -------------------------------------------------------------------------------- /internal/helpers/decode_test.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "context" 5 | "math/big" 6 | "testing" 7 | 8 | "github.com/hashicorp/terraform-plugin-framework/attr" 9 | "github.com/hashicorp/terraform-plugin-framework/types" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestDecodeScalar(t *testing.T) { 14 | ctx := context.Background() 15 | 16 | tests := []struct { 17 | name string 18 | input any 19 | expected attr.Value 20 | hasError bool 21 | }{ 22 | { 23 | name: "nil value", 24 | input: nil, 25 | expected: types.DynamicNull(), 26 | hasError: false, 27 | }, 28 | { 29 | name: "float64 value", 30 | input: 3.14, 31 | expected: types.NumberValue(big.NewFloat(3.14)), 32 | hasError: false, 33 | }, 34 | { 35 | name: "bool value", 36 | input: true, 37 | expected: types.BoolValue(true), 38 | hasError: false, 39 | }, 40 | { 41 | name: "string value", 42 | input: "test", 43 | expected: types.StringValue("test"), 44 | hasError: false, 45 | }, 46 | { 47 | name: "slice value", 48 | input: []any{1.23, "foo", false}, 49 | expected: func() attr.Value { 50 | value, _ := types.TupleValue( 51 | []attr.Type{types.NumberType, types.StringType, types.BoolType}, 52 | []attr.Value{ 53 | types.NumberValue(big.NewFloat(1.23)), 54 | types.StringValue("foo"), 55 | types.BoolValue(false), 56 | }) 57 | return value 58 | }(), 59 | hasError: false, 60 | }, 61 | { 62 | name: "map value", 63 | input: map[string]any{ 64 | "key1": nil, 65 | "key2": 3.14, 66 | "key3": true, 67 | "key4": "test", 68 | }, 69 | expected: func() attr.Value { 70 | value, _ := types.ObjectValue( 71 | map[string]attr.Type{ 72 | "key1": types.DynamicType, 73 | "key2": types.NumberType, 74 | "key3": types.BoolType, 75 | "key4": types.StringType, 76 | }, 77 | map[string]attr.Value{ 78 | "key1": types.DynamicNull(), 79 | "key2": types.NumberValue(big.NewFloat(3.14)), 80 | "key3": types.BoolValue(true), 81 | "key4": types.StringValue("test"), 82 | }, 83 | ) 84 | return value 85 | }(), 86 | hasError: false, 87 | }, 88 | { 89 | name: "unexpected type", 90 | input: struct{}{}, 91 | expected: nil, 92 | hasError: true, 93 | }, 94 | } 95 | 96 | for _, tt := range tests { 97 | t.Run(tt.name, func(t *testing.T) { 98 | decoded, diags := DecodeScalar(ctx, tt.input) 99 | if tt.hasError { 100 | assert.True(t, diags.HasError()) 101 | } else { 102 | assert.False(t, diags.HasError()) 103 | assert.Equal(t, tt.expected, decoded) 104 | } 105 | }) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /internal/helpers/encode.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | // Originally copied from https://github.com/hashicorp/terraform-provider-kubernetes/blob/main/internal/framework/provider/functions/encode.go 5 | 6 | package helpers 7 | 8 | import ( 9 | "fmt" 10 | 11 | "github.com/hashicorp/terraform-plugin-framework/attr" 12 | "github.com/hashicorp/terraform-plugin-framework/types/basetypes" 13 | ) 14 | 15 | func EncodeValue(v attr.Value) (any, error) { 16 | // Avoid nil pointer deref with broken OpenTofu custom function 17 | // implementation that passes unknown values as zero values. 18 | if v == nil || v.IsNull() { 19 | return nil, nil 20 | } 21 | 22 | switch vv := v.(type) { 23 | case basetypes.StringValue: 24 | return vv.ValueString(), nil 25 | 26 | case basetypes.NumberValue: 27 | f, _ := vv.ValueBigFloat().Float64() 28 | return f, nil 29 | 30 | case basetypes.BoolValue: 31 | return vv.ValueBool(), nil 32 | 33 | case basetypes.ObjectValue: 34 | return EncodeObject(vv) 35 | 36 | case basetypes.TupleValue: 37 | return EncodeTuple(vv) 38 | 39 | case basetypes.MapValue: 40 | return EncodeMap(vv) 41 | 42 | case basetypes.ListValue: 43 | return EncodeList(vv) 44 | 45 | case basetypes.SetValue: 46 | return EncodeSet(vv) 47 | 48 | case basetypes.DynamicValue: 49 | return EncodeValue(vv.UnderlyingValue()) 50 | 51 | default: 52 | return nil, fmt.Errorf("tried to encode unsupported type: %T: %v", v, vv) 53 | } 54 | } 55 | 56 | func EncodeSet(sv basetypes.SetValue) (es []any, err error) { 57 | elems := sv.Elements() 58 | size := len(elems) 59 | es = make([]any, size) 60 | 61 | for i := range size { 62 | es[i], err = EncodeValue(elems[i]) 63 | if err != nil { 64 | return nil, err 65 | } 66 | } 67 | 68 | return es, nil 69 | } 70 | 71 | func EncodeList(lv basetypes.ListValue) (el []any, err error) { 72 | elems := lv.Elements() 73 | size := len(elems) 74 | el = make([]any, size) 75 | 76 | for i := range size { 77 | el[i], err = EncodeValue(elems[i]) 78 | if err != nil { 79 | return nil, err 80 | } 81 | } 82 | 83 | return el, nil 84 | } 85 | 86 | func EncodeTuple(tv basetypes.TupleValue) (et []any, err error) { 87 | elems := tv.Elements() 88 | size := len(elems) 89 | et = make([]any, size) 90 | 91 | for i := range size { 92 | et[i], err = EncodeValue(elems[i]) 93 | if err != nil { 94 | return nil, err 95 | } 96 | } 97 | 98 | return et, nil 99 | } 100 | 101 | func EncodeObject(ov basetypes.ObjectValue) (eo map[string]any, err error) { 102 | attrs := ov.Attributes() 103 | eo = make(map[string]any, len(attrs)) 104 | 105 | for k, v := range attrs { 106 | eo[k], err = EncodeValue(v) 107 | if err != nil { 108 | return nil, err 109 | } 110 | } 111 | 112 | return eo, nil 113 | } 114 | 115 | func EncodeMap(mv basetypes.MapValue) (em map[string]any, err error) { 116 | elems := mv.Elements() 117 | em = make(map[string]any, len(elems)) 118 | 119 | for k, v := range elems { 120 | em[k], err = EncodeValue(v) 121 | if err != nil { 122 | return nil, err 123 | } 124 | } 125 | 126 | return em, nil 127 | } 128 | -------------------------------------------------------------------------------- /internal/helpers/encode_test.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/hashicorp/terraform-plugin-framework/attr" 8 | "github.com/hashicorp/terraform-plugin-framework/types" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestEncodeValue(t *testing.T) { 13 | tests := []struct { 14 | name string 15 | input attr.Value 16 | expected any 17 | }{ 18 | { 19 | name: "null value", 20 | input: types.StringNull(), 21 | expected: nil, 22 | }, 23 | { 24 | name: "string value", 25 | input: types.StringValue("test"), 26 | expected: "test", 27 | }, 28 | { 29 | name: "number value", 30 | input: types.NumberValue(big.NewFloat(123.45)), 31 | expected: 123.45, 32 | }, 33 | { 34 | name: "bool value", 35 | input: types.BoolValue(true), 36 | expected: true, 37 | }, 38 | { 39 | name: "object value", 40 | input: func() attr.Value { 41 | value, _ := types.ObjectValue(map[string]attr.Type{ 42 | "key1": types.StringType, 43 | "key2": types.BoolType, 44 | }, map[string]attr.Value{ 45 | "key1": types.StringValue("value"), 46 | "key2": types.BoolValue(false), 47 | }) 48 | return value 49 | }(), 50 | expected: map[string]any{ 51 | "key1": "value", 52 | "key2": false, 53 | }, 54 | }, 55 | { 56 | name: "tuple value", 57 | input: func() attr.Value { 58 | value, _ := types.TupleValue([]attr.Type{ 59 | types.StringType, 60 | types.NumberType, 61 | }, []attr.Value{ 62 | types.StringValue("test"), 63 | types.NumberValue(big.NewFloat(123.45)), 64 | }) 65 | return value 66 | }(), 67 | expected: []any{"test", 123.45}, 68 | }, 69 | { 70 | name: "map value", 71 | input: types.MapValueMust(types.StringType, map[string]attr.Value{ 72 | "key": types.StringValue("value"), 73 | }), 74 | expected: map[string]any{ 75 | "key": "value", 76 | }, 77 | }, 78 | { 79 | name: "list value", 80 | input: types.ListValueMust(types.StringType, []attr.Value{ 81 | types.StringValue("value1"), 82 | types.StringValue("value2"), 83 | }), 84 | expected: []any{"value1", "value2"}, 85 | }, 86 | { 87 | name: "set value", 88 | input: types.SetValueMust(types.StringType, []attr.Value{ 89 | types.StringValue("value1"), 90 | types.StringValue("value2"), 91 | }), 92 | expected: []any{"value1", "value2"}, 93 | }, 94 | { 95 | name: "dynamic value", 96 | input: types.DynamicValue(types.StringValue("dynamic")), 97 | expected: "dynamic", 98 | }, 99 | } 100 | 101 | for _, tt := range tests { 102 | t.Run(tt.name, func(t *testing.T) { 103 | result, err := EncodeValue(tt.input) 104 | assert.NoError(t, err) 105 | assert.Equal(t, tt.expected, result) 106 | }) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /internal/helpers/mergo.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "dario.cat/mergo" 8 | "github.com/hashicorp/terraform-plugin-framework/attr" 9 | "github.com/hashicorp/terraform-plugin-framework/diag" 10 | "github.com/hashicorp/terraform-plugin-framework/types" 11 | ) 12 | 13 | func Mergo(ctx context.Context, objs []types.Dynamic, opts ...func(*mergo.Config)) (merged types.Dynamic, diags diag.Diagnostics) { 14 | maps := make([]map[string]any, len(objs)) 15 | for i, obj := range objs { 16 | x, err := EncodeValue(obj) 17 | if err != nil { 18 | diags.Append(diag.NewErrorDiagnostic(fmt.Sprintf("Error encoding argument %d", i+1), err.Error())) 19 | return 20 | } 21 | 22 | if y, ok := x.(map[string]any); !ok { 23 | diags.Append(diag.NewErrorDiagnostic(fmt.Sprintf("Error converting argument %d to map", i+1), fmt.Sprintf("unexpected type: %T for value %#v", x, x))) 24 | return 25 | } else { 26 | maps[i] = y 27 | } 28 | } 29 | 30 | dst := make(map[string]any) 31 | for i, m := range maps { 32 | if err := mergo.Merge(&dst, m, opts...); err != nil { 33 | diags.Append(diag.NewErrorDiagnostic(fmt.Sprintf("Error merging argument %d", i+1), err.Error())) 34 | return 35 | } 36 | } 37 | 38 | var mergedValue attr.Value 39 | 40 | mergedValue, diags = DecodeScalar(ctx, dst) 41 | if diags.HasError() { 42 | return 43 | } 44 | 45 | return types.DynamicValue(mergedValue), nil 46 | } 47 | -------------------------------------------------------------------------------- /internal/provider/mergo_function.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Robin Breathe and contributors 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package provider 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "reflect" 10 | "strings" 11 | 12 | "dario.cat/mergo" 13 | "github.com/hashicorp/terraform-plugin-framework/function" 14 | "github.com/hashicorp/terraform-plugin-framework/types" 15 | "github.com/hashicorp/terraform-plugin-framework/types/basetypes" 16 | 17 | "github.com/isometry/terraform-provider-deepmerge/internal/helpers" 18 | ) 19 | 20 | var ( 21 | _ function.Function = MergoFunction{} 22 | ) 23 | 24 | func NewMergoFunction() function.Function { 25 | return MergoFunction{} 26 | } 27 | 28 | type MergoFunction struct{} 29 | 30 | func (r MergoFunction) Metadata(_ context.Context, req function.MetadataRequest, resp *function.MetadataResponse) { 31 | resp.Name = "mergo" 32 | } 33 | 34 | func (r MergoFunction) Definition(_ context.Context, _ function.DefinitionRequest, resp *function.DefinitionResponse) { 35 | resp.Definition = function.Definition{ 36 | Summary: "Deepmerge of maps with mergo semantics", 37 | MarkdownDescription: "`mergo` takes an arbitrary number of maps or objects, and returns a single map or object that contains a recursively merged set of elements from all arguments.\n\nBy default, values in later arguments override those in earlier arguments, in accordance with standard `mergo` semantics. The merge behaviour can be adjusted by passing additional string arguments to the function:\n\n* `\"override\"` or `\"replace\"` (default): New values override existing values.\n* `\"no_override\"`: New values do not override existing values.\n* `\"no_null_override\"`: Explicit null values do not override existing values.\n* `\"append\"` or `\"append_lists\"`: Append list values instead of replacing them.", 38 | VariadicParameter: function.DynamicParameter{ 39 | Name: "maps", 40 | MarkdownDescription: "Maps to merge", 41 | AllowNullValue: true, 42 | }, 43 | Return: function.DynamicReturn{}, 44 | } 45 | } 46 | 47 | func (r MergoFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { 48 | args := make([]types.Dynamic, 0) 49 | 50 | if resp.Error = function.ConcatFuncErrors(req.Arguments.Get(ctx, &args)); resp.Error != nil { 51 | return 52 | } 53 | 54 | if len(args) == 0 { 55 | resp.Error = function.NewFuncError("at least one map must be provided") 56 | return 57 | } 58 | 59 | objs := make([]types.Dynamic, 0) 60 | opts := make([]func(*mergo.Config), 0) 61 | with_override := true 62 | no_null_override := false 63 | with_append := false 64 | 65 | for i, arg := range args { 66 | if arg.IsNull() { 67 | continue 68 | } 69 | 70 | value := arg.UnderlyingValue() 71 | 72 | switch vv := value.(type) { 73 | case basetypes.StringValue: 74 | switch option := vv.ValueString(); option { 75 | case "no_override": 76 | with_override = false 77 | 78 | case "no_null_override": 79 | no_null_override = true 80 | 81 | case "override", "replace": 82 | with_override = true 83 | 84 | case "append", "append_lists": 85 | opts = append(opts, mergo.WithAppendSlice) 86 | with_append = true 87 | 88 | default: 89 | resp.Error = function.NewArgumentFuncError(int64(i), "unrecognised option") 90 | return 91 | } 92 | 93 | case basetypes.MapValue, basetypes.ObjectValue: 94 | if !vv.IsNull() { 95 | objs = append(objs, arg) 96 | } 97 | 98 | default: 99 | typeName := strings.ToLower(strings.TrimSuffix(reflect.TypeOf(value).Name(), "Value")) 100 | resp.Error = function.NewArgumentFuncError(int64(i), fmt.Sprintf("unsupported %s argument", typeName)) 101 | return 102 | } 103 | } 104 | 105 | if with_override { 106 | opts = append(opts, mergo.WithOverride) 107 | } 108 | 109 | if no_null_override { 110 | opts = append(opts, mergo.WithTransformers(noNullOverrideTransformer{with_append: with_append})) 111 | } 112 | 113 | merged, diags := helpers.Mergo(ctx, objs, opts...) 114 | if diags.HasError() { 115 | resp.Error = function.FuncErrorFromDiags(ctx, diags) 116 | return 117 | } 118 | 119 | resp.Error = function.ConcatFuncErrors(resp.Result.Set(ctx, &merged)) 120 | } 121 | 122 | type noNullOverrideTransformer struct { 123 | with_append bool 124 | } 125 | 126 | func (t noNullOverrideTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { 127 | if typ.Kind() == reflect.Map { 128 | return func(dst, src reflect.Value) error { 129 | deepMergeMaps(dst, src, t.with_append) 130 | return nil 131 | } 132 | } 133 | return nil 134 | } 135 | 136 | func deepMergeMaps(dst, src reflect.Value, appendSlice bool) reflect.Value { 137 | for _, key := range src.MapKeys() { 138 | srcElem := src.MapIndex(key) 139 | dstElem := dst.MapIndex(key) 140 | 141 | // Unwrap the interfaces of srcElem and dstElem 142 | if srcElem.Kind() == reflect.Interface { 143 | srcElem = srcElem.Elem() 144 | } 145 | 146 | if dstElem.Kind() == reflect.Interface { 147 | dstElem = dstElem.Elem() 148 | } 149 | 150 | if srcElem.Kind() == reflect.Map && dstElem.Kind() == reflect.Map { 151 | // recursive call 152 | newValue := deepMergeMaps(dstElem, srcElem, appendSlice) 153 | dst.SetMapIndex(key, newValue) 154 | } else if !srcElem.IsValid() { // skip override of nil values 155 | continue 156 | } else if srcElem.Kind() == reflect.Slice && dstElem.Kind() == reflect.Slice && appendSlice { // handle append 157 | dst.SetMapIndex(key, reflect.AppendSlice(dstElem, srcElem)) 158 | } else { 159 | dst.SetMapIndex(key, srcElem) 160 | } 161 | } 162 | 163 | return dst 164 | } 165 | -------------------------------------------------------------------------------- /internal/provider/mergo_function_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package provider 5 | 6 | import ( 7 | "regexp" 8 | "testing" 9 | 10 | "github.com/hashicorp/go-version" 11 | "github.com/hashicorp/terraform-plugin-testing/helper/resource" 12 | "github.com/hashicorp/terraform-plugin-testing/knownvalue" 13 | "github.com/hashicorp/terraform-plugin-testing/statecheck" 14 | "github.com/hashicorp/terraform-plugin-testing/tfversion" 15 | ) 16 | 17 | func TestMergoFunction_Default(t *testing.T) { 18 | resource.UnitTest(t, resource.TestCase{ 19 | TerraformVersionChecks: []tfversion.TerraformVersionCheck{ 20 | tfversion.SkipBelow(version.Must(version.NewVersion("1.8.0"))), 21 | }, 22 | ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, 23 | Steps: []resource.TestStep{ 24 | { 25 | Config: ` 26 | locals { 27 | map1 = { 28 | x1 = { 29 | y1 = true 30 | y2 = 1 31 | } 32 | } 33 | map2 = { 34 | x1 = { 35 | y2 = 2 36 | y3 = [1, 2, 3] 37 | } 38 | x2 = { 39 | y4 = { 40 | a = "hello" 41 | b = "world" 42 | } 43 | } 44 | } 45 | map3 = { 46 | x1 = { 47 | y1 = false 48 | y3 = [4, 5, 6] 49 | } 50 | x2 = { 51 | y4 = { 52 | b = "mergo" 53 | c = ["a", 2, ["b"]] 54 | } 55 | } 56 | } 57 | } 58 | output "test" { 59 | value = provider::deepmerge::mergo(local.map1, local.map2, local.map3) 60 | } 61 | `, 62 | ConfigStateChecks: []statecheck.StateCheck{ 63 | statecheck.ExpectKnownOutputValue("test", 64 | knownvalue.MapExact(map[string]knownvalue.Check{ 65 | "x1": knownvalue.MapExact(map[string]knownvalue.Check{ 66 | "y1": knownvalue.Bool(false), 67 | "y2": knownvalue.Int64Exact(2), 68 | "y3": knownvalue.ListExact([]knownvalue.Check{ 69 | knownvalue.Int64Exact(4), 70 | knownvalue.Int64Exact(5), 71 | knownvalue.Int64Exact(6), 72 | }), 73 | }), 74 | "x2": knownvalue.MapExact(map[string]knownvalue.Check{ 75 | "y4": knownvalue.MapExact(map[string]knownvalue.Check{ 76 | "a": knownvalue.StringExact("hello"), 77 | "b": knownvalue.StringExact("mergo"), 78 | "c": knownvalue.ListExact([]knownvalue.Check{ 79 | knownvalue.StringExact("a"), 80 | knownvalue.Int64Exact(2), 81 | knownvalue.ListExact([]knownvalue.Check{ 82 | knownvalue.StringExact("b"), 83 | }), 84 | }), 85 | }), 86 | }), 87 | }), 88 | ), 89 | }, 90 | }, 91 | { 92 | Config: ` 93 | locals { 94 | map1 = { 95 | a = null 96 | b = "foo" 97 | } 98 | map2 = { 99 | a = "bar" 100 | b = "baz" 101 | } 102 | } 103 | output "test" { 104 | value = provider::deepmerge::mergo(local.map1, local.map2) 105 | } 106 | `, 107 | ConfigStateChecks: []statecheck.StateCheck{ 108 | statecheck.ExpectKnownOutputValue("test", 109 | knownvalue.MapExact(map[string]knownvalue.Check{ 110 | "a": knownvalue.StringExact("bar"), 111 | "b": knownvalue.StringExact("baz"), 112 | }), 113 | ), 114 | }, 115 | }, 116 | { 117 | Config: ` 118 | locals { 119 | map1 = { 120 | a = "foo" 121 | b = "bar" 122 | } 123 | map2 = { 124 | a = null 125 | b = "bam" 126 | } 127 | } 128 | output "test" { 129 | value = provider::deepmerge::mergo(local.map1, local.map2) 130 | } 131 | `, 132 | ConfigStateChecks: []statecheck.StateCheck{ 133 | statecheck.ExpectKnownOutputValue("test", 134 | knownvalue.MapExact(map[string]knownvalue.Check{ 135 | "a": knownvalue.Null(), 136 | "b": knownvalue.StringExact("bam"), 137 | }), 138 | ), 139 | }, 140 | }, 141 | { 142 | Config: ` 143 | variable "obj" { 144 | type = object({ 145 | x1 = any 146 | x2 = any 147 | }) 148 | default = { 149 | x1 = { 150 | y1 = false 151 | y3 = [4, 5, 6] 152 | } 153 | x2 = { 154 | y4 = { 155 | b = "mergo" 156 | c = ["a", 2, ["b"]] 157 | } 158 | } 159 | 160 | } 161 | } 162 | locals { 163 | map1 = { 164 | x1 = { 165 | y1 = true 166 | y2 = 1 167 | } 168 | } 169 | map2 = { 170 | x1 = { 171 | y2 = 2 172 | y3 = [1, 2, 3] 173 | } 174 | x2 = { 175 | y4 = { 176 | a = "hello" 177 | b = "world" 178 | } 179 | } 180 | } 181 | } 182 | output "test" { 183 | value = provider::deepmerge::mergo(local.map1, local.map2, var.obj) 184 | } 185 | `, 186 | ConfigStateChecks: []statecheck.StateCheck{ 187 | statecheck.ExpectKnownOutputValue("test", 188 | knownvalue.MapExact(map[string]knownvalue.Check{ 189 | "x1": knownvalue.MapExact(map[string]knownvalue.Check{ 190 | "y1": knownvalue.Bool(false), 191 | "y2": knownvalue.Int64Exact(2), 192 | "y3": knownvalue.ListExact([]knownvalue.Check{ 193 | knownvalue.Int64Exact(4), 194 | knownvalue.Int64Exact(5), 195 | knownvalue.Int64Exact(6), 196 | }), 197 | }), 198 | "x2": knownvalue.MapExact(map[string]knownvalue.Check{ 199 | "y4": knownvalue.MapExact(map[string]knownvalue.Check{ 200 | "a": knownvalue.StringExact("hello"), 201 | "b": knownvalue.StringExact("mergo"), 202 | "c": knownvalue.ListExact([]knownvalue.Check{ 203 | knownvalue.StringExact("a"), 204 | knownvalue.Int64Exact(2), 205 | knownvalue.ListExact([]knownvalue.Check{ 206 | knownvalue.StringExact("b"), 207 | }), 208 | }), 209 | }), 210 | }), 211 | }), 212 | ), 213 | }, 214 | }, 215 | }, 216 | }) 217 | } 218 | 219 | func TestMergoFunction_Append_NoNullOverride(t *testing.T) { 220 | resource.UnitTest(t, resource.TestCase{ 221 | TerraformVersionChecks: []tfversion.TerraformVersionCheck{ 222 | tfversion.SkipBelow(version.Must(version.NewVersion("1.8.0"))), 223 | }, 224 | ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, 225 | Steps: []resource.TestStep{ 226 | { 227 | Config: ` 228 | locals { 229 | map1 = { 230 | x1 = { 231 | y1 = "foo" 232 | y2 = 1 233 | } 234 | } 235 | map2 = { 236 | x1 = { 237 | y2 = 2 238 | y3 = [1, 2, 3] 239 | } 240 | x2 = { 241 | y4 = { 242 | a = "hello" 243 | b = "world" 244 | } 245 | } 246 | } 247 | map3 = { 248 | x1 = { 249 | y1 = null 250 | y3 = [4, 5, 6] 251 | } 252 | x2 = { 253 | y4 = { 254 | b = "mergo" 255 | c = ["a", 2, ["b"]] 256 | } 257 | } 258 | } 259 | } 260 | output "test" { 261 | value = provider::deepmerge::mergo(local.map1, local.map2, local.map3, "append", "no_null_override") 262 | } 263 | `, 264 | ConfigStateChecks: []statecheck.StateCheck{ 265 | statecheck.ExpectKnownOutputValue("test", 266 | knownvalue.MapExact(map[string]knownvalue.Check{ 267 | "x1": knownvalue.MapExact(map[string]knownvalue.Check{ 268 | "y1": knownvalue.StringExact("foo"), 269 | "y2": knownvalue.Int64Exact(2), 270 | "y3": knownvalue.ListExact([]knownvalue.Check{ 271 | knownvalue.Int64Exact(1), 272 | knownvalue.Int64Exact(2), 273 | knownvalue.Int64Exact(3), 274 | knownvalue.Int64Exact(4), 275 | knownvalue.Int64Exact(5), 276 | knownvalue.Int64Exact(6), 277 | }), 278 | }), 279 | "x2": knownvalue.MapExact(map[string]knownvalue.Check{ 280 | "y4": knownvalue.MapExact(map[string]knownvalue.Check{ 281 | "a": knownvalue.StringExact("hello"), 282 | "b": knownvalue.StringExact("mergo"), 283 | "c": knownvalue.ListExact([]knownvalue.Check{ 284 | knownvalue.StringExact("a"), 285 | knownvalue.Int64Exact(2), 286 | knownvalue.ListExact([]knownvalue.Check{ 287 | knownvalue.StringExact("b"), 288 | }), 289 | }), 290 | }), 291 | }), 292 | }), 293 | ), 294 | }, 295 | }, 296 | }, 297 | }) 298 | } 299 | 300 | func TestMergoFunction_Append(t *testing.T) { 301 | resource.UnitTest(t, resource.TestCase{ 302 | TerraformVersionChecks: []tfversion.TerraformVersionCheck{ 303 | tfversion.SkipBelow(version.Must(version.NewVersion("1.8.0"))), 304 | }, 305 | ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, 306 | Steps: []resource.TestStep{ 307 | { 308 | Config: ` 309 | locals { 310 | map1 = { 311 | x1 = { 312 | y1 = true 313 | y2 = 1 314 | } 315 | } 316 | map2 = { 317 | x1 = { 318 | y2 = 2 319 | y3 = [1, 2, 3] 320 | } 321 | x2 = { 322 | y4 = { 323 | a = "hello" 324 | b = "world" 325 | } 326 | } 327 | } 328 | map3 = { 329 | x1 = { 330 | y1 = false 331 | y3 = [4, 5, 6] 332 | } 333 | x2 = { 334 | y4 = { 335 | b = "mergo" 336 | c = ["a", 2, ["b"]] 337 | } 338 | } 339 | } 340 | } 341 | output "test" { 342 | value = provider::deepmerge::mergo(local.map1, local.map2, local.map3, "append") 343 | } 344 | `, 345 | ConfigStateChecks: []statecheck.StateCheck{ 346 | statecheck.ExpectKnownOutputValue("test", 347 | knownvalue.MapExact(map[string]knownvalue.Check{ 348 | "x1": knownvalue.MapExact(map[string]knownvalue.Check{ 349 | "y1": knownvalue.Bool(false), 350 | "y2": knownvalue.Int64Exact(2), 351 | "y3": knownvalue.ListExact([]knownvalue.Check{ 352 | knownvalue.Int64Exact(1), 353 | knownvalue.Int64Exact(2), 354 | knownvalue.Int64Exact(3), 355 | knownvalue.Int64Exact(4), 356 | knownvalue.Int64Exact(5), 357 | knownvalue.Int64Exact(6), 358 | }), 359 | }), 360 | "x2": knownvalue.MapExact(map[string]knownvalue.Check{ 361 | "y4": knownvalue.MapExact(map[string]knownvalue.Check{ 362 | "a": knownvalue.StringExact("hello"), 363 | "b": knownvalue.StringExact("mergo"), 364 | "c": knownvalue.ListExact([]knownvalue.Check{ 365 | knownvalue.StringExact("a"), 366 | knownvalue.Int64Exact(2), 367 | knownvalue.ListExact([]knownvalue.Check{ 368 | knownvalue.StringExact("b"), 369 | }), 370 | }), 371 | }), 372 | }), 373 | }), 374 | ), 375 | }, 376 | }, 377 | }, 378 | }) 379 | } 380 | 381 | func TestMergoFunction_NoOverride(t *testing.T) { 382 | resource.UnitTest(t, resource.TestCase{ 383 | TerraformVersionChecks: []tfversion.TerraformVersionCheck{ 384 | tfversion.SkipBelow(version.Must(version.NewVersion("1.8.0"))), 385 | }, 386 | ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, 387 | Steps: []resource.TestStep{ 388 | { 389 | Config: ` 390 | locals { 391 | map1 = { 392 | x1 = { 393 | y1 = true 394 | y2 = 1 395 | } 396 | } 397 | map2 = { 398 | x1 = { 399 | y2 = 2 400 | y3 = [1, 2, 3] 401 | } 402 | x2 = { 403 | y4 = { 404 | a = "hello" 405 | b = "world" 406 | } 407 | } 408 | } 409 | map3 = { 410 | x1 = { 411 | y1 = false 412 | y3 = [4, 5, 6] 413 | } 414 | x2 = { 415 | y4 = { 416 | b = "mergo" 417 | c = ["a", 2, ["b"]] 418 | } 419 | } 420 | } 421 | } 422 | output "test" { 423 | value = provider::deepmerge::mergo(local.map1, local.map2, local.map3, "no_override") 424 | } 425 | `, 426 | ConfigStateChecks: []statecheck.StateCheck{ 427 | statecheck.ExpectKnownOutputValue("test", 428 | knownvalue.MapExact(map[string]knownvalue.Check{ 429 | "x1": knownvalue.MapExact(map[string]knownvalue.Check{ 430 | "y1": knownvalue.Bool(true), 431 | "y2": knownvalue.Int64Exact(1), 432 | "y3": knownvalue.ListExact([]knownvalue.Check{ 433 | knownvalue.Int64Exact(1), 434 | knownvalue.Int64Exact(2), 435 | knownvalue.Int64Exact(3), 436 | }), 437 | }), 438 | "x2": knownvalue.MapExact(map[string]knownvalue.Check{ 439 | "y4": knownvalue.MapExact(map[string]knownvalue.Check{ 440 | "a": knownvalue.StringExact("hello"), 441 | "b": knownvalue.StringExact("world"), 442 | "c": knownvalue.ListExact([]knownvalue.Check{ 443 | knownvalue.StringExact("a"), 444 | knownvalue.Int64Exact(2), 445 | knownvalue.ListExact([]knownvalue.Check{ 446 | knownvalue.StringExact("b"), 447 | }), 448 | }), 449 | }), 450 | }), 451 | }), 452 | ), 453 | }, 454 | }, 455 | }, 456 | }) 457 | } 458 | 459 | func TestMergoFunction_Null(t *testing.T) { 460 | resource.UnitTest(t, resource.TestCase{ 461 | TerraformVersionChecks: []tfversion.TerraformVersionCheck{ 462 | tfversion.SkipBelow(version.Must(version.NewVersion("1.8.0"))), 463 | }, 464 | ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, 465 | Steps: []resource.TestStep{ 466 | { 467 | Config: ` 468 | output "test" { 469 | value = provider::deepmerge::mergo(null) 470 | } 471 | `, 472 | ConfigStateChecks: []statecheck.StateCheck{ 473 | statecheck.ExpectKnownOutputValue("test", 474 | knownvalue.MapSizeExact(0), 475 | ), 476 | }, 477 | }, 478 | { 479 | Config: ` 480 | output "test" { 481 | value = provider::deepmerge::mergo({}, null) 482 | } 483 | `, 484 | ConfigStateChecks: []statecheck.StateCheck{ 485 | statecheck.ExpectKnownOutputValue("test", 486 | knownvalue.MapSizeExact(0), 487 | ), 488 | }, 489 | }, 490 | { 491 | Config: ` 492 | variable "null_map" { 493 | type = map(any) 494 | default = null 495 | } 496 | output "test" { 497 | value = provider::deepmerge::mergo(var.null_map) 498 | } 499 | `, 500 | ConfigStateChecks: []statecheck.StateCheck{ 501 | statecheck.ExpectKnownOutputValue("test", 502 | knownvalue.MapSizeExact(0), 503 | ), 504 | }, 505 | }, 506 | { 507 | Config: ` 508 | variable "null_map" { 509 | type = map(any) 510 | default = null 511 | } 512 | output "test" { 513 | value = provider::deepmerge::mergo({}, var.null_map) 514 | } 515 | `, 516 | ConfigStateChecks: []statecheck.StateCheck{ 517 | statecheck.ExpectKnownOutputValue("test", 518 | knownvalue.MapSizeExact(0), 519 | ), 520 | }, 521 | }, 522 | }, 523 | }) 524 | } 525 | 526 | func TestMergoFunction_NoOverrideWithNull(t *testing.T) { 527 | resource.UnitTest(t, resource.TestCase{ 528 | TerraformVersionChecks: []tfversion.TerraformVersionCheck{ 529 | tfversion.SkipBelow(version.Must(version.NewVersion("1.8.0"))), 530 | }, 531 | ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, 532 | Steps: []resource.TestStep{ 533 | { 534 | Config: ` 535 | locals { 536 | map1 = { 537 | a = "foo" 538 | b = "bar" 539 | } 540 | map2 = { 541 | b = "bam" 542 | } 543 | } 544 | output "test" { 545 | value = provider::deepmerge::mergo(local.map1, local.map2, "no_null_override") 546 | } 547 | `, 548 | ConfigStateChecks: []statecheck.StateCheck{ 549 | statecheck.ExpectKnownOutputValue("test", 550 | knownvalue.MapExact(map[string]knownvalue.Check{ 551 | "a": knownvalue.StringExact("foo"), 552 | "b": knownvalue.StringExact("bam"), 553 | }), 554 | ), 555 | }, 556 | }, 557 | { 558 | Config: ` 559 | locals { 560 | map1 = { 561 | a = "foo" 562 | b = "bar" 563 | } 564 | map2 = { 565 | } 566 | } 567 | output "test" { 568 | value = provider::deepmerge::mergo(local.map1, local.map2, "no_null_override") 569 | } 570 | `, 571 | ConfigStateChecks: []statecheck.StateCheck{ 572 | statecheck.ExpectKnownOutputValue("test", 573 | knownvalue.MapExact(map[string]knownvalue.Check{ 574 | "a": knownvalue.StringExact("foo"), 575 | "b": knownvalue.StringExact("bar"), 576 | }), 577 | ), 578 | }, 579 | }, 580 | { 581 | Config: ` 582 | locals { 583 | map1 = { 584 | x1 = { 585 | y1 = false 586 | y2 = 1 587 | } 588 | } 589 | map2 = { 590 | x1 = { 591 | y2 = 1 592 | y3 = [4, 5, 6] 593 | } 594 | x2 = { 595 | y4 = { 596 | a = "hello" 597 | b = "world" 598 | } 599 | } 600 | } 601 | map3 = { 602 | x1 = { 603 | y1 = true 604 | y3 = [1, 2, 3] 605 | } 606 | x2 = { 607 | y4 = { 608 | b = "mergo" 609 | c = ["a", 2, ["b"]] 610 | } 611 | } 612 | } 613 | } 614 | output "test" { 615 | value = provider::deepmerge::mergo(local.map1, local.map2, local.map3, "no_null_override") 616 | } 617 | `, 618 | ConfigStateChecks: []statecheck.StateCheck{ 619 | statecheck.ExpectKnownOutputValue("test", 620 | knownvalue.MapExact(map[string]knownvalue.Check{ 621 | "x1": knownvalue.MapExact(map[string]knownvalue.Check{ 622 | "y1": knownvalue.Bool(true), 623 | "y2": knownvalue.Int64Exact(1), 624 | "y3": knownvalue.ListExact([]knownvalue.Check{ 625 | knownvalue.Int64Exact(1), 626 | knownvalue.Int64Exact(2), 627 | knownvalue.Int64Exact(3), 628 | }), 629 | }), 630 | "x2": knownvalue.MapExact(map[string]knownvalue.Check{ 631 | "y4": knownvalue.MapExact(map[string]knownvalue.Check{ 632 | "a": knownvalue.StringExact("hello"), 633 | "b": knownvalue.StringExact("mergo"), 634 | "c": knownvalue.ListExact([]knownvalue.Check{ 635 | knownvalue.StringExact("a"), 636 | knownvalue.Int64Exact(2), 637 | knownvalue.ListExact([]knownvalue.Check{ 638 | knownvalue.StringExact("b"), 639 | }), 640 | }), 641 | }), 642 | }), 643 | }), 644 | ), 645 | }, 646 | }, 647 | { 648 | Config: ` 649 | locals { 650 | map1 = { 651 | a = "foo" 652 | b = "bar" 653 | } 654 | map2 = { 655 | a = null 656 | b = "bam" 657 | } 658 | } 659 | output "test" { 660 | value = provider::deepmerge::mergo(local.map1, local.map2, "no_null_override") 661 | } 662 | `, 663 | ConfigStateChecks: []statecheck.StateCheck{ 664 | statecheck.ExpectKnownOutputValue("test", 665 | knownvalue.MapExact(map[string]knownvalue.Check{ 666 | "a": knownvalue.StringExact("foo"), 667 | "b": knownvalue.StringExact("bam"), 668 | }), 669 | ), 670 | }, 671 | }, 672 | { 673 | Config: ` 674 | locals { 675 | map1 = { 676 | a = "foo" 677 | b = "bar" 678 | } 679 | map2 = { 680 | a = null 681 | b = null 682 | } 683 | } 684 | output "test" { 685 | value = provider::deepmerge::mergo(local.map1, local.map2, "no_null_override") 686 | } 687 | `, 688 | ConfigStateChecks: []statecheck.StateCheck{ 689 | statecheck.ExpectKnownOutputValue("test", 690 | knownvalue.MapExact(map[string]knownvalue.Check{ 691 | "a": knownvalue.StringExact("foo"), 692 | "b": knownvalue.StringExact("bar"), 693 | }), 694 | ), 695 | }, 696 | }, 697 | { 698 | Config: ` 699 | locals { 700 | map1 = { 701 | x1 = { 702 | y1 = false 703 | y2 = 1 704 | } 705 | } 706 | map2 = { 707 | x1 = { 708 | y2 = 1 709 | y3 = [4, 5, 6] 710 | } 711 | x2 = { 712 | y4 = { 713 | a = "hello" 714 | b = "world" 715 | } 716 | } 717 | } 718 | map3 = { 719 | x1 = { 720 | y1 = true 721 | y3 = [1, 2, 3] 722 | } 723 | x2 = { 724 | y4 = { 725 | b = "mergo" 726 | c = ["a", 2, ["b"]] 727 | } 728 | } 729 | } 730 | } 731 | output "test" { 732 | value = provider::deepmerge::mergo(local.map1, local.map2, local.map3, "no_null_override") 733 | } 734 | `, 735 | ConfigStateChecks: []statecheck.StateCheck{ 736 | statecheck.ExpectKnownOutputValue("test", 737 | knownvalue.MapExact(map[string]knownvalue.Check{ 738 | "x1": knownvalue.MapExact(map[string]knownvalue.Check{ 739 | "y1": knownvalue.Bool(true), 740 | "y2": knownvalue.Int64Exact(1), 741 | "y3": knownvalue.ListExact([]knownvalue.Check{ 742 | knownvalue.Int64Exact(1), 743 | knownvalue.Int64Exact(2), 744 | knownvalue.Int64Exact(3), 745 | }), 746 | }), 747 | "x2": knownvalue.MapExact(map[string]knownvalue.Check{ 748 | "y4": knownvalue.MapExact(map[string]knownvalue.Check{ 749 | "a": knownvalue.StringExact("hello"), 750 | "b": knownvalue.StringExact("mergo"), 751 | "c": knownvalue.ListExact([]knownvalue.Check{ 752 | knownvalue.StringExact("a"), 753 | knownvalue.Int64Exact(2), 754 | knownvalue.ListExact([]knownvalue.Check{ 755 | knownvalue.StringExact("b"), 756 | }), 757 | }), 758 | }), 759 | }), 760 | }), 761 | ), 762 | }, 763 | }, 764 | { 765 | Config: ` 766 | locals { 767 | map1 = { 768 | x1 = { 769 | y2 = { 770 | z1 = 1 771 | z2 = 2 772 | } 773 | } 774 | x2 = { 775 | s1 = "hello" 776 | s2 = "world" 777 | s3 = null 778 | } 779 | x3 = { 780 | t1 = "foo" 781 | } 782 | } 783 | map2 = { 784 | x1 = { 785 | y2 = { 786 | z2 = null 787 | z3 = 3 788 | } 789 | y2 = "bar" 790 | } 791 | x3 = { 792 | t1 = null 793 | } 794 | } 795 | map3 = { 796 | x1 = { 797 | y2 = null 798 | } 799 | } 800 | map4 = { 801 | x1 = { 802 | y1 = 4 803 | } 804 | } 805 | map5 = { 806 | x1 = { 807 | y2 = { 808 | z1 = { 809 | n1 = 1 810 | n2 = { 811 | m1 = 1 812 | } 813 | } 814 | z2 = null 815 | z3 = null 816 | z4 = 4 817 | } 818 | } 819 | x2 = { 820 | s2 = "mergo" 821 | s4 = "today" 822 | } 823 | x3 = { 824 | t2 = "foz" 825 | } 826 | } 827 | } 828 | output "test" { 829 | value = provider::deepmerge::mergo(local.map1, local.map2, local.map3, local.map4, local.map5, "no_null_override") 830 | } 831 | `, 832 | ConfigStateChecks: []statecheck.StateCheck{ 833 | statecheck.ExpectKnownOutputValue("test", 834 | knownvalue.MapExact(map[string]knownvalue.Check{ 835 | "x1": knownvalue.MapExact(map[string]knownvalue.Check{ 836 | "y1": knownvalue.Int64Exact(4), 837 | "y2": knownvalue.MapExact(map[string]knownvalue.Check{ 838 | "z1": knownvalue.MapExact(map[string]knownvalue.Check{ 839 | "n1": knownvalue.Int64Exact(1), 840 | "n2": knownvalue.MapExact(map[string]knownvalue.Check{ 841 | "m1": knownvalue.Int64Exact(1), 842 | }), 843 | }), 844 | "z2": knownvalue.Null(), 845 | "z3": knownvalue.Null(), 846 | "z4": knownvalue.Int64Exact(4), 847 | }), 848 | }), 849 | "x2": knownvalue.MapExact(map[string]knownvalue.Check{ 850 | "s1": knownvalue.StringExact("hello"), 851 | "s2": knownvalue.StringExact("mergo"), 852 | "s3": knownvalue.Null(), 853 | "s4": knownvalue.StringExact("today"), 854 | }), 855 | "x3": knownvalue.MapExact(map[string]knownvalue.Check{ 856 | "t1": knownvalue.StringExact("foo"), 857 | "t2": knownvalue.StringExact("foz"), 858 | }), 859 | }), 860 | ), 861 | }, 862 | }, 863 | }, 864 | }) 865 | } 866 | 867 | func TestMergoFunction_InvalidType(t *testing.T) { 868 | resource.UnitTest(t, resource.TestCase{ 869 | TerraformVersionChecks: []tfversion.TerraformVersionCheck{ 870 | tfversion.SkipBelow(version.Must(version.NewVersion("1.8.0"))), 871 | }, 872 | ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, 873 | Steps: []resource.TestStep{ 874 | { 875 | Config: ` 876 | output "test" { 877 | value = provider::deepmerge::mergo(true) 878 | } 879 | `, 880 | ExpectError: regexp.MustCompile(`unsupported bool argument`), 881 | }, 882 | { 883 | Config: ` 884 | output "test" { 885 | value = provider::deepmerge::mergo(99.9) 886 | } 887 | `, 888 | ExpectError: regexp.MustCompile(`unsupported number argument`), 889 | }, 890 | { 891 | Config: ` 892 | output "test" { 893 | value = provider::deepmerge::mergo(["a", "b", "c"]) 894 | } 895 | `, 896 | ExpectError: regexp.MustCompile(`unsupported tuple argument`), 897 | }, 898 | 899 | { 900 | Config: ` 901 | output "test" { 902 | value = provider::deepmerge::mergo(tolist(["a", "b", "c"])) 903 | } 904 | `, 905 | ExpectError: regexp.MustCompile(`unsupported list argument`), 906 | }, 907 | { 908 | Config: ` 909 | output "test" { 910 | value = provider::deepmerge::mergo(toset(["a", "b", "c"])) 911 | } 912 | `, 913 | ExpectError: regexp.MustCompile(`unsupported set argument`), 914 | }, 915 | }, 916 | }) 917 | } 918 | -------------------------------------------------------------------------------- /internal/provider/provider.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package provider 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/hashicorp/terraform-plugin-framework/datasource" 10 | "github.com/hashicorp/terraform-plugin-framework/function" 11 | "github.com/hashicorp/terraform-plugin-framework/provider" 12 | "github.com/hashicorp/terraform-plugin-framework/resource" 13 | ) 14 | 15 | // Ensure DeepmergeProvider satisfies various provider interfaces. 16 | var _ provider.Provider = &DeepmergeProvider{} 17 | var _ provider.ProviderWithFunctions = &DeepmergeProvider{} 18 | 19 | // DeepmergeProvider defines the provider implementation. 20 | type DeepmergeProvider struct { 21 | // version is set to the provider version on release, "dev" when the 22 | // provider is built and ran locally, and "test" when running acceptance 23 | // testing. 24 | version string 25 | } 26 | 27 | // DeepmergeProviderModel describes the provider data model. 28 | type DeepmergeProviderModel struct { 29 | } 30 | 31 | func (p *DeepmergeProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { 32 | resp.TypeName = "deepmerge" 33 | resp.Version = p.version 34 | } 35 | 36 | func (p *DeepmergeProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { 37 | resp.Schema.Description = `Deepmerge functions` 38 | } 39 | 40 | func (p *DeepmergeProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { 41 | } 42 | 43 | func (p *DeepmergeProvider) Resources(ctx context.Context) []func() resource.Resource { 44 | return []func() resource.Resource{} 45 | } 46 | 47 | func (p *DeepmergeProvider) DataSources(ctx context.Context) []func() datasource.DataSource { 48 | return []func() datasource.DataSource{} 49 | } 50 | 51 | func (p *DeepmergeProvider) Functions(ctx context.Context) []func() function.Function { 52 | return []func() function.Function{ 53 | NewMergoFunction, 54 | } 55 | } 56 | 57 | func New(version string) func() provider.Provider { 58 | return func() provider.Provider { 59 | return &DeepmergeProvider{ 60 | version: version, 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /internal/provider/provider_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package provider 5 | 6 | import ( 7 | "github.com/hashicorp/terraform-plugin-framework/providerserver" 8 | "github.com/hashicorp/terraform-plugin-go/tfprotov6" 9 | ) 10 | 11 | // testAccProtoV6ProviderFactories are used to instantiate a provider during 12 | // acceptance testing. The factory function will be invoked for every Terraform 13 | // CLI command executed to create a provider server to which the CLI can 14 | // reattach. 15 | var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){ 16 | "deepmerge": providerserver.NewProtocol6WithError(New("test")()), 17 | } 18 | 19 | // func testAccPreCheck(t *testing.T) { 20 | // // You can add code here to run prior to any test case execution, for example assertions 21 | // // about the appropriate environment variables being set are common to see in a pre-check 22 | // // function. 23 | // } 24 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "flag" 9 | "log" 10 | 11 | "github.com/hashicorp/terraform-plugin-framework/providerserver" 12 | 13 | "github.com/isometry/terraform-provider-deepmerge/internal/provider" 14 | ) 15 | 16 | // Run "go generate" to format example terraform files and generate the docs for the registry/website 17 | 18 | // If you do not have terraform installed, you can remove the formatting command, but its suggested to 19 | // ensure the documentation is formatted properly. 20 | //go:generate terraform fmt -recursive ./examples/ 21 | 22 | // Run the docs generation tool, check its repository for more information on how it works and how docs 23 | // can be customized. 24 | //go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs generate 25 | 26 | var ( 27 | // these will be set by the goreleaser configuration 28 | // to appropriate values for the compiled binary. 29 | version string = "dev" 30 | 31 | // goreleaser can pass other information to the main package, such as the specific commit 32 | // https://goreleaser.com/cookbooks/using-main.version/ 33 | ) 34 | 35 | func main() { 36 | var debug bool 37 | 38 | flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") 39 | flag.Parse() 40 | 41 | opts := providerserver.ServeOpts{ 42 | Address: "registry.terraform.io/isometry/deepmerge", 43 | Debug: debug, 44 | } 45 | 46 | err := providerserver.Serve(context.Background(), provider.New(version), opts) 47 | 48 | if err != nil { 49 | log.Fatal(err.Error()) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /terraform-registry-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "metadata": { 4 | "protocol_versions": ["6.0"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tools/tools.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) HashiCorp, Inc. 2 | // SPDX-License-Identifier: MPL-2.0 3 | 4 | //go:build tools 5 | 6 | package tools 7 | 8 | import ( 9 | // Documentation generation 10 | _ "github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs" 11 | ) 12 | --------------------------------------------------------------------------------