├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── .goreleaser.yml ├── .vscode └── launch.json ├── CHANGELOG.md ├── GNUmakefile ├── LICENSE ├── README.md ├── docs ├── index.md └── resources │ └── run.md ├── go.mod ├── go.sum ├── internal └── provider │ ├── cli_config.go │ ├── cli_config_unix.go │ ├── cli_config_windows.go │ ├── client.go │ ├── provider.go │ ├── provider_test.go │ ├── resource_run.go │ ├── resource_run_test.go │ └── wait.go ├── main.go ├── templates ├── index.md.tmpl └── resources │ └── run.md.tmpl ├── test ├── noop │ └── main.tf └── tfcinit │ ├── .terraform.lock.hcl │ ├── README.md │ ├── main.tf │ └── variables.tf └── tools └── tools.go /.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/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Hi there, 2 | 3 | Thank you for opening an issue. Please note that we try to keep the Terraform issue tracker reserved for bug reports and feature requests. For general usage questions, please see: https://www.terraform.io/community.html. 4 | 5 | ### Terraform Version 6 | Run `terraform -v` to show the version. If you are not running the latest version of Terraform, please upgrade because your issue may have already been fixed. 7 | 8 | ### Affected Resource(s) 9 | Please list the resources as a list, for example: 10 | - opc_instance 11 | - opc_storage_volume 12 | 13 | If this issue appears to affect multiple resources, it may be an issue with Terraform's core, so please mention this. 14 | 15 | ### Terraform Configuration Files 16 | ```hcl 17 | # Copy-paste your Terraform configurations here - for large Terraform configs, 18 | # please use a service like Dropbox and share a link to the ZIP file. For 19 | # security, you can also encrypt the files using our GPG public key. 20 | ``` 21 | 22 | ### Debug Output 23 | Please provider a link to a GitHub Gist containing the complete debug output: https://www.terraform.io/docs/internals/debugging.html. Please do NOT paste the debug output in the issue; just paste a link to the Gist. 24 | 25 | ### Panic Output 26 | If Terraform produced a panic, please provide a link to a GitHub Gist containing the output of the `crash.log`. 27 | 28 | ### Expected Behavior 29 | What should have happened? 30 | 31 | ### Actual Behavior 32 | What actually happened? 33 | 34 | ### Steps to Reproduce 35 | Please list the steps required to reproduce the issue, for example: 36 | 1. `terraform apply` 37 | 38 | ### Important Factoids 39 | Are there anything atypical about your accounts that we should know? For example: Running in EC2 Classic? Custom version of OpenStack? Tight ACLs? 40 | 41 | ### References 42 | Are there any other GitHub issues (open or closed) or Pull Requests that should be linked here? For example: 43 | - GH-1234 44 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # See GitHub's docs for more information on this file: 2 | # https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/configuration-options-for-dependency-updates 3 | version: 2 4 | updates: 5 | # Maintain dependencies for GitHub Actions 6 | - package-ecosystem: "github-actions" 7 | directory: "/" 8 | schedule: 9 | # Check for updates to GitHub Actions every weekday 10 | interval: "daily" 11 | 12 | # Maintain dependencies for Go modules 13 | - package-ecosystem: "gomod" 14 | directory: "/" 15 | schedule: 16 | # Check for updates to Go modules every weekday 17 | interval: "daily" 18 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This GitHub action can publish assets for release when a tag is created. 2 | # Currently its setup to run on any tag that matches the pattern "v*" (ie. v0.1.0). 3 | # 4 | # This uses an action (hashicorp/ghaction-import-gpg) that assumes you set your 5 | # private key in the `GPG_PRIVATE_KEY` secret and passphrase in the `PASSPHRASE` 6 | # secret. If you would rather own your own GPG handling, please fork this action 7 | # or use an alternative one for key handling. 8 | # 9 | # You will need to pass the `--batch` flag to `gpg` in your signing step 10 | # in `goreleaser` to indicate this is being used in a non-interactive mode. 11 | # 12 | name: release 13 | on: 14 | push: 15 | tags: 16 | - 'v*' 17 | jobs: 18 | goreleaser: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - 22 | name: Checkout 23 | uses: actions/checkout@v3.3.0 24 | - 25 | name: Unshallow 26 | run: git fetch --prune --unshallow 27 | - 28 | name: Set up Go 29 | uses: actions/setup-go@v3.3.1 30 | with: 31 | go-version: 1.17 32 | - 33 | name: Import GPG key 34 | id: import_gpg 35 | uses: hashicorp/ghaction-import-gpg@v2.1.0 36 | env: 37 | # These secrets will need to be configured for the repository: 38 | GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} 39 | PASSPHRASE: ${{ secrets.PASSPHRASE }} 40 | - 41 | name: Run GoReleaser 42 | uses: goreleaser/goreleaser-action@v4.2.0 43 | with: 44 | version: latest 45 | args: release --rm-dist 46 | env: 47 | GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} 48 | # GitHub sets this automatically 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This GitHub action runs your tests for each commit push and/or PR. Optionally 2 | # you can turn it on using a cron schedule for regular testing. 3 | # 4 | name: Tests 5 | on: 6 | pull_request: 7 | paths-ignore: 8 | - 'README.md' 9 | push: 10 | paths-ignore: 11 | - 'README.md' 12 | # For systems with an upstream API that could drift unexpectedly (like most SaaS systems, etc.), 13 | # we recommend testing at a regular interval not necessarily tied to code changes. This will 14 | # ensure you are alerted to something breaking due to an API change, even if the code did not 15 | # change. 16 | # schedule: 17 | # - cron: '0 13 * * *' 18 | jobs: 19 | # ensure the code builds... 20 | build: 21 | name: Build 22 | runs-on: ubuntu-latest 23 | timeout-minutes: 5 24 | steps: 25 | 26 | - name: Set up Go 27 | uses: actions/setup-go@v3.3.1 28 | with: 29 | go-version: '1.17' 30 | id: go 31 | 32 | - name: Check out code into the Go module directory 33 | uses: actions/checkout@v3.3.0 34 | 35 | - name: Get dependencies 36 | run: | 37 | go mod download 38 | 39 | - name: Build 40 | run: | 41 | go build -v . 42 | -------------------------------------------------------------------------------- /.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 | 37 | # Other 38 | terraform-provider-multispace 39 | test/tfcinit/terraform.tfvars 40 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # Visit https://goreleaser.com for documentation on how to customize this 2 | # behavior. 3 | before: 4 | hooks: 5 | # this is just an example and not a requirement for provider building/publishing 6 | - go mod tidy 7 | builds: 8 | - env: 9 | # goreleaser does not work with CGO, it could also complicate 10 | # usage by users in CI/CD systems like Terraform Cloud where 11 | # they are unable to install libraries. 12 | - CGO_ENABLED=0 13 | mod_timestamp: '{{ .CommitTimestamp }}' 14 | flags: 15 | - -trimpath 16 | ldflags: 17 | - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}' 18 | goos: 19 | - freebsd 20 | - windows 21 | - linux 22 | - darwin 23 | goarch: 24 | - amd64 25 | - '386' 26 | - arm 27 | - arm64 28 | ignore: 29 | - goos: darwin 30 | goarch: '386' 31 | binary: '{{ .ProjectName }}_v{{ .Version }}' 32 | archives: 33 | - format: zip 34 | name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' 35 | checksum: 36 | name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS' 37 | algorithm: sha256 38 | signs: 39 | - artifacts: checksum 40 | args: 41 | # if you are using this in a GitHub action or some other automated pipeline, you 42 | # need to pass the batch flag to indicate its not interactive. 43 | - "--batch" 44 | - "--local-user" 45 | - "{{ .Env.GPG_FINGERPRINT }}" # set this environment variable for your signing key 46 | - "--output" 47 | - "${signature}" 48 | - "--detach-sign" 49 | - "${artifact}" 50 | release: 51 | # If you want to manually examine the release before its live, uncomment this line: 52 | # draft: true 53 | changelog: 54 | skip: true 55 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Acceptance Tests", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "test", 12 | // this assumes your workspace is the root of the repo 13 | "program": "${fileDirname}", 14 | "env": { 15 | "TF_ACC": "1", 16 | }, 17 | "args": [], 18 | }, 19 | { 20 | "name": "Debug - Attach External CLI", 21 | "type": "go", 22 | "request": "launch", 23 | "mode": "debug", 24 | // this assumes your workspace is the root of the repo 25 | "program": "${workspaceFolder}", 26 | "env": {}, 27 | "args": [ 28 | // pass the debug flag for reattaching 29 | "-debug", 30 | ], 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.1 (Nov 23, 2021) 2 | 3 | BUG FIXES: 4 | 5 | * Fix possible panic that could happen if run failed to create [GH-9] 6 | * Understand cost estimation and Sentinel policy states [GH-8] 7 | 8 | ## 0.1.0 9 | 10 | Initial release 11 | -------------------------------------------------------------------------------- /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 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraform Multispace Provider 2 | 3 | The `multispace` Terraform provider implements resources to help work 4 | with multi-workspace workflows in Terraform Cloud (or Enterprise). 5 | The goal of the provider is to make it easy to perform cascading 6 | creation/deletes in the proper order across a series of dependent 7 | Terraform workspaces. 8 | 9 | For more details on motivation, see the ["why?" section](#why). 10 | 11 | **Warning:** Despite my affiliation with HashiCorp, this is **NOT** an official 12 | HashiCorp project and is not supported by HashiCorp. This was created on 13 | my personal time for personal use cases. 14 | 15 | ## Features 16 | 17 | * Cascading create/destroy of multiple Terraform workspaces in 18 | dependency order. 19 | 20 | * Automatic retry of failed plans or applies within a workspace. 21 | 22 | * Optionally wait for a human to manually confirm a plan for one or 23 | more workspaces before continuing. 24 | 25 | ## Installation 26 | 27 | See the installation instructions [on the Terraform Registry](https://registry.terraform.io/providers/mitchellh/multispace/latest). Generally, add the `mitchellh/multispace` 28 | provider to your `required_providers` block and run `terraform init`: 29 | 30 | ```hcl 31 | terraform { 32 | required_providers { 33 | multispace = { 34 | source = "mitchellh/multispace" 35 | version = "" 36 | } 37 | } 38 | } 39 | ``` 40 | 41 | ## Usage 42 | 43 | The example below cascades applies and destroys across multiple workspaces. 44 | 45 | The recommended usage includes pairing this with the 46 | [tfe provider](https://registry.terraform.io/providers/hashicorp/tfe/latest). 47 | The `tfe` provider is used to configure your workspaces, and the 48 | `multispace` provider is used to create a tree of workspaces that 49 | are initialized together. 50 | 51 | **Note on usage:** I usually only use this to manage the create/destroy 52 | lifecycle today. The steady-state modification workflow uses the standard 53 | Terraform Cloud VCS-driven workflows. This provider just helps me stand up 54 | my initial environments and subsequently tear them down. 55 | 56 | ```hcl 57 | resource "multispace_run" "root" { 58 | # Use string workspace names here and not data sources so that 59 | # you can define the multispace runs before the workspace even exists. 60 | workspace = "tfc" 61 | organization = "my-org" 62 | } 63 | 64 | resource "multispace_run" "physical" { 65 | organization = "my-org" 66 | workspace = "k8s-physical" 67 | depends_on = [multispace_run.root] 68 | 69 | retry = false 70 | } 71 | 72 | resource "multispace_run" "core" { 73 | organization = "my-org" 74 | workspace = "k8s-core" 75 | depends_on = [multispace_run.physical] 76 | } 77 | 78 | resource "multispace_run" "dns" { 79 | organization = "my-org" 80 | workspace = "dns" 81 | depends_on = [multispace_run.root] 82 | manual_confirm = true 83 | } 84 | 85 | resource "multispace_run" "ingress" { 86 | organization = "my-org" 87 | workspace = "ingress" 88 | depends_on = [multispace_run.core, multispace_run.dns] 89 | } 90 | ``` 91 | 92 | ## Why? 93 | 94 | Multiple [workspaces](https://www.terraform.io/docs/cloud/workspaces/index.html) 95 | are my recommended approach to working with Terraform. Small, focused workspaces 96 | make Terraform runs fast, limit the blast radius, and enable easier 97 | work separation by teams. The [`terraform_remote_state` data source](https://www.terraform.io/docs/language/state/remote-state-data.html) 98 | can be used to pass outputs from one workspace to another workspace. This 99 | enables a clean separation of responsibilities. This is also 100 | [officially recommended by Terraform](https://www.terraform.io/docs/cloud/guides/recommended-practices/part1.html). 101 | 102 | I also use multiple workspaces as a way to model **environments**: dev, 103 | staging, production, etc. An environment to me is a collection of many 104 | workspaces working together to create a working environment. For example, 105 | one project of mine has the following workspaces that depend on each other 106 | to create a full environment: k8s-physical, k8s-core, dns, metrics, etc. 107 | 108 | **The problem statement** is that I do not have a good way to create my 109 | workspaces, create them all at once in the right order, and then destroy them 110 | if I'm done with the environment. Without this provider, I have to manually 111 | click through the Terraform Cloud UI. 112 | 113 | With this provider, I can now create a single Terraform module that is used 114 | to launch a _complete environment_ for a project, composed of multiple workspaces. 115 | And I can destroy that entire environment with a `terraform destroy`, which 116 | cascades a destroy through all the workspaces in the correct order thanks 117 | to Terraform. 118 | 119 | Note that Terraform Cloud does provide [run triggers](https://www.terraform.io/docs/cloud/workspaces/run-triggers.html) 120 | but this doesn't quite solve my problem: I don't generally want run triggers, 121 | I just want to mainly do what I'd describe as a "cascading apply/destroy" 122 | for creation/destruction. For steady-state modifications once an environment 123 | exists, I use the typical Terraform Cloud VCS-driven workflow (which may or 124 | may not involve run triggers at that point). 125 | 126 | ## Future Functionality 127 | 128 | The list below has functionality I'd like to add in the future: 129 | 130 | * Only create if there is state, otherwise, assume initialization is done. 131 | This will allow this provider to be adopted into existing workspace 132 | trees more easily. 133 | 134 | ## Developing the Provider 135 | 136 | 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). 137 | 138 | To compile the provider, run `go install`. This will build the provider and put the provider binary in the `$GOPATH/bin` directory. 139 | 140 | To generate or update documentation, run `go generate`. 141 | 142 | In order to run the full suite of Acceptance tests, run `make testacc`. 143 | 144 | *Note:* Acceptance tests create real resources, and often cost money to run. 145 | 146 | ```sh 147 | $ make testacc 148 | ``` 149 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "" 3 | page_title: "Provider: multispace" 4 | description: |- 5 | The `multispace` Terraform provider implements resources to help work with multi-workspace workflows in Terraform Cloud (or Enterprise) with pure Terraform. 6 | --- 7 | 8 | # Multispace Provider 9 | 10 | The `multispace` Terraform provider implements resources to help work 11 | with multi-workspace workflows in Terraform Cloud (or Enterprise). 12 | The goal of the provider is to make it easy to perform cascading 13 | creation/deletes in the proper order across a series of dependent 14 | Terraform workspaces. 15 | 16 | For more details on motivation, see the ["why?" section](#why). 17 | 18 | **Warning:** Despite my affiliation with HashiCorp, this is **NOT** an official 19 | HashiCorp project and is not supported by HashiCorp. This was created on 20 | my personal time for personal use cases. 21 | 22 | 23 | ## Schema 24 | 25 | ### Optional 26 | 27 | - **hostname** (String) The Terraform Enterprise hostname to connect to. Defaults to app.terraform.io. 28 | - **ssl_skip_verify** (Boolean) Whether or not to skip certificate verifications. 29 | - **token** (String) The token used to authenticate with Terraform Enterprise. We recommend omitting 30 | the token which can be set as credentials in the CLI config file. 31 | 32 | ## Usage 33 | 34 | The example below cascades applies and destroys across multiple workspaces. 35 | 36 | The recommended usage includes pairing this with the 37 | [tfe provider](https://registry.terraform.io/providers/hashicorp/tfe/latest). 38 | The `tfe` provider is used to configure your workspaces, and the 39 | `multispace` provider is used to create a tree of workspaces that 40 | are initialized together. 41 | 42 | **Note on usage:** I usually only use this to manage the create/destroy 43 | lifecycle today. The steady-state modification workflow uses the standard 44 | Terraform Cloud VCS-driven workflows. This provider just helps me stand up 45 | my initial environments and subsequently tear them down. 46 | 47 | ```hcl 48 | resource "multispace_run" "root" { 49 | # Use string workspace names here and not data sources so that 50 | # you can define the multispace runs before the workspace even exists. 51 | workspace = "tfc" 52 | organization = "my-org" 53 | } 54 | 55 | resource "multispace_run" "physical" { 56 | organization = "my-org" 57 | workspace = "k8s-physical" 58 | depends_on = [multispace_run.root] 59 | 60 | retry = false 61 | } 62 | 63 | resource "multispace_run" "core" { 64 | organization = "my-org" 65 | workspace = "k8s-core" 66 | depends_on = [multispace_run.physical] 67 | } 68 | 69 | resource "multispace_run" "dns" { 70 | organization = "my-org" 71 | workspace = "dns" 72 | depends_on = [multispace_run.root] 73 | manual_confirm = true 74 | } 75 | 76 | resource "multispace_run" "ingress" { 77 | organization = "my-org" 78 | workspace = "ingress" 79 | depends_on = [multispace_run.core, multispace_run.dns] 80 | } 81 | ``` 82 | 83 | ## Why? 84 | 85 | Multiple [workspaces](https://www.terraform.io/docs/cloud/workspaces/index.html) 86 | are my recommended approach to working with Terraform. Small, focused workspaces 87 | make Terraform runs fast, limit the blast radius, and enable easier 88 | work separation by teams. The [`terraform_remote_state` data source](https://www.terraform.io/docs/language/state/remote-state-data.html) 89 | can be used to pass outputs from one workspace to another workspace. This 90 | enables a clean separation of responsibilities. This is also 91 | [officially recommended by Terraform](https://www.terraform.io/docs/cloud/guides/recommended-practices/part1.html). 92 | 93 | I also use multiple workspaces as a way to model **environments**: dev, 94 | staging, production, etc. An environment to me is a collection of many 95 | workspaces working together to create a working environment. For example, 96 | one project of mine has the following workspaces that depend on each other 97 | to create a full environment: k8s-physical, k8s-core, dns, metrics, etc. 98 | 99 | **The problem statement** is that I do not have a good way to create my 100 | workspaces, create them all at once in the right order, and then destroy them 101 | if I'm done with the environment. Without this provider, I have to manually 102 | click through the Terraform Cloud UI. 103 | 104 | With this provider, I can now create a single Terraform module that is used 105 | to launch a _complete environment_ for a project, composed of multiple workspaces. 106 | And I can destroy that entire environment with a `terraform destroy`, which 107 | cascades a destroy through all the workspaces in the correct order thanks 108 | to Terraform. 109 | 110 | Note that Terraform Cloud does provide [run triggers](https://www.terraform.io/docs/cloud/workspaces/run-triggers.html) 111 | but this doesn't quite solve my problem: I don't generally want run triggers, 112 | I just want to mainly do what I'd describe as a "cascading apply/destroy" 113 | for creation/destruction. For steady-state modifications once an environment 114 | exists, I use the typical Terraform Cloud VCS-driven workflow (which may or 115 | may not involve run triggers at that point). 116 | 117 | -------------------------------------------------------------------------------- /docs/resources/run.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "" 3 | page_title: "Resource: multispace_run" 4 | description: |- 5 | A `multispace_run` manages the initialization and destruction of a Terraform workspace. This will queue and run an `apply` once (only if no other applies have been run) and on destroy will queue and run a `destroy` run. 6 | --- 7 | 8 | # Resource: Resource 9 | 10 | A `multispace_run` runs one `apply` on creation and one `destroy` on 11 | destruction. This can be used to enable cascading initialization of 12 | Terraform workspaces. 13 | 14 | The workspace must be created prior to the execution of this resource, 15 | but it can be created dynamically at Terraform apply-time using the 16 | [tfe provider](https://registry.terraform.io/providers/hashicorp/tfe/latest). 17 | 18 | ~> **Warning!** This will _auto-apply_ a create on creation and a destroy 19 | on destruction. You should be very careful about what is allowed to trigger 20 | this resource and when. Note that while I use the term "auto-apply", the 21 | [auto-apply workspace setting](https://www.terraform.io/docs/cloud/workspaces/settings.html#auto-apply-and-manual-apply) 22 | _does NOT_ need to be enabled and is unused by this provider. 23 | 24 | ## Existing Workspaces 25 | 26 | If you use a `multispace_run` on a workspace that already exist and has 27 | had a successful apply, `multispace_run` will queue _another_ apply. This 28 | provider expects to "own" exactly one apply. Therefore, be careful if 29 | using this with existing Terraform workspaces. 30 | 31 | A future version of this provider will change this default to skip 32 | if the last created state version was from a non-destroy run. The current 33 | version **does not do this.** 34 | 35 | ## Timeouts 36 | 37 | Workspace creation can take a long time. The default timeouts for the 38 | create and destroy of a workspace is set to 15 minutes. This can be 39 | customized using the 40 | [resource timeouts configuration](https://www.terraform.io/docs/language/resources/syntax.html#operation-timeouts). 41 | 42 | ## Example Usage: Cascading Workspaces 43 | 44 | ```hcl 45 | resource "multispace_run" "root" { 46 | # Use string workspace names here and not data sources so that 47 | # you can define the multispace runs before the workspace even exists. 48 | organization = "my-org" 49 | workspace = "tfc" 50 | } 51 | 52 | resource "multispace_run" "physical" { 53 | organization = "my-org" 54 | workspace = "k8s-physical" 55 | depends_on = [multispace_run.root] 56 | } 57 | 58 | resource "multispace_run" "core" { 59 | organization = "my-org" 60 | workspace = "k8s-core" 61 | depends_on = [multispace_run.physical] 62 | } 63 | 64 | resource "multispace_run" "dns" { 65 | organization = "my-org" 66 | workspace = "dns" 67 | depends_on = [multispace_run.root] 68 | } 69 | 70 | resource "multispace_run" "ingress" { 71 | organization = "my-org" 72 | workspace = "ingress" 73 | depends_on = [multispace_run.core, multispace_run.dns] 74 | } 75 | ``` 76 | 77 | ## Example Usage: Run Retry 78 | 79 | Run retrying is enabled by default and no further configuration is required. 80 | Retrying causes `multispace_run` to retry if there is an error during the 81 | plan or apply. This can be customized using the `retry_*` fields. 82 | 83 | ```hcl 84 | resource "multispace_run" "root" { 85 | organization = "my-org" 86 | workspace = "tfc" 87 | 88 | retry_attempts = 3 89 | retry_backoff_min = 15 90 | } 91 | ``` 92 | 93 | ## Example Usage: Manual Confirmation 94 | 95 | You may want to manually confirm the plan or apply of some resources. 96 | If you specify the `manual_confirm` field to true, then this resource will 97 | wait for a human to manually confirm the run it queued. This requires a human 98 | to watch the workspace, review, and confirm the plan within a reasonable 99 | amount of time. 100 | 101 | -> **Be careful of resource timeouts.** It is very possible for Terraform 102 | to timeout while waiting for confirmation. Please set your timeouts accordingly. 103 | 104 | ```hcl 105 | resource "multispace_run" "root" { 106 | organization = "my-org" 107 | workspace = "tfc" 108 | manual_confirm = true 109 | } 110 | ``` 111 | 112 | 113 | ## Schema 114 | 115 | ### Required 116 | 117 | - **organization** (String) The name of the Terraform Cloud organization that owns the workspace. 118 | - **workspace** (String) The name of the Terraform Cloud workspace to execute. 119 | 120 | ### Optional 121 | 122 | - **id** (String) The ID of this resource. 123 | - **manual_confirm** (Boolean) If true, a human will have to manually confirm a plan to start the apply. This applies to the creation only. Destroy never requires manual confirmation. This requires a human to carefully watch the execution of this Terraform run and hit the 'confirm' button. Be aware of resource timeouts during the Terraform run. 124 | - **retry** (Boolean) Whether or not to retry on plan or apply errors. 125 | - **retry_attempts** (Number) The number of retry attempts made for any errors during plan or apply. This applies to both creation and destruction. 126 | - **retry_backoff_max** (Number) The maximum seconds to wait between retry attempts. Retries are done using an exponential backoff, so this can be used to limit the maximum time between retries. 127 | - **retry_backoff_min** (Number) The minimum seconds to wait between retry attempts. 128 | - **timeouts** (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) 129 | 130 | 131 | ### Nested Schema for `timeouts` 132 | 133 | Optional: 134 | 135 | - **create** (String) 136 | - **delete** (String) 137 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mitchellh/terraform-provider-multispace 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/hashicorp/go-tfe v0.19.0 7 | github.com/hashicorp/hcl v1.0.0 8 | github.com/hashicorp/terraform-plugin-docs v0.5.1 9 | github.com/hashicorp/terraform-plugin-sdk/v2 v2.8.0 10 | github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 14 | cloud.google.com/go v0.61.0 h1:NLQf5e1OMspfNT1RAHOB3ublr1TW3YTXO8OiWwVjK2U= 15 | cloud.google.com/go v0.61.0/go.mod h1:XukKJg4Y7QsUu0Hxg3qQKUWR4VuWivmyMK2+rUyxAqw= 16 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 17 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 18 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 19 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 20 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 21 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 22 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 23 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 24 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 25 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 26 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 27 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 28 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 29 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 30 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 31 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 32 | cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA= 33 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 34 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 35 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 36 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 37 | github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= 38 | github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= 39 | github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= 40 | github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= 41 | github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= 42 | github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= 43 | github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= 44 | github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= 45 | github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= 46 | github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= 47 | github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= 48 | github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= 49 | github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= 50 | github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= 51 | github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= 52 | github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= 53 | github.com/andybalholm/crlf v0.0.0-20171020200849-670099aa064f/go.mod h1:k8feO4+kXDxro6ErPXBRTJ/ro2mf0SsFG8s7doP9kJE= 54 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= 55 | github.com/apparentlymart/go-cidr v1.0.1/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= 56 | github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= 57 | github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFUye+ZcSR6opIgz9Co7WcDx6ZcY+RjfFHoA0I= 58 | github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= 59 | github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0= 60 | github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= 61 | github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= 62 | github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= 63 | github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= 64 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to= 65 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 66 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 67 | github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= 68 | github.com/aws/aws-sdk-go v1.25.3 h1:uM16hIw9BotjZKMZlX05SN2EFtaWfi/NonPKIARiBLQ= 69 | github.com/aws/aws-sdk-go v1.25.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 70 | github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= 71 | github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= 72 | github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= 73 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 74 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 75 | github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= 76 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 77 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 78 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 79 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 80 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 81 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 82 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 83 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 84 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 85 | github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= 86 | github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= 87 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 88 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 89 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 90 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 91 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= 92 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 93 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= 94 | github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= 95 | github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= 96 | github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= 97 | github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= 98 | github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= 99 | github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= 100 | github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= 101 | github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= 102 | github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= 103 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 104 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 105 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 106 | github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= 107 | github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 108 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 109 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 110 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 111 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= 112 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 113 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 114 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 115 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 116 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 117 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 118 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 119 | github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 120 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 121 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 122 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 123 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 124 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 125 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 126 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 127 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 128 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 129 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 130 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 131 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 132 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= 133 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 134 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 135 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 136 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 137 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 138 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 139 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 140 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 141 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 142 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 143 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 144 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 145 | github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= 146 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 147 | github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= 148 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 149 | github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs= 150 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 151 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 152 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 153 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 154 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 155 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 156 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 157 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 158 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 159 | github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= 160 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 161 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 162 | github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= 163 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 164 | github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= 165 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 166 | github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= 167 | github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= 168 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 169 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 170 | github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 171 | github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 172 | github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= 173 | github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= 174 | github.com/hashicorp/go-getter v1.5.3 h1:NF5+zOlQegim+w/EUhSLh6QhXHmZMEeHLQzllkQ3ROU= 175 | github.com/hashicorp/go-getter v1.5.3/go.mod h1:BrrV/1clo8cCYu6mxvboYg+KutTiFnXjMEgDD8+i7ZI= 176 | github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= 177 | github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= 178 | github.com/hashicorp/go-hclog v0.15.0 h1:qMuK0wxsoW4D0ddCCYwPSTm4KQv1X1ke3WmPWZ0Mvsk= 179 | github.com/hashicorp/go-hclog v0.15.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= 180 | github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= 181 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 182 | github.com/hashicorp/go-plugin v1.3.0/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0= 183 | github.com/hashicorp/go-plugin v1.4.1 h1:6UltRQlLN9iZO513VveELp5xyaFxVD2+1OVylE+2E+w= 184 | github.com/hashicorp/go-plugin v1.4.1/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= 185 | github.com/hashicorp/go-retryablehttp v0.5.2 h1:AoISa4P4IsW0/m4T6St8Yw38gTl5GtBAgfkhYh1xAz4= 186 | github.com/hashicorp/go-retryablehttp v0.5.2/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= 187 | github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= 188 | github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= 189 | github.com/hashicorp/go-slug v0.7.0 h1:8HIi6oreWPtnhpYd8lIGQBgp4rXzDWQTOhfILZm+nok= 190 | github.com/hashicorp/go-slug v0.7.0/go.mod h1:Ib+IWBYfEfJGI1ZyXMGNbu2BU+aa3Dzu41RKLH301v4= 191 | github.com/hashicorp/go-tfe v0.19.0 h1:W65PPyz9tCQu96/QXJ4narQcHRMq53N6xKAAPZRVhno= 192 | github.com/hashicorp/go-tfe v0.19.0/go.mod h1:7lChm1Mjsh0ofrUNkP8MHljUFrnKNZNTw36S6qSbJZU= 193 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 194 | github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= 195 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 196 | github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 197 | github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 198 | github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw= 199 | github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 200 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 201 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 202 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 203 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 204 | github.com/hashicorp/hcl/v2 v2.3.0 h1:iRly8YaMwTBAKhn1Ybk7VSdzbnopghktCD031P8ggUE= 205 | github.com/hashicorp/hcl/v2 v2.3.0/go.mod h1:d+FwDBbOLvpAM3Z6J7gPj/VoAGkNe/gm352ZhjJ/Zv8= 206 | github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d h1:9ARUJJ1VVynB176G1HCwleORqCaXm/Vx0uUi0dL26I0= 207 | github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d/go.mod h1:Yog5+CPEM3c99L1CL2CFCYoSzgWm5vTU58idbRUaLik= 208 | github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= 209 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 210 | github.com/hashicorp/terraform-exec v0.14.0/go.mod h1:qrAASDq28KZiMPDnQ02sFS9udcqEkRly002EA2izXTA= 211 | github.com/hashicorp/terraform-exec v0.15.0 h1:cqjh4d8HYNQrDoEmlSGelHmg2DYDh5yayckvJ5bV18E= 212 | github.com/hashicorp/terraform-exec v0.15.0/go.mod h1:H4IG8ZxanU+NW0ZpDRNsvh9f0ul7C0nHP+rUR/CHs7I= 213 | github.com/hashicorp/terraform-json v0.12.0/go.mod h1:pmbq9o4EuL43db5+0ogX10Yofv1nozM+wskr/bGFJpI= 214 | github.com/hashicorp/terraform-json v0.13.0 h1:Li9L+lKD1FO5RVFRM1mMMIBDoUHslOniyEi5CM+FWGY= 215 | github.com/hashicorp/terraform-json v0.13.0/go.mod h1:y5OdLBCT+rxbwnpxZs9kGL7R9ExU76+cpdY8zHwoazk= 216 | github.com/hashicorp/terraform-plugin-docs v0.5.1 h1:WwrUcamix9x0TqfTw/WGHMRqoTe1QPZKaeWJPuFb4lQ= 217 | github.com/hashicorp/terraform-plugin-docs v0.5.1/go.mod h1:SQwEgy0/B0UPQ07rNEG1Wpt6E3jvRcCwkVHPNybGgc0= 218 | github.com/hashicorp/terraform-plugin-go v0.4.0 h1:LFbXNeLDo0J/wR0kUzSPq0RpdmFh2gNedzU0n/gzPAo= 219 | github.com/hashicorp/terraform-plugin-go v0.4.0/go.mod h1:7u/6nt6vaiwcWE2GuJKbJwNlDFnf5n95xKw4hqIVr58= 220 | github.com/hashicorp/terraform-plugin-sdk/v2 v2.8.0 h1:GSumgrL6GGcRYU37YuF1CC59hRPR7Yzy6tpoFlo8wr4= 221 | github.com/hashicorp/terraform-plugin-sdk/v2 v2.8.0/go.mod h1:6KbP09YzlB++S6XSUKYl83WyoHVN4MgeoCbPRsdfCtA= 222 | github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0= 223 | github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg= 224 | github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= 225 | github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= 226 | github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= 227 | github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= 228 | github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 229 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 230 | github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 231 | github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= 232 | github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 233 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 234 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 235 | github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= 236 | github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= 237 | github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= 238 | github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 239 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= 240 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 241 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 242 | github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= 243 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 244 | github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= 245 | github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 246 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 247 | github.com/klauspost/compress v1.11.2 h1:MiK62aErc3gIiVEtyzKfeOHgW7atJb5g/KNX5m3c2nQ= 248 | github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= 249 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 250 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 251 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 252 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 253 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 254 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 255 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 256 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 257 | github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= 258 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 259 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 260 | github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= 261 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 262 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 263 | github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= 264 | github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 265 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 266 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 267 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 268 | github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= 269 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 270 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 271 | github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 272 | github.com/mitchellh/cli v1.1.2 h1:PvH+lL2B7IQ101xQL63Of8yFS2y+aDlsFcsqNc+u/Kw= 273 | github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= 274 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 275 | github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= 276 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= 277 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 278 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 279 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 280 | github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 281 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 282 | github.com/mitchellh/go-testing-interface v1.0.4 h1:ZU1VNC02qyufSZsjjs7+khruk2fKvbQ3TwRV/IBCeFA= 283 | github.com/mitchellh/go-testing-interface v1.0.4/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 284 | github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 285 | github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= 286 | github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 287 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 288 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 289 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 290 | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= 291 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 292 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 293 | github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758= 294 | github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs= 295 | github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= 296 | github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= 297 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 298 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 299 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 300 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 301 | github.com/posener/complete v1.1.1 h1:ccV59UEOTzVDnDUEFdT95ZzHVZ+5+158q8+SJb2QV5w= 302 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 303 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 304 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 305 | github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= 306 | github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= 307 | github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= 308 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 309 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 310 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= 311 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 312 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 313 | github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 314 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 315 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 316 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 317 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 318 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 319 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 320 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 321 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 322 | github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= 323 | github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 324 | github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= 325 | github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= 326 | github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= 327 | github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= 328 | github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= 329 | github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= 330 | github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= 331 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 332 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 333 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 334 | github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= 335 | github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= 336 | github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= 337 | github.com/zclconf/go-cty v1.8.4/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= 338 | github.com/zclconf/go-cty v1.9.1/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= 339 | github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0= 340 | github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= 341 | github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= 342 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 343 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 344 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 345 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 346 | go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= 347 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 348 | golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 349 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 350 | golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 351 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 352 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 353 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 354 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 355 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 356 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 357 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= 358 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 359 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 360 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 361 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 362 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 363 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 364 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 365 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 366 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 367 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 368 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 369 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 370 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 371 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 372 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 373 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 374 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 375 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 376 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 377 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 378 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 379 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 380 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= 381 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 382 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 383 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 384 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 385 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 386 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 387 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 388 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 389 | golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= 390 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 391 | golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 392 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 393 | golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 394 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 395 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 396 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 397 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 398 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 399 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 400 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 401 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 402 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 403 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 404 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 405 | golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 406 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 407 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 408 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 409 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 410 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 411 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 412 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 413 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 414 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 415 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 416 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 417 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 418 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 419 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 420 | golang.org/x/net v0.0.0-20210326060303-6b1517762897 h1:KrsHThm5nFk34YtATK1LsThyGhGbGe1olrte/HInHvs= 421 | golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= 422 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 423 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 424 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 425 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 426 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= 427 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 428 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 429 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 430 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 431 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 432 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 433 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 434 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 435 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 436 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 437 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 438 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 439 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 440 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 441 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 442 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 443 | golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 444 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 445 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 446 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 447 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 448 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 449 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 450 | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 451 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 452 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 453 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 454 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 455 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 456 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 457 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 458 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 459 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 460 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 461 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 462 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 463 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 464 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 465 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 466 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 467 | golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 468 | golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 469 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 470 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo= 471 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 472 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 473 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 474 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 475 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 476 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 477 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 478 | golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= 479 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 480 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 481 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 482 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= 483 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 484 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 485 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 486 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 487 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 488 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 489 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 490 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 491 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 492 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 493 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 494 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 495 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 496 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 497 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 498 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 499 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 500 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 501 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 502 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 503 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 504 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 505 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 506 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 507 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 508 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 509 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 510 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 511 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 512 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 513 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 514 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 515 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 516 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 517 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 518 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 519 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 520 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 521 | golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed h1:+qzWo37K31KxduIYaBeMqJ8MUOyTayOQKpH9aDPLMSY= 522 | golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 523 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 524 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 525 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 526 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 527 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 528 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 529 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 530 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 531 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 532 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 533 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 534 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 535 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 536 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 537 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 538 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 539 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 540 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 541 | google.golang.org/api v0.29.0 h1:BaiDisFir8O4IJxvAabCGGkQ6yCJegNQqSVoYUNAnbk= 542 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 543 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 544 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 545 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 546 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 547 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 548 | google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= 549 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 550 | google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 551 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 552 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 553 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 554 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 555 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 556 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 557 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 558 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 559 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 560 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 561 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 562 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 563 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 564 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 565 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 566 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 567 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 568 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 569 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 570 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 571 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 572 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 573 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 574 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 575 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 576 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 577 | google.golang.org/genproto v0.0.0-20200711021454-869866162049 h1:YFTFpQhgvrLrmxtiIncJxFXeCyq84ixuKWVCaCAi9Oc= 578 | google.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 579 | google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 580 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 581 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 582 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 583 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 584 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 585 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 586 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 587 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 588 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 589 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 590 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 591 | google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= 592 | google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 593 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 594 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 595 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 596 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 597 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 598 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 599 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 600 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 601 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 602 | google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= 603 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 604 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 605 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 606 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 607 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 608 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 609 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 610 | gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= 611 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 612 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 613 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 614 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 615 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 616 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 617 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 618 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 619 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 620 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 621 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 622 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 623 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 624 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 625 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 626 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 627 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 628 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 629 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 630 | -------------------------------------------------------------------------------- /internal/provider/cli_config.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "os" 7 | 8 | "github.com/hashicorp/hcl" 9 | ) 10 | 11 | // Config is the structure of the configuration for the Terraform CLI. 12 | type Config struct { 13 | Hosts map[string]*ConfigHost `hcl:"host"` 14 | Credentials map[string]map[string]interface{} `hcl:"credentials"` 15 | } 16 | 17 | // ConfigHost is the structure of the "host" nested block within the CLI 18 | // configuration, which can be used to override the default service host 19 | // discovery behavior for a particular hostname. 20 | type ConfigHost struct { 21 | Services map[string]interface{} `hcl:"services"` 22 | } 23 | 24 | // cliConfig tries to find and parse the configuration of the Terraform CLI. 25 | // This is an optional step, so any errors are ignored. 26 | func cliConfig() *Config { 27 | mainConfig := &Config{} 28 | credentialsConfig := &Config{} 29 | combinedConfig := &Config{} 30 | 31 | // Main CLI config file; might contain manually-entered credentials, and/or 32 | // some host service discovery objects. Location is configurable via 33 | // enviroment variables. 34 | configFilePath := locateConfigFile() 35 | if configFilePath != "" { 36 | mainConfig = readCliConfigFile(configFilePath) 37 | } 38 | 39 | // Credentials file; might contain credentials auto-configured by terraform 40 | // login. Location isn't configurable. 41 | credentialsFilePath, err := credentialsFile() 42 | if err != nil { 43 | log.Printf("[ERROR] Error detecting default credentials file path: %s", err) 44 | } else { 45 | credentialsConfig = readCliConfigFile(credentialsFilePath) 46 | } 47 | 48 | // Use host service discovery configs from main config file. 49 | combinedConfig.Hosts = mainConfig.Hosts 50 | 51 | // Combine both sets of credentials. Per Terraform's own behavior, the main 52 | // config file overrides the credentials file if they have any overlapping 53 | // hostnames. 54 | combinedConfig.Credentials = credentialsConfig.Credentials 55 | if combinedConfig.Credentials == nil { 56 | combinedConfig.Credentials = make(map[string]map[string]interface{}) 57 | } 58 | for host, creds := range mainConfig.Credentials { 59 | combinedConfig.Credentials[host] = creds 60 | } 61 | 62 | return combinedConfig 63 | } 64 | 65 | func locateConfigFile() string { 66 | // To find the main CLI config file, follow Terraform's own logic: try 67 | // TF_CLI_CONFIG_FILE, then try TERRAFORM_CONFIG, then try the default 68 | // location. 69 | 70 | if os.Getenv("TF_CLI_CONFIG_FILE") != "" { 71 | return os.Getenv("TF_CLI_CONFIG_FILE") 72 | } 73 | 74 | if os.Getenv("TERRAFORM_CONFIG") != "" { 75 | return os.Getenv("TERRAFORM_CONFIG") 76 | } 77 | filePath, err := configFile() 78 | if err != nil { 79 | log.Printf("[ERROR] Error detecting default CLI config file path: %s", err) 80 | return "" 81 | } 82 | 83 | return filePath 84 | } 85 | 86 | func readCliConfigFile(configFilePath string) *Config { 87 | config := &Config{} 88 | 89 | // Read the CLI config file content. 90 | content, err := ioutil.ReadFile(configFilePath) 91 | if err != nil { 92 | log.Printf("[ERROR] Error reading CLI config or credentials file %s: %v", configFilePath, err) 93 | return config 94 | } 95 | 96 | // Parse the CLI config file content. 97 | obj, err := hcl.Parse(string(content)) 98 | if err != nil { 99 | log.Printf("[ERROR] Error parsing CLI config or credentials file %s: %v", configFilePath, err) 100 | return config 101 | } 102 | 103 | // Decode the CLI config file content. 104 | if err := hcl.DecodeObject(&config, obj); err != nil { 105 | log.Printf("[ERROR] Error decoding CLI config or credentials file %s: %v", configFilePath, err) 106 | } 107 | 108 | return config 109 | } 110 | -------------------------------------------------------------------------------- /internal/provider/cli_config_unix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package provider 5 | 6 | import ( 7 | "errors" 8 | "os" 9 | "os/user" 10 | "path/filepath" 11 | ) 12 | 13 | func configFile() (string, error) { 14 | dir, err := homeDir() 15 | if err != nil { 16 | return "", err 17 | } 18 | 19 | return filepath.Join(dir, ".terraformrc"), nil 20 | } 21 | 22 | func credentialsFile() (string, error) { 23 | dir, err := configDir() 24 | if err != nil { 25 | return "", err 26 | } 27 | 28 | return filepath.Join(dir, "credentials.tfrc.json"), nil 29 | } 30 | 31 | func configDir() (string, error) { 32 | dir, err := homeDir() 33 | if err != nil { 34 | return "", err 35 | } 36 | 37 | return filepath.Join(dir, ".terraform.d"), nil 38 | } 39 | 40 | func homeDir() (string, error) { 41 | // First prefer the HOME environmental variable 42 | if home := os.Getenv("HOME"); home != "" { 43 | // FIXME: homeDir gets called from globalPluginDirs during init, before 44 | // the logging is setup. We should move meta initializtion outside of 45 | // init, but in the meantime we just need to silence this output. 46 | //log.Printf("[DEBUG] Detected home directory from env var: %s", home) 47 | 48 | return home, nil 49 | } 50 | 51 | // If that fails, try build-in module 52 | user, err := user.Current() 53 | if err != nil { 54 | return "", err 55 | } 56 | 57 | if user.HomeDir == "" { 58 | return "", errors.New("blank output") 59 | } 60 | 61 | return user.HomeDir, nil 62 | } 63 | -------------------------------------------------------------------------------- /internal/provider/cli_config_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package provider 5 | 6 | import ( 7 | "path/filepath" 8 | "syscall" 9 | "unsafe" 10 | ) 11 | 12 | var ( 13 | shell = syscall.MustLoadDLL("Shell32.dll") 14 | getFolderPath = shell.MustFindProc("SHGetFolderPathW") 15 | ) 16 | 17 | const CSIDL_APPDATA = 26 18 | 19 | func configFile() (string, error) { 20 | dir, err := homeDir() 21 | if err != nil { 22 | return "", err 23 | } 24 | 25 | return filepath.Join(dir, "terraform.rc"), nil 26 | } 27 | 28 | func credentialsFile() (string, error) { 29 | dir, err := configDir() 30 | if err != nil { 31 | return "", err 32 | } 33 | 34 | return filepath.Join(dir, "credentials.tfrc.json"), nil 35 | } 36 | 37 | func configDir() (string, error) { 38 | dir, err := homeDir() 39 | if err != nil { 40 | return "", err 41 | } 42 | 43 | return filepath.Join(dir, "terraform.d"), nil 44 | } 45 | 46 | func homeDir() (string, error) { 47 | b := make([]uint16, syscall.MAX_PATH) 48 | 49 | // See: http://msdn.microsoft.com/en-us/library/windows/desktop/bb762181(v=vs.85).aspx 50 | r, _, err := getFolderPath.Call(0, CSIDL_APPDATA, 0, 0, uintptr(unsafe.Pointer(&b[0]))) 51 | if uint32(r) != 0 { 52 | return "", err 53 | } 54 | 55 | return syscall.UTF16ToString(b), nil 56 | } 57 | -------------------------------------------------------------------------------- /internal/provider/client.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | // This file is mostly taken from the official tfe provider, licensed MPL2. 4 | 5 | import ( 6 | "crypto/tls" 7 | "errors" 8 | "fmt" 9 | "log" 10 | "net/http" 11 | "net/url" 12 | "os" 13 | "strconv" 14 | 15 | "github.com/hashicorp/go-tfe" 16 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging" 17 | "github.com/hashicorp/terraform-svchost" 18 | "github.com/hashicorp/terraform-svchost/auth" 19 | "github.com/hashicorp/terraform-svchost/disco" 20 | ) 21 | 22 | const ( 23 | defaultHostname = "app.terraform.io" 24 | defaultSSLSkipVerify = false 25 | ) 26 | 27 | var ( 28 | tfeServiceIDs = []string{"tfe.v2.2"} 29 | errMissingAuthToken = errors.New("Required token could not be found. Please set the token using an input variable in the provider configuration block or by using the TFE_TOKEN environment variable.") 30 | ) 31 | 32 | func getClient(version string, tfeHost, token string, insecure bool) (*tfe.Client, error) { 33 | h := tfeHost 34 | if tfeHost == "" { 35 | if os.Getenv("TFE_HOSTNAME") != "" { 36 | h = os.Getenv("TFE_HOSTNAME") 37 | } else { 38 | h = defaultHostname 39 | } 40 | } 41 | 42 | log.Printf("[DEBUG] Configuring client for host %q", h) 43 | 44 | // Parse the hostname for comparison, 45 | hostname, err := svchost.ForComparison(h) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | providerUaString := fmt.Sprintf( 51 | "terraform-provider-multispace/%s", 52 | version, 53 | ) 54 | 55 | httpClient := tfe.DefaultConfig().HTTPClient 56 | transport := httpClient.Transport.(*http.Transport) 57 | if transport.TLSClientConfig == nil { 58 | transport.TLSClientConfig = &tls.Config{} 59 | } 60 | 61 | // If ssl_skip_verify is false, it is either set that way in configuration 62 | // or unset. Check the environment to see if it was set to true there. 63 | // Strictly speaking, this means that the env var can override an explicit 64 | // 'false' in configuration (which is not true of the other settings), but 65 | // that's how it goes with a boolean zero value. 66 | if !insecure && os.Getenv("TFE_SSL_SKIP_VERIFY") != "" { 67 | v := os.Getenv("TFE_SSL_SKIP_VERIFY") 68 | insecure, err = strconv.ParseBool(v) 69 | if err != nil { 70 | return nil, err 71 | } 72 | } 73 | if insecure { 74 | log.Printf("[DEBUG] Warning: Client configured to skip certificate verifications") 75 | } 76 | transport.TLSClientConfig.InsecureSkipVerify = insecure 77 | 78 | // Get the Terraform CLI configuration. 79 | config := cliConfig() 80 | 81 | // Create a new credential source and service discovery object. 82 | credsSrc := credentialsSource(config) 83 | services := disco.NewWithCredentialsSource(credsSrc) 84 | services.SetUserAgent(providerUaString) 85 | services.Transport = logging.NewTransport("TFE Discovery", transport) 86 | 87 | // Add any static host configurations service discovery object. 88 | for userHost, hostConfig := range config.Hosts { 89 | host, err := svchost.ForComparison(userHost) 90 | if err != nil { 91 | // ignore invalid hostnames. 92 | continue 93 | } 94 | services.ForceHostServices(host, hostConfig.Services) 95 | } 96 | 97 | // Discover the Terraform Enterprise address. 98 | host, err := services.Discover(hostname) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | // Get the full Terraform Enterprise service address. 104 | var address *url.URL 105 | var discoErr error 106 | for _, tfeServiceID := range tfeServiceIDs { 107 | service, err := host.ServiceURL(tfeServiceID) 108 | if _, ok := err.(*disco.ErrVersionNotSupported); !ok && err != nil { 109 | return nil, err 110 | } 111 | // If discoErr is nil we save the first error. When multiple services 112 | // are checked and we found one that didn't give an error we need to 113 | // reset the discoErr. So if err is nil, we assign it as well. 114 | if discoErr == nil || err == nil { 115 | discoErr = err 116 | } 117 | if service != nil { 118 | address = service 119 | break 120 | } 121 | } 122 | 123 | // When we don't have any constraints errors, also check for discovery 124 | // errors before we continue. 125 | if discoErr != nil { 126 | return nil, discoErr 127 | } 128 | 129 | // If a token wasn't set in the provider configuration block, try and fetch it 130 | // from the environment or from Terraform's CLI configuration or 131 | // configured credential helper. 132 | if token == "" { 133 | if os.Getenv("TFE_TOKEN") != "" { 134 | log.Printf("[DEBUG] TFE_TOKEN used for token value") 135 | token = os.Getenv("TFE_TOKEN") 136 | } else { 137 | log.Printf("[DEBUG] Attempting to fetch token from Terraform CLI configuration for hostname %s...", hostname) 138 | creds, err := services.CredentialsForHost(hostname) 139 | if err != nil { 140 | log.Printf("[DEBUG] Failed to get credentials for %s: %s (ignoring)", hostname, err) 141 | } 142 | if creds != nil { 143 | token = creds.Token() 144 | } 145 | } 146 | } 147 | 148 | // If we still don't have a token at this point, we return an error. 149 | if token == "" { 150 | return nil, errMissingAuthToken 151 | } 152 | 153 | // Wrap the configured transport to enable logging. 154 | httpClient.Transport = logging.NewTransport("TFE", transport) 155 | 156 | // Create a new TFE client config 157 | cfg := &tfe.Config{ 158 | Address: address.String(), 159 | Token: token, 160 | HTTPClient: httpClient, 161 | } 162 | 163 | // Create a new TFE client. 164 | client, err := tfe.NewClient(cfg) 165 | if err != nil { 166 | return nil, err 167 | } 168 | 169 | client.RetryServerErrors(true) 170 | return client, nil 171 | } 172 | 173 | func credentialsSource(config *Config) auth.CredentialsSource { 174 | creds := auth.NoCredentials 175 | 176 | // Add all configured credentials to the credentials source. 177 | if len(config.Credentials) > 0 { 178 | staticTable := map[svchost.Hostname]map[string]interface{}{} 179 | for userHost, creds := range config.Credentials { 180 | host, err := svchost.ForComparison(userHost) 181 | if err != nil { 182 | // We expect the config was already validated by the time we get 183 | // here, so we'll just ignore invalid hostnames. 184 | continue 185 | } 186 | staticTable[host] = creds 187 | } 188 | creds = auth.StaticCredentialsSource(staticTable) 189 | } 190 | 191 | return creds 192 | } 193 | -------------------------------------------------------------------------------- /internal/provider/provider.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 8 | ) 9 | 10 | func init() { 11 | // Set descriptions to support markdown syntax, this will be used in document generation 12 | // and the language server. 13 | schema.DescriptionKind = schema.StringMarkdown 14 | } 15 | 16 | func New(version string) func() *schema.Provider { 17 | return func() *schema.Provider { 18 | p := &schema.Provider{ 19 | Schema: map[string]*schema.Schema{ 20 | "hostname": { 21 | Type: schema.TypeString, 22 | Optional: true, 23 | Description: descriptions["hostname"], 24 | }, 25 | 26 | "token": { 27 | Type: schema.TypeString, 28 | Optional: true, 29 | Description: descriptions["token"], 30 | }, 31 | 32 | "ssl_skip_verify": { 33 | Type: schema.TypeBool, 34 | Optional: true, 35 | Description: descriptions["ssl_skip_verify"], 36 | }, 37 | }, 38 | 39 | ResourcesMap: map[string]*schema.Resource{ 40 | "multispace_run": resourceRun(), 41 | }, 42 | } 43 | 44 | p.ConfigureContextFunc = configure(version, p) 45 | 46 | return p 47 | } 48 | } 49 | 50 | func configure(version string, p *schema.Provider) func(context.Context, *schema.ResourceData) (interface{}, diag.Diagnostics) { 51 | return func( 52 | ctx context.Context, 53 | d *schema.ResourceData, 54 | ) (interface{}, diag.Diagnostics) { 55 | hostname := d.Get("hostname").(string) 56 | token := d.Get("token").(string) 57 | insecure := d.Get("ssl_skip_verify").(bool) 58 | 59 | client, err := getClient(version, hostname, token, insecure) 60 | if err != nil { 61 | return nil, diag.Errorf("%s", err.Error()) 62 | } 63 | 64 | return client, nil 65 | } 66 | } 67 | 68 | var descriptions = map[string]string{ 69 | "hostname": "The Terraform Enterprise hostname to connect to. Defaults to app.terraform.io.", 70 | "token": "The token used to authenticate with Terraform Enterprise. We recommend omitting\n" + 71 | "the token which can be set as credentials in the CLI config file.", 72 | "ssl_skip_verify": "Whether or not to skip certificate verifications.", 73 | } 74 | -------------------------------------------------------------------------------- /internal/provider/provider_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 7 | ) 8 | 9 | // providerFactories are used to instantiate a provider during acceptance testing. 10 | // The factory function will be invoked for every Terraform CLI command executed 11 | // to create a provider server to which the CLI can reattach. 12 | var providerFactories = map[string]func() (*schema.Provider, error){ 13 | "multispace": func() (*schema.Provider, error) { 14 | return New("dev")(), nil 15 | }, 16 | } 17 | 18 | func TestProvider(t *testing.T) { 19 | if err := New("dev")().InternalValidate(); err != nil { 20 | t.Fatalf("err: %s", err) 21 | } 22 | } 23 | 24 | func testAccPreCheck(t *testing.T) { 25 | // You can add code here to run prior to any test case execution, for example assertions 26 | // about the appropriate environment variables being set are common to see in a pre-check 27 | // function. 28 | } 29 | -------------------------------------------------------------------------------- /internal/provider/resource_run.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "strings" 8 | "time" 9 | 10 | tfe "github.com/hashicorp/go-tfe" 11 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 12 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 13 | ) 14 | 15 | func resourceRun() *schema.Resource { 16 | return &schema.Resource{ 17 | Description: "Workspace run (create/destroy)", 18 | 19 | CreateContext: resourceRunCreate, 20 | ReadContext: resourceRunRead, 21 | UpdateContext: resourceRunUpdate, 22 | DeleteContext: resourceRunDelete, 23 | 24 | Schema: map[string]*schema.Schema{ 25 | "organization": { 26 | Description: runDescriptions["organization"], 27 | Type: schema.TypeString, 28 | Required: true, 29 | ForceNew: true, 30 | }, 31 | 32 | "workspace": { 33 | Description: runDescriptions["workspace"], 34 | Type: schema.TypeString, 35 | Required: true, 36 | ForceNew: true, 37 | }, 38 | 39 | "manual_confirm": { 40 | Description: runDescriptions["manual_confirm"], 41 | Type: schema.TypeBool, 42 | Optional: true, 43 | Default: false, 44 | }, 45 | 46 | "retry": { 47 | Description: runDescriptions["retry"], 48 | Type: schema.TypeBool, 49 | Optional: true, 50 | Default: true, 51 | }, 52 | 53 | "retry_attempts": { 54 | Description: runDescriptions["retry_attempts"], 55 | Type: schema.TypeInt, 56 | Optional: true, 57 | Default: 3, 58 | }, 59 | 60 | "retry_backoff_min": { 61 | Description: runDescriptions["retry_backoff_min"], 62 | Type: schema.TypeInt, 63 | Optional: true, 64 | Default: 1, 65 | }, 66 | 67 | "retry_backoff_max": { 68 | Description: runDescriptions["retry_backoff_max"], 69 | Type: schema.TypeInt, 70 | Optional: true, 71 | Default: 30, 72 | }, 73 | }, 74 | 75 | Timeouts: &schema.ResourceTimeout{ 76 | Create: schema.DefaultTimeout(15 * time.Minute), 77 | Delete: schema.DefaultTimeout(15 * time.Minute), 78 | }, 79 | } 80 | } 81 | 82 | func resourceRunCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 83 | return doRun(ctx, d, meta, false) 84 | } 85 | 86 | func resourceRunRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 87 | client := meta.(*tfe.Client) 88 | id := d.Id() 89 | 90 | // Get our run. If it doesn't exist, then we assume that we were never 91 | // created. And if it exists in any form, we assume we're still in that 92 | // state. 93 | _, err := client.Runs.Read(ctx, id) 94 | if err != nil { 95 | if strings.Contains(err.Error(), "not found") { 96 | d.SetId("") 97 | return nil 98 | } 99 | 100 | return diag.FromErr(err) 101 | } 102 | 103 | return nil 104 | } 105 | 106 | func resourceRunUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 107 | // Update we do nothing since we should have created during apply. 108 | return nil 109 | } 110 | 111 | func resourceRunDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 112 | return doRun(ctx, d, meta, true) 113 | } 114 | 115 | func doRun( 116 | ctx context.Context, 117 | d *schema.ResourceData, 118 | meta interface{}, 119 | destroy bool, 120 | ) diag.Diagnostics { 121 | // We only set the ID on create 122 | setId := func(v string) { 123 | if !destroy { 124 | d.SetId(v) 125 | } 126 | } 127 | 128 | // Get our retry information 129 | retry := d.Get("retry").(bool) 130 | retryMaxAttempts := d.Get("retry_attempts").(int) 131 | retryBOMin := d.Get("retry_backoff_min").(int) 132 | retryBOMax := d.Get("retry_backoff_max").(int) 133 | retryCurAttempts := 0 134 | 135 | RETRY: 136 | retryCurAttempts++ 137 | if retryCurAttempts > 1 { 138 | // If we're retrying, then perform the backoff. 139 | select { 140 | case <-ctx.Done(): 141 | return diag.FromErr(ctx.Err()) 142 | case <-time.After(backoff( 143 | float64(retryBOMin), float64(retryBOMax), retryCurAttempts)): 144 | } 145 | } 146 | if retryCurAttempts > retryMaxAttempts { 147 | return diag.Errorf( 148 | "Maximum retry attempts %d reached. Please see the web UI "+ 149 | "to see any errors during plan or apply.", 150 | retryMaxAttempts, 151 | ) 152 | } 153 | 154 | client := meta.(*tfe.Client) 155 | org := d.Get("organization").(string) 156 | workspace := d.Get("workspace").(string) 157 | 158 | // Get our workspace because we need it to queue a plan 159 | ws, err := client.Workspaces.Read(ctx, org, workspace) 160 | if err != nil { 161 | return diag.FromErr(err) 162 | } 163 | 164 | // Create a run 165 | run, err := client.Runs.Create(ctx, tfe.RunCreateOptions{ 166 | Message: tfe.String(fmt.Sprintf( 167 | "terraform-provider-multispace on %s", 168 | time.Now().Format("Mon Jan 2 15:04:05 MST 2006"), 169 | )), 170 | Workspace: ws, 171 | IsDestroy: tfe.Bool(destroy), 172 | 173 | // Never auto-apply because we handle all that. 174 | AutoApply: tfe.Bool(false), 175 | }) 176 | 177 | // I THINK if err != nil implies run == nil but we can never be too sure. 178 | // If we have a non-nil run, we want to save the ID so we don't dangle 179 | // resources. 180 | if run != nil { 181 | // The ID we use is the run we queue. We can use this to look this 182 | // run up again in the case of a partial failure. 183 | setId(run.ID) 184 | log.Printf("[INFO] run created: %s", run.ID) 185 | } 186 | 187 | if err != nil { 188 | return diag.FromErr(err) 189 | } 190 | 191 | // If we reach this point and run is nil, there is a bug in the TFE 192 | // API client. But its non-sensical and we can't continue so we must exit. 193 | if run == nil { 194 | log.Printf("[ERROR] TFE client returned err nil but also run nil") 195 | return diag.Errorf( 196 | "INTERNAL ERROR: run create API did not error, but also did not " + 197 | "return a run ID.") 198 | } 199 | 200 | // Wait for the plan to complete. 201 | run, diags := waitForRun(ctx, client, org, run, ws, true, []tfe.RunStatus{ 202 | tfe.RunPlanned, 203 | tfe.RunPlannedAndFinished, 204 | tfe.RunErrored, 205 | tfe.RunCostEstimated, 206 | tfe.RunPolicyChecked, 207 | tfe.RunPolicySoftFailed, 208 | tfe.RunPolicyOverride, 209 | }, []tfe.RunStatus{ 210 | tfe.RunPending, 211 | tfe.RunPlanQueued, 212 | tfe.RunPlanning, 213 | tfe.RunCostEstimating, 214 | tfe.RunPolicyChecking, 215 | }) 216 | if diags != nil { 217 | return diags 218 | } 219 | 220 | // If the run errored, we should have exited already but lets just exit now. 221 | if run.Status == tfe.RunErrored || run.Status == tfe.RunPolicySoftFailed { 222 | // Clear the ID, we didn't create anything. 223 | setId("") 224 | 225 | if retry { 226 | // Retry 227 | goto RETRY 228 | } 229 | 230 | return diag.Errorf( 231 | "Run %q errored during plan. Please open the web UI to view the error", 232 | run.ID, 233 | ) 234 | } 235 | 236 | // If the plan has no changes, then we're done. 237 | if !run.HasChanges || run.Status == tfe.RunPlannedAndFinished { 238 | log.Printf("[INFO] plan finished, no changes") 239 | return nil 240 | } 241 | 242 | // If a policy soft-fails, we need human approval before we continue 243 | if run.Status == tfe.RunPolicyOverride { 244 | log.Printf("[INFO] policy check soft-failed, waiting for manual override. %q", run.ID) 245 | run, diags = waitForRun(ctx, client, org, run, ws, true, []tfe.RunStatus{ 246 | tfe.RunConfirmed, 247 | tfe.RunApplyQueued, 248 | tfe.RunApplying, 249 | }, []tfe.RunStatus{run.Status}) 250 | if diags != nil { 251 | return diags 252 | } 253 | } 254 | 255 | // If we're doing a manual confirmation, then we wait for the human to confirm. 256 | if !destroy && d.Get("manual_confirm").(bool) { 257 | log.Printf("[INFO] plan complete, waiting for manual confirm. %q", run.ID) 258 | run, diags = waitForRun(ctx, client, org, run, ws, true, []tfe.RunStatus{ 259 | tfe.RunConfirmed, 260 | tfe.RunApplyQueued, 261 | tfe.RunApplying, 262 | }, []tfe.RunStatus{run.Status}) 263 | if diags != nil { 264 | return diags 265 | } 266 | } else { 267 | // Apply the plan. 268 | log.Printf("[INFO] plan complete, confirming apply. %q", run.ID) 269 | if err := client.Runs.Apply(ctx, run.ID, tfe.RunApplyOptions{ 270 | Comment: tfe.String(fmt.Sprintf( 271 | "terraform-provider-multispace on %s", 272 | time.Now().Format("Mon Jan 2 15:04:05 MST 2006"), 273 | )), 274 | }); err != nil { 275 | return diag.FromErr(err) 276 | } 277 | } 278 | 279 | // Wait now for the apply to complete 280 | run, diags = waitForRun(ctx, client, org, run, ws, false, []tfe.RunStatus{ 281 | tfe.RunApplied, 282 | tfe.RunErrored, 283 | }, []tfe.RunStatus{ 284 | tfe.RunConfirmed, 285 | tfe.RunApplyQueued, 286 | tfe.RunApplying, 287 | }) 288 | if diags != nil { 289 | return diags 290 | } 291 | 292 | // If the run errored, we should have exited already but lets just exit now. 293 | if run.Status == tfe.RunErrored { 294 | // Clear the ID, we didn't create anything. 295 | setId("") 296 | 297 | if retry { 298 | // Retry 299 | goto RETRY 300 | } 301 | 302 | return diag.Errorf( 303 | "Run %q errored during apply. Please open the web UI to view the error", 304 | run.ID, 305 | ) 306 | } 307 | 308 | // If this is not applied, we're in some unexpected state. 309 | if run.Status != tfe.RunApplied { 310 | setId("") 311 | 312 | return diag.Errorf( 313 | "Run %q entered unexpected state %q, expected applied", 314 | run.ID, run.Status, 315 | ) 316 | } 317 | 318 | return nil 319 | } 320 | 321 | var runDescriptions = map[string]string{ 322 | "organization": "The name of the Terraform Cloud organization that owns the workspace.", 323 | "workspace": "The name of the Terraform Cloud workspace to execute.", 324 | "manual_confirm": "If true, a human will have to manually confirm a plan " + 325 | "to start the apply. This applies to the creation only. Destroy never " + 326 | "requires manual confirmation. This requires a human to carefully watch the execution " + 327 | "of this Terraform run and hit the 'confirm' button. Be aware of resource " + 328 | "timeouts during the Terraform run.", 329 | "retry": "Whether or not to retry on plan or apply errors.", 330 | "retry_attempts": "The number of retry attempts made for any errors during " + 331 | "plan or apply. This applies to both creation and destruction.", 332 | "retry_backoff_min": "The minimum seconds to wait between retry attempts.", 333 | "retry_backoff_max": "The maximum seconds to wait between retry attempts. Retries " + 334 | "are done using an exponential backoff, so this can be used to limit " + 335 | "the maximum time between retries.", 336 | } 337 | -------------------------------------------------------------------------------- /internal/provider/resource_run_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | ) 8 | 9 | func TestAccResourceRun(t *testing.T) { 10 | resource.UnitTest(t, resource.TestCase{ 11 | PreCheck: func() { testAccPreCheck(t) }, 12 | ProviderFactories: providerFactories, 13 | Steps: []resource.TestStep{ 14 | { 15 | Config: testAccResourceRun, 16 | }, 17 | }, 18 | }) 19 | } 20 | 21 | const testAccResourceRun = ` 22 | resource "multispace_run" "root" { 23 | organization = "multispace-test" 24 | workspace = "root" 25 | } 26 | 27 | resource "multispace_run" "A" { 28 | organization = "multispace-test" 29 | workspace = "A" 30 | depends_on = [multispace_run.root] 31 | manual_confirm = true 32 | } 33 | 34 | resource "multispace_run" "B" { 35 | organization = "multispace-test" 36 | workspace = "B" 37 | depends_on = [multispace_run.A] 38 | } 39 | 40 | resource "multispace_run" "C" { 41 | organization = "multispace-test" 42 | workspace = "C" 43 | depends_on = [multispace_run.A] 44 | } 45 | ` 46 | -------------------------------------------------------------------------------- /internal/provider/wait.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | // Much of this implementation is taken from the core Terraform project, 4 | // licensed MPL2. Slight modifications have been made. 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "log" 10 | "math" 11 | "time" 12 | 13 | tfe "github.com/hashicorp/go-tfe" 14 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 15 | ) 16 | 17 | var ( 18 | backoffMin = 1000.0 19 | backoffMax = 3000.0 20 | runPollInterval = 3 * time.Second 21 | ) 22 | 23 | // backoff will perform exponential backoff based on the iteration and 24 | // limited by the provided min and max (in milliseconds) durations. 25 | func backoff(min, max float64, iter int) time.Duration { 26 | backoff := math.Pow(2, float64(iter)/5) * min 27 | if backoff > max { 28 | backoff = max 29 | } 30 | return time.Duration(backoff) * time.Millisecond 31 | } 32 | 33 | func waitForRun( 34 | ctx context.Context, 35 | client *tfe.Client, 36 | orgName string, 37 | r *tfe.Run, 38 | w *tfe.Workspace, 39 | opPlan bool, 40 | terminal []tfe.RunStatus, 41 | progress []tfe.RunStatus, 42 | ) (*tfe.Run, diag.Diagnostics) { 43 | if progress == nil { 44 | progress = []tfe.RunStatus{tfe.RunPending, tfe.RunConfirmed} 45 | } 46 | 47 | started := time.Now() 48 | updated := started 49 | for i := 0; ; i++ { 50 | select { 51 | case <-ctx.Done(): 52 | return r, diag.FromErr(ctx.Err()) 53 | case <-time.After(backoff(backoffMin, backoffMax, i)): 54 | // Timer up, show status 55 | } 56 | 57 | // Retrieve the run to get its current status. 58 | r, err := client.Runs.Read(ctx, r.ID) 59 | if err != nil { 60 | return r, diag.Errorf("Failed to retrieve run: %s", err) 61 | } 62 | 63 | // If we have terminal states and we reached any, we're done. 64 | for _, s := range terminal { 65 | if r.Status == s { 66 | log.Printf("[DEBUG] reached terminal state %q", r.Status) 67 | return r, nil 68 | } 69 | } 70 | 71 | // If we have progressive states and we're not any of those, then 72 | // exit early. 73 | found := false 74 | for _, s := range progress { 75 | if r.Status == s { 76 | found = true 77 | break 78 | } 79 | } 80 | if !found { 81 | log.Printf("[DEBUG] non-progressive state, exiting %q", r.Status) 82 | return r, nil 83 | } 84 | 85 | // Check if 30 seconds have passed since the last update. 86 | current := time.Now() 87 | if i == 0 || current.Sub(updated).Seconds() > 30 { 88 | updated = current 89 | position := 0 90 | elapsed := "" 91 | 92 | // Calculate and set the elapsed time. 93 | if i > 0 { 94 | elapsed = fmt.Sprintf( 95 | " (%s elapsed)", current.Sub(started).Truncate(30*time.Second)) 96 | } 97 | 98 | // Retrieve the workspace used to run this operation in. 99 | w, err = client.Workspaces.ReadByID(ctx, w.ID) 100 | if err != nil { 101 | return nil, diag.Errorf("Failed to retrieve workspace: %s", err) 102 | } 103 | 104 | // If the workspace is locked the run will not be queued and we can 105 | // update the status without making any expensive calls. 106 | if w.Locked && w.CurrentRun != nil { 107 | cr, err := client.Runs.Read(ctx, w.CurrentRun.ID) 108 | if err != nil { 109 | return r, diag.Errorf("Failed to retrieve current run: %s", err) 110 | } 111 | if cr.Status == tfe.RunPending { 112 | log.Printf( 113 | "[DEBUG] Waiting for the manually locked workspace to " + 114 | "be unlocked..." + elapsed) 115 | continue 116 | } 117 | } 118 | 119 | // Skip checking the workspace queue when we are the current run. 120 | if w.CurrentRun == nil || w.CurrentRun.ID != r.ID { 121 | found := false 122 | options := tfe.RunListOptions{} 123 | runlist: 124 | for { 125 | rl, err := client.Runs.List(ctx, w.ID, options) 126 | if err != nil { 127 | return r, diag.Errorf("Failed to retrieve run list: %s", err) 128 | } 129 | 130 | // Loop through all runs to calculate the workspace queue position. 131 | for _, item := range rl.Items { 132 | if !found { 133 | if r.ID == item.ID { 134 | found = true 135 | } 136 | continue 137 | } 138 | 139 | // If the run is in a final state, ignore it and continue. 140 | switch item.Status { 141 | case tfe.RunApplied, tfe.RunCanceled, tfe.RunDiscarded, tfe.RunErrored: 142 | continue 143 | case tfe.RunPlanned: 144 | if opPlan { 145 | continue 146 | } 147 | } 148 | 149 | // Increase the workspace queue position. 150 | position++ 151 | 152 | // Stop searching when we reached the current run. 153 | if w.CurrentRun != nil && w.CurrentRun.ID == item.ID { 154 | break runlist 155 | } 156 | } 157 | 158 | // Exit the loop when we've seen all pages. 159 | if rl.CurrentPage >= rl.TotalPages { 160 | break 161 | } 162 | 163 | // Update the page number to get the next page. 164 | options.PageNumber = rl.NextPage 165 | } 166 | 167 | if position > 0 { 168 | log.Printf( 169 | "[INFO] Waiting for %d run(s) to finish before being queued...%s", 170 | position, 171 | elapsed, 172 | ) 173 | continue 174 | } 175 | } 176 | 177 | options := tfe.RunQueueOptions{} 178 | search: 179 | for { 180 | rq, err := client.Organizations.RunQueue(ctx, orgName, options) 181 | if err != nil { 182 | return r, diag.Errorf("Failed to retrieve queue: %s", err) 183 | } 184 | 185 | // Search through all queued items to find our run. 186 | for _, item := range rq.Items { 187 | if r.ID == item.ID { 188 | position = item.PositionInQueue 189 | break search 190 | } 191 | } 192 | 193 | // Exit the loop when we've seen all pages. 194 | if rq.CurrentPage >= rq.TotalPages { 195 | break 196 | } 197 | 198 | // Update the page number to get the next page. 199 | options.PageNumber = rq.NextPage 200 | } 201 | 202 | if position > 0 { 203 | c, err := client.Organizations.Capacity(ctx, orgName) 204 | if err != nil { 205 | return r, diag.Errorf("Failed to retrieve capacity: %s", err) 206 | } 207 | log.Printf( 208 | "[INFO] Waiting for %d queued run(s) to finish before starting...%s", 209 | position-c.Running, 210 | elapsed, 211 | ) 212 | continue 213 | } 214 | 215 | log.Printf("[DEBUG] Waiting for the run to start...%s", elapsed) 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "log" 7 | 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" 9 | 10 | "github.com/mitchellh/terraform-provider-multispace/internal/provider" 11 | ) 12 | 13 | // Run "go generate" to format example terraform files and generate the docs for the registry/website 14 | 15 | // If you do not have terraform installed, you can remove the formatting command, but its suggested to 16 | // ensure the documentation is formatted properly. 17 | //go:generate terraform fmt -recursive ./examples/ 18 | 19 | // Run the docs generation tool, check its repository for more information on how it works and how docs 20 | // can be customized. 21 | //go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs 22 | 23 | var ( 24 | // these will be set by the goreleaser configuration 25 | // to appropriate values for the compiled binary 26 | version string = "dev" 27 | 28 | // goreleaser can also pass the specific commit if you want 29 | // commit string = "" 30 | ) 31 | 32 | func main() { 33 | var debugMode bool 34 | 35 | flag.BoolVar(&debugMode, "debug", false, "set to true to run the provider with support for debuggers like delve") 36 | flag.Parse() 37 | 38 | opts := &plugin.ServeOpts{ProviderFunc: provider.New(version)} 39 | 40 | if debugMode { 41 | err := plugin.Debug(context.Background(), "registry.terraform.io/mitchellh/multispace", opts) 42 | if err != nil { 43 | log.Fatal(err.Error()) 44 | } 45 | return 46 | } 47 | 48 | plugin.Serve(opts) 49 | } 50 | -------------------------------------------------------------------------------- /templates/index.md.tmpl: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "" 3 | page_title: "Provider: multispace" 4 | description: |- 5 | The `multispace` Terraform provider implements resources to help work with multi-workspace workflows in Terraform Cloud (or Enterprise) with pure Terraform. 6 | --- 7 | 8 | # Multispace Provider 9 | 10 | The `multispace` Terraform provider implements resources to help work 11 | with multi-workspace workflows in Terraform Cloud (or Enterprise). 12 | The goal of the provider is to make it easy to perform cascading 13 | creation/deletes in the proper order across a series of dependent 14 | Terraform workspaces. 15 | 16 | For more details on motivation, see the ["why?" section](#why). 17 | 18 | **Warning:** Despite my affiliation with HashiCorp, this is **NOT** an official 19 | HashiCorp project and is not supported by HashiCorp. This was created on 20 | my personal time for personal use cases. 21 | 22 | {{ .SchemaMarkdown | trimspace }} 23 | 24 | ## Usage 25 | 26 | The example below cascades applies and destroys across multiple workspaces. 27 | 28 | The recommended usage includes pairing this with the 29 | [tfe provider](https://registry.terraform.io/providers/hashicorp/tfe/latest). 30 | The `tfe` provider is used to configure your workspaces, and the 31 | `multispace` provider is used to create a tree of workspaces that 32 | are initialized together. 33 | 34 | **Note on usage:** I usually only use this to manage the create/destroy 35 | lifecycle today. The steady-state modification workflow uses the standard 36 | Terraform Cloud VCS-driven workflows. This provider just helps me stand up 37 | my initial environments and subsequently tear them down. 38 | 39 | ```hcl 40 | resource "multispace_run" "root" { 41 | # Use string workspace names here and not data sources so that 42 | # you can define the multispace runs before the workspace even exists. 43 | workspace = "tfc" 44 | organization = "my-org" 45 | } 46 | 47 | resource "multispace_run" "physical" { 48 | organization = "my-org" 49 | workspace = "k8s-physical" 50 | depends_on = [multispace_run.root] 51 | 52 | retry = false 53 | } 54 | 55 | resource "multispace_run" "core" { 56 | organization = "my-org" 57 | workspace = "k8s-core" 58 | depends_on = [multispace_run.physical] 59 | } 60 | 61 | resource "multispace_run" "dns" { 62 | organization = "my-org" 63 | workspace = "dns" 64 | depends_on = [multispace_run.root] 65 | manual_confirm = true 66 | } 67 | 68 | resource "multispace_run" "ingress" { 69 | organization = "my-org" 70 | workspace = "ingress" 71 | depends_on = [multispace_run.core, multispace_run.dns] 72 | } 73 | ``` 74 | 75 | ## Why? 76 | 77 | Multiple [workspaces](https://www.terraform.io/docs/cloud/workspaces/index.html) 78 | are my recommended approach to working with Terraform. Small, focused workspaces 79 | make Terraform runs fast, limit the blast radius, and enable easier 80 | work separation by teams. The [`terraform_remote_state` data source](https://www.terraform.io/docs/language/state/remote-state-data.html) 81 | can be used to pass outputs from one workspace to another workspace. This 82 | enables a clean separation of responsibilities. This is also 83 | [officially recommended by Terraform](https://www.terraform.io/docs/cloud/guides/recommended-practices/part1.html). 84 | 85 | I also use multiple workspaces as a way to model **environments**: dev, 86 | staging, production, etc. An environment to me is a collection of many 87 | workspaces working together to create a working environment. For example, 88 | one project of mine has the following workspaces that depend on each other 89 | to create a full environment: k8s-physical, k8s-core, dns, metrics, etc. 90 | 91 | **The problem statement** is that I do not have a good way to create my 92 | workspaces, create them all at once in the right order, and then destroy them 93 | if I'm done with the environment. Without this provider, I have to manually 94 | click through the Terraform Cloud UI. 95 | 96 | With this provider, I can now create a single Terraform module that is used 97 | to launch a _complete environment_ for a project, composed of multiple workspaces. 98 | And I can destroy that entire environment with a `terraform destroy`, which 99 | cascades a destroy through all the workspaces in the correct order thanks 100 | to Terraform. 101 | 102 | Note that Terraform Cloud does provide [run triggers](https://www.terraform.io/docs/cloud/workspaces/run-triggers.html) 103 | but this doesn't quite solve my problem: I don't generally want run triggers, 104 | I just want to mainly do what I'd describe as a "cascading apply/destroy" 105 | for creation/destruction. For steady-state modifications once an environment 106 | exists, I use the typical Terraform Cloud VCS-driven workflow (which may or 107 | may not involve run triggers at that point). 108 | 109 | -------------------------------------------------------------------------------- /templates/resources/run.md.tmpl: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "" 3 | page_title: "Resource: multispace_run" 4 | description: |- 5 | A `multispace_run` manages the initialization and destruction of a Terraform workspace. This will queue and run an `apply` once (only if no other applies have been run) and on destroy will queue and run a `destroy` run. 6 | --- 7 | 8 | # Resource: {{ .Name }} 9 | 10 | A `multispace_run` runs one `apply` on creation and one `destroy` on 11 | destruction. This can be used to enable cascading initialization of 12 | Terraform workspaces. 13 | 14 | The workspace must be created prior to the execution of this resource, 15 | but it can be created dynamically at Terraform apply-time using the 16 | [tfe provider](https://registry.terraform.io/providers/hashicorp/tfe/latest). 17 | 18 | ~> **Warning!** This will _auto-apply_ a create on creation and a destroy 19 | on destruction. You should be very careful about what is allowed to trigger 20 | this resource and when. Note that while I use the term "auto-apply", the 21 | [auto-apply workspace setting](https://www.terraform.io/docs/cloud/workspaces/settings.html#auto-apply-and-manual-apply) 22 | _does NOT_ need to be enabled and is unused by this provider. 23 | 24 | ## Existing Workspaces 25 | 26 | If you use a `multispace_run` on a workspace that already exist and has 27 | had a successful apply, `multispace_run` will queue _another_ apply. This 28 | provider expects to "own" exactly one apply. Therefore, be careful if 29 | using this with existing Terraform workspaces. 30 | 31 | A future version of this provider will change this default to skip 32 | if the last created state version was from a non-destroy run. The current 33 | version **does not do this.** 34 | 35 | ## Timeouts 36 | 37 | Workspace creation can take a long time. The default timeouts for the 38 | create and destroy of a workspace is set to 15 minutes. This can be 39 | customized using the 40 | [resource timeouts configuration](https://www.terraform.io/docs/language/resources/syntax.html#operation-timeouts). 41 | 42 | ## Example Usage: Cascading Workspaces 43 | 44 | ```hcl 45 | resource "multispace_run" "root" { 46 | # Use string workspace names here and not data sources so that 47 | # you can define the multispace runs before the workspace even exists. 48 | organization = "my-org" 49 | workspace = "tfc" 50 | } 51 | 52 | resource "multispace_run" "physical" { 53 | organization = "my-org" 54 | workspace = "k8s-physical" 55 | depends_on = [multispace_run.root] 56 | } 57 | 58 | resource "multispace_run" "core" { 59 | organization = "my-org" 60 | workspace = "k8s-core" 61 | depends_on = [multispace_run.physical] 62 | } 63 | 64 | resource "multispace_run" "dns" { 65 | organization = "my-org" 66 | workspace = "dns" 67 | depends_on = [multispace_run.root] 68 | } 69 | 70 | resource "multispace_run" "ingress" { 71 | organization = "my-org" 72 | workspace = "ingress" 73 | depends_on = [multispace_run.core, multispace_run.dns] 74 | } 75 | ``` 76 | 77 | ## Example Usage: Run Retry 78 | 79 | Run retrying is enabled by default and no further configuration is required. 80 | Retrying causes `multispace_run` to retry if there is an error during the 81 | plan or apply. This can be customized using the `retry_*` fields. 82 | 83 | ```hcl 84 | resource "multispace_run" "root" { 85 | organization = "my-org" 86 | workspace = "tfc" 87 | 88 | retry_attempts = 3 89 | retry_backoff_min = 15 90 | } 91 | ``` 92 | 93 | ## Example Usage: Manual Confirmation 94 | 95 | You may want to manually confirm the plan or apply of some resources. 96 | If you specify the `manual_confirm` field to true, then this resource will 97 | wait for a human to manually confirm the run it queued. This requires a human 98 | to watch the workspace, review, and confirm the plan within a reasonable 99 | amount of time. 100 | 101 | -> **Be careful of resource timeouts.** It is very possible for Terraform 102 | to timeout while waiting for confirmation. Please set your timeouts accordingly. 103 | 104 | ```hcl 105 | resource "multispace_run" "root" { 106 | organization = "my-org" 107 | workspace = "tfc" 108 | manual_confirm = true 109 | } 110 | ``` 111 | 112 | {{ .SchemaMarkdown | trimspace }} 113 | -------------------------------------------------------------------------------- /test/noop/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | random = { 4 | source = "hashicorp/random" 5 | version = "3.1.0" 6 | } 7 | } 8 | } 9 | 10 | resource "random_pet" "pet" {} 11 | 12 | output "pet" { 13 | value = random_pet.pet.id 14 | } 15 | -------------------------------------------------------------------------------- /test/tfcinit/.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/hashicorp/random" { 5 | version = "3.1.0" 6 | constraints = "3.1.0" 7 | hashes = [ 8 | "h1:BZMEPucF+pbu9gsPk0G0BHx7YP04+tKdq2MrRDF1EDM=", 9 | "zh:2bbb3339f0643b5daa07480ef4397bd23a79963cc364cdfbb4e86354cb7725bc", 10 | "zh:3cd456047805bf639fbf2c761b1848880ea703a054f76db51852008b11008626", 11 | "zh:4f251b0eda5bb5e3dc26ea4400dba200018213654b69b4a5f96abee815b4f5ff", 12 | "zh:7011332745ea061e517fe1319bd6c75054a314155cb2c1199a5b01fe1889a7e2", 13 | "zh:738ed82858317ccc246691c8b85995bc125ac3b4143043219bd0437adc56c992", 14 | "zh:7dbe52fac7bb21227acd7529b487511c91f4107db9cc4414f50d04ffc3cab427", 15 | "zh:a3a9251fb15f93e4cfc1789800fc2d7414bbc18944ad4c5c98f466e6477c42bc", 16 | "zh:a543ec1a3a8c20635cf374110bd2f87c07374cf2c50617eee2c669b3ceeeaa9f", 17 | "zh:d9ab41d556a48bd7059f0810cf020500635bfc696c9fc3adab5ea8915c1d886b", 18 | "zh:d9e13427a7d011dbd654e591b0337e6074eef8c3b9bb11b2e39eaaf257044fd7", 19 | "zh:f7605bd1437752114baf601bdf6931debe6dc6bfe3006eb7e9bb9080931dca8a", 20 | ] 21 | } 22 | 23 | provider "registry.terraform.io/hashicorp/tfe" { 24 | version = "0.26.1" 25 | constraints = "~> 0.26.1" 26 | hashes = [ 27 | "h1:EI3qNBH48uHsbsFQsz7b0EjtJsMEbTP+6L2AHKE6/pc=", 28 | "zh:0819d398848bd29384364c71a2722014920a5d43a2758574472923a27303b2d2", 29 | "zh:101db61eac908bd0b35a68cdf54ef647c7e25d9837e4bff8b667d65452bad217", 30 | "zh:35c9aee4615ba355b554ee9545d805f34d9d2288eea84205f72a9b0215657286", 31 | "zh:58870295ff4dd39881ad962a610b50205aaa4bc546ee63871659ee76fccec239", 32 | "zh:92d3e95cd136c2eb9e6a6f644e9ca0cd7689ba84013a3354d0dd6fe391bca5aa", 33 | "zh:9cf2f7491f8ca9c41d762114aa41d545a9bfff822a8a1ba1279feb8987c31e1d", 34 | "zh:a3bfded94d10a8271c7d583190f2e5e5af527fa1273348dfc49287b1d90ef7eb", 35 | "zh:b1f96239fa4caca4f3d853835da49f689b962e0be6b2aa60e3bed53f940c19fa", 36 | "zh:db079ec70f87481c2e45fa59c48fe51b64f1ddc57712b2def03f4d487882fa18", 37 | "zh:dd582833f346424d242e1f3401b4e2210cef0cde8ea8f1906b375e1dd493f60f", 38 | "zh:eb0182431c51e958e368c002a9789310dcdecf38de16d24d6c04820284791d5e", 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /test/tfcinit/README.md: -------------------------------------------------------------------------------- 1 | This Terraform module can be initialized to a Terraform workspace 2 | and used to initialize other Terraform workspaces used for the 3 | acceptance tests of the multispace provider. 4 | -------------------------------------------------------------------------------- /test/tfcinit/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | tfe = { 4 | source = "hashicorp/tfe" 5 | version = "~> 0.26.1" 6 | } 7 | } 8 | } 9 | 10 | locals { 11 | # All the workspaces we will create. They all do the same thing, 12 | # which is run the "noop" module which does [almost] nothing. 13 | workspaces = ["root", "A", "B", "C"] 14 | } 15 | 16 | resource "tfe_organization" "org" { 17 | name = "multispace-test" 18 | email = var.email 19 | } 20 | 21 | resource "tfe_oauth_client" "client" { 22 | organization = tfe_organization.org.name 23 | service_provider = "github" 24 | http_url = "https://github.com" 25 | api_url = "https://api.github.com" 26 | oauth_token = var.oauth_token 27 | } 28 | 29 | resource "tfe_workspace" "ws" { 30 | for_each = toset(local.workspaces) 31 | name = each.value 32 | organization = tfe_organization.org.name 33 | working_directory = "test/noop" 34 | queue_all_runs = false 35 | 36 | vcs_repo { 37 | identifier = var.github_repo 38 | oauth_token_id = tfe_oauth_client.client.oauth_token_id 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/tfcinit/variables.tf: -------------------------------------------------------------------------------- 1 | variable "email" { 2 | type = string 3 | description = "Email used for Terraform Cloud." 4 | } 5 | 6 | variable "github_repo" { 7 | type = string 8 | description = "Multispace provider repository. This must be forked." 9 | default = "mitchellh/terraform-provider-multispace" 10 | } 11 | 12 | variable "oauth_token" { 13 | type = string 14 | description = <