Merge remote-tracking branch 'origin/main' into update-consul

This commit is contained in:
Rémi Lapeyre 2021-07-28 12:18:01 +02:00
commit da6717761e
119 changed files with 2919 additions and 586 deletions

View File

@ -11,14 +11,14 @@ Hi there,
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.
If your issue relates to Terraform Cloud/Enterprise, please contact tf-cloud@hashicorp.support.
If your issue relates to a specific Terraform provider, please open it in the provider's own repository. The index of providers is at https://github.com/terraform-providers.
If your issue relates to a specific Terraform provider, please open it in the provider's own repository. The index of providers is at https://registry.terraform.io/browse/providers.
To fix problems, we need clear reproduction cases - we need to be able to see it happen locally. A reproduction case is ideally something a Terraform Core engineer can git-clone or copy-paste and run immediately, without inventing any details or context.
* A short example can be directly copy-pasteable; longer examples should be in separate git repositories, especially if multiple files are needed
* Please include all needed context. For example, if you figured out that an expression can cause a crash, put the expression in a variable definition or a resource
* Set defaults on (or omit) any variables. The person reproducing it should not need to invent variable settings
* If multiple steps are required, such as running terraform twice, consider scripting it in a simple shell script. For example, see [this case](https://github.com/danieldreier/terraform-issue-reproductions/tree/master/25719). Providing a script can be easier than explaining what changes to make to the config between runs.
* If multiple steps are required, such as running terraform twice, consider scripting it in a simple shell script. Providing a script can be easier than explaining what changes to make to the config between runs.
* Omit any unneeded complexity: remove variables, conditional statements, functions, modules, providers, and resources that are not needed to trigger the bug
* When possible, use the [null resource](https://www.terraform.io/docs/providers/null/resource.html) provider rather than a real provider in order to minimize external dependencies. We know this isn't always feasible. The Terraform Core team doesn't have deep domain knowledge in every provider, or access to every cloud platform for reproduction cases.

View File

@ -7,7 +7,7 @@
/internal/backend/remote-state/consul @hashicorp/consul @remilapeyre
/internal/backend/remote-state/cos @likexian
/internal/backend/remote-state/etcdv2 Unmaintained
/internal/backend/remote-state/etcdv3 @bmcustodio
/internal/backend/remote-state/etcdv3 Unmaintained
/internal/backend/remote-state/gcs @hashicorp/terraform-google
/internal/backend/remote-state/http @hashicorp/terraform-core
/internal/backend/remote-state/manta Unmaintained

View File

@ -21,11 +21,11 @@ The key features of Terraform are:
- **Change Automation**: Complex changesets can be applied to your infrastructure with minimal human interaction. With the previously mentioned execution plan and resource graph, you know exactly what Terraform will change and in what order, avoiding many possible human errors.
For more information, see the [introduction section](http://www.terraform.io/intro) of the Terraform website.
For more information, see the [introduction section](https://www.terraform.io/intro) of the Terraform website.
Getting Started & Documentation
-------------------------------
Documentation is available on the [Terraform website](http://www.terraform.io):
Documentation is available on the [Terraform website](https://www.terraform.io):
- [Intro](https://www.terraform.io/intro/index.html)
- [Docs](https://www.terraform.io/docs/index.html)

28
go.mod
View File

@ -17,19 +17,13 @@ require (
github.com/apparentlymart/go-userdirs v0.0.0-20200915174352-b0c018a67c13
github.com/apparentlymart/go-versions v1.0.1
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/aws/aws-sdk-go v1.37.0
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
github.com/bgentry/speakeasy v0.1.0
github.com/bmatcuk/doublestar v1.1.5
github.com/boltdb/bolt v1.3.1 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
github.com/coreos/bbolt v1.3.0 // indirect
github.com/coreos/etcd v3.3.10+incompatible
github.com/coreos/go-semver v0.2.0 // indirect
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d // indirect
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f
github.com/davecgh/go-spew v1.1.1
github.com/dylanmei/iso8601 v0.1.0 // indirect
github.com/dylanmei/winrmtest v0.0.0-20190225150635-99b7fe2fddf1
@ -42,13 +36,9 @@ require (
github.com/gophercloud/gophercloud v0.10.1-0.20200424014253-c3bfe50899e5
github.com/gophercloud/utils v0.0.0-20200423144003-7c72efc7435d
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
github.com/gorilla/websocket v1.4.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.8.5 // indirect
github.com/hashicorp/aws-sdk-go-base v0.6.0
github.com/hashicorp/consul/api v1.9.1
github.com/hashicorp/consul/sdk v0.8.0 // indirect
github.com/hashicorp/consul/sdk v0.8.0
github.com/hashicorp/errwrap v1.1.0
github.com/hashicorp/go-azure-helpers v0.14.0
github.com/hashicorp/go-checkpoint v0.5.0
@ -63,12 +53,11 @@ require (
github.com/hashicorp/go-uuid v1.0.1
github.com/hashicorp/go-version v1.2.1
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f
github.com/hashicorp/hcl/v2 v2.10.0
github.com/hashicorp/hcl/v2 v2.10.1
github.com/hashicorp/terraform-config-inspect v0.0.0-20210209133302-4fd17a0faac2
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
github.com/jmespath/go-jmespath v0.4.0
github.com/jonboulle/clockwork v0.1.0 // indirect
github.com/joyent/triton-go v0.0.0-20180313100802-d8f9c0314926
github.com/jtolds/gls v4.2.1+incompatible // indirect
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
@ -91,30 +80,21 @@ require (
github.com/mitchellh/reflectwalk v1.0.1
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/packer-community/winrmcp v0.0.0-20180921211025-c76d91c1e7db
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c // indirect
github.com/pkg/browser v0.0.0-20201207095918-0426ae3fba23
github.com/pkg/errors v0.9.1
github.com/posener/complete v1.2.3
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 // indirect
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a // indirect
github.com/soheilhy/cmux v0.1.4 // indirect
github.com/spf13/afero v1.2.2
github.com/tencentcloud/tencentcloud-sdk-go v3.0.82+incompatible
github.com/tencentyun/cos-go-sdk-v5 v0.0.0-20190808065407-f07404cefc8c
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 // indirect
github.com/tombuildsstuff/giovanni v0.15.1
github.com/ugorji/go v0.0.0-20180813092308-00b869d2f4a5 // indirect
github.com/xanzy/ssh-agent v0.2.1
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18 // indirect
github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557
github.com/zclconf/go-cty v1.9.0
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b
github.com/zclconf/go-cty-yaml v1.0.2
go.uber.org/atomic v1.3.2 // indirect
go.uber.org/multierr v1.1.0 // indirect
go.uber.org/zap v1.9.1 // indirect
go.etcd.io/etcd v0.5.0-alpha.5.0.20210428180535-15715dcf1ace
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/mod v0.4.2
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110

106
go.sum
View File

@ -135,16 +135,15 @@ github.com/aws/aws-sdk-go v1.37.0 h1:GzFnhOIsrGyQ69s7VgqtrG2BG8v7X7vwB3Xpbd/DBBk
github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bmatcuk/doublestar v1.1.5 h1:2bNwBOmhyFEFcoB3tGvTD5xanq+4kyOZlB8wFYbMjkk=
github.com/bmatcuk/doublestar v1.1.5/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
@ -157,16 +156,17 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/coreos/bbolt v1.3.0 h1:HIgH5xUWXT914HCI671AxuTTqjj64UOFr7pHn48LUTI=
github.com/coreos/bbolt v1.3.0/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d h1:t5Wuyh53qYyg9eqn4BbnlIT+vmhyww0TatL+zT3uWgI=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -178,6 +178,8 @@ github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQ
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4 h1:qk/FSDDxo05wdJH28W+p5yivv7LuLYLRXPPD8KQCtZs=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dylanmei/iso8601 v0.1.0 h1:812NGQDBcqquTfH5Yeo7lwR0nzx/cKdsmf3qMjPURUI=
github.com/dylanmei/iso8601 v0.1.0/go.mod h1:w9KhXSgIyROl1DefbMYIE7UVSIvELTbMrCfx+QkYnoQ=
github.com/dylanmei/winrmtest v0.0.0-20190225150635-99b7fe2fddf1 h1:r1oACdS2XYiAWcfF8BJXkoU8l1J71KehGR+d99yWEDA=
@ -192,7 +194,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
@ -200,7 +201,6 @@ github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTg
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@ -222,6 +222,7 @@ github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
@ -313,19 +314,17 @@ github.com/gophercloud/utils v0.0.0-20200423144003-7c72efc7435d h1:fduaPzWwIfvOM
github.com/gophercloud/utils v0.0.0-20200423144003-7c72efc7435d/go.mod h1:ehWUbLQJPqS0Ep+CxeD559hsm9pthPXadJNKwZkp43w=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 h1:z53tR0945TRRQO/fLEVPI6SMv7ZflF0TEaTAoU7tOzg=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.8.5 h1:2+KSC78XiO6Qy0hIjfc1OD9H+hsaJdJlb8Kqsd41CTE=
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/aws-sdk-go-base v0.6.0 h1:qmUbzM36msbBF59YctwuO5w0M2oNXjlilgKpnEhx1uw=
github.com/hashicorp/aws-sdk-go-base v0.6.0/go.mod h1:2fRjWDv3jJBeN6mVWFHV6hFTNeFBx2gpDLQaZNxUVAY=
github.com/hashicorp/consul v0.0.0-20171026175957-610f3c86a089 h1:1eDpXAxTh0iPv+1kc9/gfSI2pxRERDsTk/lNGolwHn8=
github.com/hashicorp/consul v0.0.0-20171026175957-610f3c86a089/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI=
github.com/hashicorp/consul/api v1.9.1 h1:SngrdG2L62qqLsUz85qcPhFZ78rPf8tcD5qjMgs6MME=
github.com/hashicorp/consul/api v1.9.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
github.com/hashicorp/consul/sdk v0.8.0 h1:OJtKBtEjboEZvG6AOUdh4Z1Zbyu0WcxQ0qatRrZHTVU=
@ -347,8 +346,6 @@ github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v0.15.0 h1:qMuK0wxsoW4D0ddCCYwPSTm4KQv1X1ke3WmPWZ0Mvsk=
github.com/hashicorp/go-hclog v0.15.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v0.0.0-20180129170900-7f3cd4390caa h1:0nA8i+6Rwqaq9xlpmVxxTwk6rxiEhX+E6Wh4vPNHiS8=
github.com/hashicorp/go-immutable-radix v0.0.0-20180129170900-7f3cd4390caa/go.mod h1:6ij3Z20p+OhOkCSrA0gImAWoHYQRGbnlcuk6XYTiaRw=
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
@ -362,16 +359,13 @@ github.com/hashicorp/go-plugin v1.4.1 h1:6UltRQlLN9iZO513VveELp5xyaFxVD2+1OVylE+
github.com/hashicorp/go-plugin v1.4.1/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
github.com/hashicorp/go-retryablehttp v0.5.2 h1:AoISa4P4IsW0/m4T6St8Yw38gTl5GtBAgfkhYh1xAz4=
github.com/hashicorp/go-retryablehttp v0.5.2/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
github.com/hashicorp/go-slug v0.4.1 h1:/jAo8dNuLgSImoLXaX7Od7QB4TfYCVPam+OpAt5bZqc=
github.com/hashicorp/go-slug v0.4.1/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8=
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86 h1:7YOlAIO2YWnJZkQp7B5eFykaIY7C9JndqAFQyVV5BhM=
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-tfe v0.15.0 h1:vdnz1NjOhvmap+cj8iPsL8SbS4iFFVuNYFkGpF5SdoA=
@ -390,17 +384,14 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f h1:UdxlrJz4JOnY8W+DbLISwf2B8WXEolNRA8BGCwI9jws=
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
github.com/hashicorp/hcl/v2 v2.0.0/go.mod h1:oVVDG71tEinNGYCxinCYadcmKU9bglqW9pV3txagJ90=
github.com/hashicorp/hcl/v2 v2.10.0 h1:1S1UnuhDGlv3gRFV4+0EdwB+znNP5HmcGbIqwnSCByg=
github.com/hashicorp/hcl/v2 v2.10.0/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
github.com/hashicorp/hcl/v2 v2.10.1 h1:h4Xx4fsrRE26ohAk/1iGF/JBqRQbyUqu5Lvj60U54ys=
github.com/hashicorp/hcl/v2 v2.10.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
github.com/hashicorp/jsonapi v0.0.0-20210518035559-1e50d74c8db3 h1:mzwkutymYIXR5oQT9YnfbLuuw7LZmksiHKRPUTN5ijo=
github.com/hashicorp/jsonapi v0.0.0-20210518035559-1e50d74c8db3/go.mod h1:Yog5+CPEM3c99L1CL2CFCYoSzgWm5vTU58idbRUaLik=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
github.com/hashicorp/memberlist v0.1.0 h1:qSsCiC0WYD39lbSitKNt40e30uorm2Ss/d4JGU1hzH8=
github.com/hashicorp/memberlist v0.1.0/go.mod h1:ncdBp14cuox2iFOq3kDiquKU6fqsTBc3W6JvZwjxxsE=
github.com/hashicorp/memberlist v0.2.2 h1:5+RffWKwqJ71YPu9mWsF7ZOscZmwfasdA8kbdC7AO2g=
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/serf v0.0.0-20160124182025-e4ec8cc423bb h1:ZbgmOQt8DOg796figP87/EFCVx2v2h9yRvwHF/zceX4=
github.com/hashicorp/serf v0.0.0-20160124182025-e4ec8cc423bb/go.mod h1:h/Ru6tmZazX7WO/GDmwdpS975F019L4t5ng5IgwbNrE=
github.com/hashicorp/serf v0.9.5 h1:EBWvyu9tcRszt3Bxp3KNssBMP1KuHWyO51lz9+786iM=
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
github.com/hashicorp/terraform-config-inspect v0.0.0-20210209133302-4fd17a0faac2 h1:l+bLFvHjqtgNQwWxwrFX9PemGAAO2P1AGZM7zlMNvCs=
@ -418,6 +409,7 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
@ -432,6 +424,7 @@ github.com/joyent/triton-go v0.0.0-20180313100802-d8f9c0314926 h1:kie3qOosvRKqwi
github.com/joyent/triton-go v0.0.0-20180313100802-d8f9c0314926/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA=
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@ -442,6 +435,7 @@ github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVY
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.11.2 h1:MiK62aErc3gIiVEtyzKfeOHgW7atJb5g/KNX5m3c2nQ=
@ -449,8 +443,8 @@ github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
@ -477,7 +471,6 @@ github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786/go.mod h1:kCEb
github.com/masterzen/winrm v0.0.0-20200615185753-c42b5136ff88 h1:cxuVcCvCLD9yYDbRCWw0jSgh1oT6P6mv3aJDKK5o7X4=
github.com/masterzen/winrm v0.0.0-20200615185753-c42b5136ff88/go.mod h1:a2HXwefeat3evJHxFXSayvRHpYEPJYtErl4uIzfaUqY=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
@ -488,14 +481,14 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-shellwords v1.0.4 h1:xmZZyxuP+bYKAKkA9ABYXVNJ+G/Wf3R8d8vAP3LDJJk=
github.com/mattn/go-shellwords v1.0.4/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.8 h1:Zi8HNpze3NeRWH1PQV6O71YcvJRQ6j0lORO6DAEmAAI=
github.com/miekg/dns v1.0.8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/cli v1.1.2 h1:PvH+lL2B7IQ101xQL63Of8yFS2y+aDlsFcsqNc+u/Kw=
@ -543,6 +536,7 @@ github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@ -564,22 +558,20 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.1 h1:LrvDIY//XNo65Lq84G/akBuMGlawHvGBABv8f/ZN6DI=
github.com/posener/complete v1.2.1/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DKN0g6uH7E=
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 h1:D+CiwcpGTW6pL6bv6KI3KbyEyCKyS+1JWS2h8PNDnGA=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1 h1:/K3IL0Z1quvmJ7X0A1AwNEK7CRkVK3YwfOU/QAL4WGg=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
@ -589,8 +581,9 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUt
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a h1:JSvGDIbmil4Ui/dDdFBExb7/cmkNjyX5F97oglmvCDo=
@ -599,12 +592,14 @@ github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@ -617,14 +612,13 @@ github.com/tencentcloud/tencentcloud-sdk-go v3.0.82+incompatible h1:5Td2b0yfaOvw
github.com/tencentcloud/tencentcloud-sdk-go v3.0.82+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4=
github.com/tencentyun/cos-go-sdk-v5 v0.0.0-20190808065407-f07404cefc8c h1:iRD1CqtWUjgEVEmjwTMbP1DMzz1HRytOsgx/rlw/vNs=
github.com/tencentyun/cos-go-sdk-v5 v0.0.0-20190808065407-f07404cefc8c/go.mod h1:wk2XFUg6egk4tSDNZtXeKfe2G6690UVyt163PuUxBZk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 h1:lYIiVDtZnyTWlNwiAxLj0bbpTcx1BWCFhXjfsvmPdNc=
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966 h1:j6JEOq5QWFker+d7mFQYOhjTZonQ7YkLTHm56dbn+yM=
github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tombuildsstuff/giovanni v0.15.1 h1:CVRaLOJ7C/eercCrKIsarfJ4SZoGMdBL9Q2deFDUXco=
github.com/tombuildsstuff/giovanni v0.15.1/go.mod h1:0TZugJPEtqzPlMpuJHYfXY6Dq2uLPrXf98D2XQSxNbA=
github.com/ugorji/go v0.0.0-20180813092308-00b869d2f4a5 h1:cMjKdf4PxEBN9K5HaD9UMW8gkTbM0kMzkTa9SJe0WNQ=
github.com/ugorji/go v0.0.0-20180813092308-00b869d2f4a5/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/vmihailenco/msgpack v3.3.3+incompatible h1:wapg9xDUZDzGCNFlwc5SqI1rvcciqcxEHac4CYj89xI=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U=
@ -633,8 +627,8 @@ github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37w
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18 h1:MPPkRncZLN9Kh4MEFmbnK4h3BD7AUmskWv2+EeZJCCs=
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557 h1:Jpn2j6wHkC9wJv5iMfJhKqrZJx3TahFx+7sbZ7zQdxs=
github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -651,6 +645,10 @@ github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY3
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
github.com/zclconf/go-cty-yaml v1.0.2 h1:dNyg4QLTrv2IfJpm7Wtxi55ed5gLGOlPrZ6kMd51hY0=
github.com/zclconf/go-cty-yaml v1.0.2/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0=
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.5.0-alpha.5.0.20210428180535-15715dcf1ace h1:wOZR+AfzmQYNRqx1F+LL9TX8vBVzSbndRoc0tr/Bp4k=
go.etcd.io/etcd v0.5.0-alpha.5.0.20210428180535-15715dcf1ace/go.mod h1:q+i20RPAmay+xq8LJ3VMOhXCNk4YCk3V7QP91meFavw=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@ -663,8 +661,8 @@ go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -730,7 +728,6 @@ golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -789,6 +786,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -803,6 +801,7 @@ golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -860,10 +859,12 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -1024,6 +1025,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=

View File

@ -10,3 +10,9 @@ type CountAttr struct {
func (ca CountAttr) String() string {
return "count." + ca.Name
}
func (ca CountAttr) UniqueKey() UniqueKey {
return ca // A CountAttr is its own UniqueKey
}
func (ca CountAttr) uniqueKeySigil() {}

View File

@ -10,3 +10,9 @@ type ForEachAttr struct {
func (f ForEachAttr) String() string {
return "each." + f.Name
}
func (f ForEachAttr) UniqueKey() UniqueKey {
return f // A ForEachAttr is its own UniqueKey
}
func (f ForEachAttr) uniqueKeySigil() {}

View File

@ -14,6 +14,12 @@ func (v InputVariable) String() string {
return "var." + v.Name
}
func (v InputVariable) UniqueKey() UniqueKey {
return v // A InputVariable is its own UniqueKey
}
func (v InputVariable) uniqueKeySigil() {}
// Absolute converts the receiver into an absolute address within the given
// module instance.
func (v InputVariable) Absolute(m ModuleInstance) AbsInputVariableInstance {

View File

@ -14,6 +14,12 @@ func (v LocalValue) String() string {
return "local." + v.Name
}
func (v LocalValue) UniqueKey() UniqueKey {
return v // A LocalValue is its own UniqueKey
}
func (v LocalValue) uniqueKeySigil() {}
// Absolute converts the receiver into an absolute address within the given
// module instance.
func (v LocalValue) Absolute(m ModuleInstance) AbsLocalValue {

View File

@ -15,6 +15,12 @@ func (c ModuleCall) String() string {
return "module." + c.Name
}
func (c ModuleCall) UniqueKey() UniqueKey {
return c // A ModuleCall is its own UniqueKey
}
func (c ModuleCall) uniqueKeySigil() {}
// Instance returns the address of an instance of the receiver identified by
// the given key.
func (c ModuleCall) Instance(key InstanceKey) ModuleCallInstance {
@ -47,7 +53,11 @@ func (c AbsModuleCall) absMoveableSigil() {
}
func (c AbsModuleCall) String() string {
return fmt.Sprintf("%s.%s", c.Module, c.Call.Name)
if len(c.Module) == 0 {
return "module." + c.Call.Name
}
return fmt.Sprintf("%s.module.%s", c.Module, c.Call.Name)
}
func (c AbsModuleCall) Instance(key InstanceKey) ModuleInstance {
@ -79,6 +89,12 @@ func (c ModuleCallInstance) String() string {
return fmt.Sprintf("module.%s%s", c.Call.Name, c.Key)
}
func (c ModuleCallInstance) UniqueKey() UniqueKey {
return c // A ModuleCallInstance is its own UniqueKey
}
func (c ModuleCallInstance) uniqueKeySigil() {}
func (c ModuleCallInstance) Absolute(moduleAddr ModuleInstance) ModuleInstance {
ret := make(ModuleInstance, len(moduleAddr), len(moduleAddr)+1)
copy(ret, moduleAddr)
@ -118,6 +134,12 @@ func (m ModuleCallOutput) String() string {
return fmt.Sprintf("%s.%s", m.Call.String(), m.Name)
}
func (m ModuleCallOutput) UniqueKey() UniqueKey {
return m // A ModuleCallOutput is its own UniqueKey
}
func (m ModuleCallOutput) uniqueKeySigil() {}
// ModuleCallInstanceOutput is the address of a particular named output produced by
// an instance of a module call.
type ModuleCallInstanceOutput struct {
@ -139,6 +161,12 @@ func (co ModuleCallInstanceOutput) String() string {
return fmt.Sprintf("%s.%s", co.Call.String(), co.Name)
}
func (co ModuleCallInstanceOutput) UniqueKey() UniqueKey {
return co // A ModuleCallInstanceOutput is its own UniqueKey
}
func (co ModuleCallInstanceOutput) uniqueKeySigil() {}
// AbsOutputValue returns the absolute output value address that corresponds
// to the receving module call output address, once resolved in the given
// calling module.

View File

@ -275,6 +275,14 @@ func (m ModuleInstance) String() string {
return buf.String()
}
type moduleInstanceKey string
func (m ModuleInstance) UniqueKey() UniqueKey {
return moduleInstanceKey(m.String())
}
func (mk moduleInstanceKey) uniqueKeySigil() {}
// Equal returns true if the receiver and the given other value
// contains the exact same parts.
func (m ModuleInstance) Equal(o ModuleInstance) bool {

View File

@ -39,6 +39,10 @@ type MoveEndpoint struct {
relSubject AbsMoveable
}
func (e *MoveEndpoint) ObjectKind() MoveEndpointKind {
return absMoveableEndpointKind(e.relSubject)
}
func (e *MoveEndpoint) String() string {
// Our internal pseudo-AbsMovable representing the relative
// address (either ModuleInstance or AbsResourceInstance) is
@ -73,7 +77,7 @@ func (e *MoveEndpoint) MightUnifyWith(other *MoveEndpoint) bool {
// address, because the rules for whether unify can succeed depend
// only on the relative part of the addresses, not on which module
// they were declared in.
from, to := UnifyMoveEndpoints(RootModuleInstance, e, other)
from, to := UnifyMoveEndpoints(RootModule, e, other)
return from != nil && to != nil
}
@ -147,11 +151,10 @@ func ParseMoveEndpoint(traversal hcl.Traversal) (*MoveEndpoint, tfdiags.Diagnost
// UnifyMoveEndpoints takes a pair of MoveEndpoint objects representing the
// "from" and "to" addresses in a moved block, and returns a pair of
// AbsMoveable addresses guaranteed to be of the same dynamic type
// MoveEndpointInModule addresses guaranteed to be of the same dynamic type
// that represent what the two MoveEndpoint addresses refer to.
//
// moduleAddr must be the address of the module instance where the move
// was declared.
// moduleAddr must be the address of the module where the move was declared.
//
// This function deals both with the conversion from relative to absolute
// addresses and with resolving the ambiguity between no-key instance
@ -163,7 +166,7 @@ func ParseMoveEndpoint(traversal hcl.Traversal) (*MoveEndpoint, tfdiags.Diagnost
// given addresses are incompatible then UnifyMoveEndpoints returns (nil, nil),
// in which case the caller should typically report an error to the user
// stating the unification constraints.
func UnifyMoveEndpoints(moduleAddr ModuleInstance, relFrom, relTo *MoveEndpoint) (absFrom, absTo AbsMoveable) {
func UnifyMoveEndpoints(moduleAddr Module, relFrom, relTo *MoveEndpoint) (modFrom, modTo *MoveEndpointInModule) {
// First we'll make a decision about which address type we're
// ultimately trying to unify to. For our internal purposes
@ -197,17 +200,17 @@ func UnifyMoveEndpoints(moduleAddr ModuleInstance, relFrom, relTo *MoveEndpoint)
panic("unhandled move address types")
}
absFrom = relFrom.prepareAbsMoveable(moduleAddr, wantType)
absTo = relTo.prepareAbsMoveable(moduleAddr, wantType)
if absFrom == nil || absTo == nil {
modFrom = relFrom.prepareMoveEndpointInModule(moduleAddr, wantType)
modTo = relTo.prepareMoveEndpointInModule(moduleAddr, wantType)
if modFrom == nil || modTo == nil {
// if either of them failed then they both failed, to make the
// caller's life a little easier.
return nil, nil
}
return absFrom, absTo
return modFrom, modTo
}
func (e *MoveEndpoint) prepareAbsMoveable(moduleAddr ModuleInstance, wantType TargetableAddrType) AbsMoveable {
func (e *MoveEndpoint) prepareMoveEndpointInModule(moduleAddr Module, wantType TargetableAddrType) *MoveEndpointInModule {
// relAddr can only be either AbsResourceInstance or ModuleInstance, the
// internal intermediate representation produced by ParseMoveEndpoint.
relAddr := e.relSubject
@ -216,40 +219,43 @@ func (e *MoveEndpoint) prepareAbsMoveable(moduleAddr ModuleInstance, wantType Ta
case ModuleInstance:
switch wantType {
case ModuleInstanceAddrType:
ret := make(ModuleInstance, 0, len(moduleAddr)+len(relAddr))
ret = append(ret, moduleAddr...)
ret = append(ret, relAddr...)
return ret
// Since our internal representation is already a module instance,
// we can just rewrap this one.
return &MoveEndpointInModule{
SourceRange: e.SourceRange,
module: moduleAddr,
relSubject: relAddr,
}
case ModuleAddrType:
// NOTE: We're fudging a little here and using
// ModuleAddrType to represent AbsModuleCall rather
// than Module.
callerAddr := make(ModuleInstance, 0, len(moduleAddr)+len(relAddr)-1)
callerAddr = append(callerAddr, moduleAddr...)
callerAddr = append(callerAddr, relAddr[:len(relAddr)-1]...)
return AbsModuleCall{
callerAddr, callAddr := relAddr.Call()
absCallAddr := AbsModuleCall{
Module: callerAddr,
Call: ModuleCall{
Name: relAddr[len(relAddr)-1].Name,
},
Call: callAddr,
}
return &MoveEndpointInModule{
SourceRange: e.SourceRange,
module: moduleAddr,
relSubject: absCallAddr,
}
default:
return nil // can't make any other types from a ModuleInstance
}
case AbsResourceInstance:
callerAddr := make(ModuleInstance, 0, len(moduleAddr)+len(relAddr.Module))
callerAddr = append(callerAddr, moduleAddr...)
callerAddr = append(callerAddr, relAddr.Module...)
switch wantType {
case AbsResourceInstanceAddrType:
return AbsResourceInstance{
Module: callerAddr,
Resource: relAddr.Resource,
return &MoveEndpointInModule{
SourceRange: e.SourceRange,
module: moduleAddr,
relSubject: relAddr,
}
case AbsResourceAddrType:
return AbsResource{
Module: callerAddr,
Resource: relAddr.Resource.Resource,
return &MoveEndpointInModule{
SourceRange: e.SourceRange,
module: moduleAddr,
relSubject: relAddr.ContainingResource(),
}
default:
return nil // can't make any other types from an AbsResourceInstance

View File

@ -0,0 +1,33 @@
package addrs
import "fmt"
// MoveEndpointKind represents the different kinds of object that a movable
// address can refer to.
type MoveEndpointKind rune
//go:generate go run golang.org/x/tools/cmd/stringer -type MoveEndpointKind
const (
// MoveEndpointModule indicates that a move endpoint either refers to
// an individual module instance or to all instances of a particular
// module call.
MoveEndpointModule MoveEndpointKind = 'M'
// MoveEndpointResource indicates that a move endpoint either refers to
// an individual resource instance or to all instances of a particular
// resource.
MoveEndpointResource MoveEndpointKind = 'R'
)
func absMoveableEndpointKind(addr AbsMoveable) MoveEndpointKind {
switch addr := addr.(type) {
case ModuleInstance, AbsModuleCall:
return MoveEndpointModule
case AbsResourceInstance, AbsResource:
return MoveEndpointResource
default:
// The above should be exhaustive for all AbsMoveable types.
panic(fmt.Sprintf("unsupported address type %T", addr))
}
}

View File

@ -0,0 +1,319 @@
package addrs
import (
"fmt"
"strings"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// MoveEndpointInModule annotates a MoveEndpoint with the address of the
// module where it was declared, which is the form we use for resolving
// whether move statements chain from or are nested within other move
// statements.
type MoveEndpointInModule struct {
// SourceRange is the location of the physical endpoint address
// in configuration, if this MoveEndpoint was decoded from a
// configuration expresson.
SourceRange tfdiags.SourceRange
// The internals are unexported here because, as with MoveEndpoint,
// we're somewhat abusing AbsMoveable here to represent an address
// relative to the module, rather than as an absolute address.
// Conceptually, the following two fields represent a matching pattern
// for AbsMoveables where the elements of "module" behave as
// ModuleInstanceStep values with a wildcard instance key, because
// a moved block in a module affects all instances of that module.
// Unlike MoveEndpoint, relSubject in this case can be any of the
// address types that implement AbsMoveable.
module Module
relSubject AbsMoveable
}
func (e *MoveEndpointInModule) ObjectKind() MoveEndpointKind {
return absMoveableEndpointKind(e.relSubject)
}
// String produces a string representation of the object matching pattern
// represented by the reciever.
//
// Since there is no direct syntax for representing such an object matching
// pattern, this function uses a splat-operator-like representation to stand
// in for the wildcard instance keys.
func (e *MoveEndpointInModule) String() string {
if e == nil {
return ""
}
var buf strings.Builder
for _, name := range e.module {
buf.WriteString("module.")
buf.WriteString(name)
buf.WriteString("[*].")
}
buf.WriteString(e.relSubject.String())
// For consistency we'll also use the splat-like wildcard syntax to
// represent the final step being either a resource or module call
// rather than an instance, so we can more easily distinguish the two
// in the string representation.
switch e.relSubject.(type) {
case AbsModuleCall, AbsResource:
buf.WriteString("[*]")
}
return buf.String()
}
// SelectsModule returns true if the reciever directly selects either
// the given module or a resource nested directly inside that module.
//
// This is a good function to use to decide which modules in a state
// to consider when processing a particular move statement. For a
// module move the given module itself is what will move, while a
// resource move indicates that we should search each of the resources in
// the given module to see if they match.
func (e *MoveEndpointInModule) SelectsModule(addr ModuleInstance) bool {
// In order to match the given module path should be at least as
// long as the path to the module where the move endpoint was defined.
if len(addr) < len(e.module) {
return false
}
containerPart := addr[:len(e.module)]
relPart := addr[len(e.module):]
// The names of all of the steps that align with e.module must match,
// though the instance keys are wildcards for this part.
for i := range e.module {
if containerPart[i].Name != e.module[i] {
return false
}
}
// The remaining module address steps must match both name and key.
// The logic for all of these is similar but we will retrieve the
// module address differently for each type.
var relMatch ModuleInstance
switch relAddr := e.relSubject.(type) {
case ModuleInstance:
relMatch = relAddr
case AbsModuleCall:
// This one requires a little more fuss because the call effectively
// slices in two the final step of the module address.
if len(relPart) != len(relAddr.Module)+1 {
return false
}
callPart := relPart[len(relPart)-1]
if callPart.Name != relAddr.Call.Name {
return false
}
case AbsResource:
relMatch = relAddr.Module
case AbsResourceInstance:
relMatch = relAddr.Module
default:
panic(fmt.Sprintf("unhandled relative address type %T", relAddr))
}
if len(relPart) != len(relMatch) {
return false
}
for i := range relMatch {
if relPart[i] != relMatch[i] {
return false
}
}
return true
}
// CanChainFrom returns true if the reciever describes an address that could
// potentially select an object that the other given address could select.
//
// In other words, this decides whether the move chaining rule applies, if
// the reciever is the "to" from one statement and the other given address
// is the "from" of another statement.
func (e *MoveEndpointInModule) CanChainFrom(other *MoveEndpointInModule) bool {
// TODO: implement
return false
}
// NestedWithin returns true if the reciever describes an address that is
// contained within one of the objects that the given other address could
// select.
func (e *MoveEndpointInModule) NestedWithin(other *MoveEndpointInModule) bool {
// TODO: implement
return false
}
// MoveDestination considers a an address representing a module
// instance in the context of source and destination move endpoints and then,
// if the module address matches the from endpoint, returns the corresponding
// new module address that the object should move to.
//
// MoveDestination will return false in its second return value if the receiver
// doesn't match fromMatch, indicating that the given move statement doesn't
// apply to this object.
//
// Both of the given endpoints must be from the same move statement and thus
// must have matching object types. If not, MoveDestination will panic.
func (m ModuleInstance) MoveDestination(fromMatch, toMatch *MoveEndpointInModule) (ModuleInstance, bool) {
// NOTE: This implementation assumes the invariant that fromMatch and
// toMatch both belong to the same configuration statement, and thus they
// will both have the same address type and the same declaration module.
// The root module instance is not itself moveable.
if m.IsRoot() {
return nil, false
}
// The two endpoints must either be module call or module instance
// addresses, or else this statement can never match.
if fromMatch.ObjectKind() != MoveEndpointModule {
return nil, false
}
// The given module instance must have a prefix that matches the
// declaration module of the two endpoints.
if len(fromMatch.module) > len(m) {
return nil, false // too short to possibly match
}
for i := range fromMatch.module {
if fromMatch.module[i] != m[i].Name {
return nil, false // this step doesn't match
}
}
// The rest of our work will be against the part of the reciever that's
// relative to the declaration module. mRel is a weird abuse of
// ModuleInstance that represents a relative module address, similar to
// what we do for MoveEndpointInModule.relSubject.
mPrefix, mRel := m[:len(fromMatch.module)], m[len(fromMatch.module):]
// Our next goal is to split mRel into two parts: the match (if any) and
// the suffix. Our result will then replace the match with the replacement
// in toMatch while preserving the prefix and suffix.
var mSuffix, mNewMatch ModuleInstance
switch relSubject := fromMatch.relSubject.(type) {
case ModuleInstance:
if len(relSubject) > len(mRel) {
return nil, false // too short to possibly match
}
for i := range relSubject {
if relSubject[i] != mRel[i] {
return nil, false // this step doesn't match
}
}
// If we get to here then we've found a match. Since the statement
// addresses are already themselves ModuleInstance fragments we can
// just slice out the relevant parts.
mNewMatch = toMatch.relSubject.(ModuleInstance)
mSuffix = mRel[len(relSubject):]
case AbsModuleCall:
// The module instance part of relSubject must be a prefix of
// mRel, and mRel must be at least one step longer to account for
// the call step itself.
if len(relSubject.Module) > len(mRel)-1 {
return nil, false
}
for i := range relSubject.Module {
if relSubject.Module[i] != mRel[i] {
return nil, false // this step doesn't match
}
}
// The call name must also match the next step of mRel, after
// the relSubject.Module prefix.
callStep := mRel[len(relSubject.Module)]
if callStep.Name != relSubject.Call.Name {
return nil, false
}
// If we get to here then we've found a match. We need to construct
// a new mNewMatch that's an instance of the "new" relSubject with
// the same key as our call.
mNewMatch = toMatch.relSubject.(AbsModuleCall).Instance(callStep.InstanceKey)
mSuffix = mRel[len(relSubject.Module)+1:]
default:
panic("invalid address type for module-kind move endpoint")
}
ret := make(ModuleInstance, 0, len(mPrefix)+len(mNewMatch)+len(mSuffix))
ret = append(ret, mPrefix...)
ret = append(ret, mNewMatch...)
ret = append(ret, mSuffix...)
return ret, true
}
// MoveDestination considers a an address representing a resource
// in the context of source and destination move endpoints and then,
// if the resource address matches the from endpoint, returns the corresponding
// new resource address that the object should move to.
//
// MoveDestination will return false in its second return value if the receiver
// doesn't match fromMatch, indicating that the given move statement doesn't
// apply to this object.
//
// Both of the given endpoints must be from the same move statement and thus
// must have matching object types. If not, MoveDestination will panic.
func (r AbsResource) MoveDestination(fromMatch, toMatch *MoveEndpointInModule) (AbsResource, bool) {
switch fromMatch.ObjectKind() {
case MoveEndpointModule:
// If we've moving a module then any resource inside that module
// moves too.
fromMod := r.Module
toMod, match := fromMod.MoveDestination(fromMatch, toMatch)
if !match {
return AbsResource{}, false
}
return r.Resource.Absolute(toMod), true
case MoveEndpointResource:
// TODO: Implement
return AbsResource{}, false
default:
panic("unexpected object kind")
}
}
// MoveDestination considers a an address representing a resource
// instance in the context of source and destination move endpoints and then,
// if the instance address matches the from endpoint, returns the corresponding
// new instance address that the object should move to.
//
// MoveDestination will return false in its second return value if the receiver
// doesn't match fromMatch, indicating that the given move statement doesn't
// apply to this object.
//
// Both of the given endpoints must be from the same move statement and thus
// must have matching object types. If not, MoveDestination will panic.
func (r AbsResourceInstance) MoveDestination(fromMatch, toMatch *MoveEndpointInModule) (AbsResourceInstance, bool) {
switch fromMatch.ObjectKind() {
case MoveEndpointModule:
// If we've moving a module then any resource inside that module
// moves too.
fromMod := r.Module
toMod, match := fromMod.MoveDestination(fromMatch, toMatch)
if !match {
return AbsResourceInstance{}, false
}
return r.Resource.Absolute(toMod), true
case MoveEndpointResource:
switch fromMatch.relSubject.(type) {
case AbsResource:
oldResource := r.ContainingResource()
newResource, match := oldResource.MoveDestination(fromMatch, toMatch)
if !match {
return AbsResourceInstance{}, false
}
return newResource.Instance(r.Resource.Key), true
case AbsResourceInstance:
// TODO: Implement
return AbsResourceInstance{}, false
default:
panic("invalid address type for resource-kind move endpoint")
}
default:
panic("unexpected object kind")
}
}

View File

@ -0,0 +1,877 @@
package addrs
import (
"fmt"
"strings"
"testing"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestModuleInstanceMoveDestination(t *testing.T) {
tests := []struct {
DeclModule string
StmtFrom, StmtTo string
Reciever string
WantMatch bool
WantResult string
}{
{
``,
`module.foo`,
`module.bar`,
`module.foo`,
true,
`module.bar`,
},
{
``,
`module.foo`,
`module.bar`,
`module.foo[1]`,
true,
`module.bar[1]`,
},
{
``,
`module.foo`,
`module.bar`,
`module.foo["a"]`,
true,
`module.bar["a"]`,
},
{
``,
`module.foo`,
`module.bar.module.foo`,
`module.foo`,
true,
`module.bar.module.foo`,
},
{
``,
`module.foo.module.bar`,
`module.bar`,
`module.foo.module.bar`,
true,
`module.bar`,
},
{
``,
`module.foo[1]`,
`module.foo[2]`,
`module.foo[1]`,
true,
`module.foo[2]`,
},
{
``,
`module.foo[1]`,
`module.foo`,
`module.foo[1]`,
true,
`module.foo`,
},
{
``,
`module.foo`,
`module.foo[1]`,
`module.foo`,
true,
`module.foo[1]`,
},
{
``,
`module.foo`,
`module.foo[1]`,
`module.foo.module.bar`,
true,
`module.foo[1].module.bar`,
},
{
``,
`module.foo`,
`module.foo[1]`,
`module.foo.module.bar[0]`,
true,
`module.foo[1].module.bar[0]`,
},
{
``,
`module.foo`,
`module.bar.module.foo`,
`module.foo[0]`,
true,
`module.bar.module.foo[0]`,
},
{
``,
`module.foo.module.bar`,
`module.bar`,
`module.foo.module.bar[0]`,
true,
`module.bar[0]`,
},
{
`foo`,
`module.bar`,
`module.baz`,
`module.foo.module.bar`,
true,
`module.foo.module.baz`,
},
{
`foo`,
`module.bar`,
`module.baz`,
`module.foo[1].module.bar`,
true,
`module.foo[1].module.baz`,
},
{
`foo`,
`module.bar`,
`module.bar[1]`,
`module.foo[1].module.bar`,
true,
`module.foo[1].module.bar[1]`,
},
{
``,
`module.foo[1]`,
`module.foo[2]`,
`module.foo`,
false, // the receiver has a non-matching instance key (NoKey)
``,
},
{
``,
`module.foo[1]`,
`module.foo[2]`,
`module.foo[2]`,
false, // the receiver is already the "to" address
``,
},
{
``,
`module.foo`,
`module.bar`,
``,
false, // the root module can never be moved
``,
},
{
`foo`,
`module.bar`,
`module.bar[1]`,
`module.boz`,
false, // the receiver is outside the declaration module
``,
},
{
`foo.bar`,
`module.bar`,
`module.bar[1]`,
`module.boz`,
false, // the receiver is outside the declaration module
``,
},
{
`foo.bar`,
`module.a`,
`module.b`,
`module.boz`,
false, // the receiver is outside the declaration module
``,
},
{
``,
`module.a1.module.a2`,
`module.b1.module.b2`,
`module.c`,
false, // the receiver is outside the declaration module
``,
},
{
``,
`module.a1.module.a2[0]`,
`module.b1.module.b2[1]`,
`module.c`,
false, // the receiver is outside the declaration module
``,
},
{
``,
`module.a1.module.a2`,
`module.b1.module.b2`,
`module.a1.module.b2`,
false, // the receiver is outside the declaration module
``,
},
{
``,
`module.a1.module.a2`,
`module.b1.module.b2`,
`module.b1.module.a2`,
false, // the receiver is outside the declaration module
``,
},
{
``,
`module.a1.module.a2[0]`,
`module.b1.module.b2[1]`,
`module.a1.module.b2[0]`,
false, // the receiver is outside the declaration module
``,
},
{
``,
`foo_instance.bar`,
`foo_instance.baz`,
`module.foo`,
false, // a resource address can never match a module instance
``,
},
}
for _, test := range tests {
t.Run(
fmt.Sprintf(
"%s: %s to %s with %s",
test.DeclModule,
test.StmtFrom, test.StmtTo,
test.Reciever,
),
func(t *testing.T) {
parseStmtEP := func(t *testing.T, input string) *MoveEndpoint {
t.Helper()
traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(input), "", hcl.InitialPos)
if hclDiags.HasErrors() {
// We're not trying to test the HCL parser here, so any
// failures at this point are likely to be bugs in the
// test case itself.
t.Fatalf("syntax error: %s", hclDiags.Error())
}
moveEp, diags := ParseMoveEndpoint(traversal)
if diags.HasErrors() {
t.Fatalf("unexpected error: %s", diags.Err().Error())
}
return moveEp
}
fromEPLocal := parseStmtEP(t, test.StmtFrom)
toEPLocal := parseStmtEP(t, test.StmtTo)
declModule := RootModule
if test.DeclModule != "" {
declModule = strings.Split(test.DeclModule, ".")
}
fromEP, toEP := UnifyMoveEndpoints(declModule, fromEPLocal, toEPLocal)
if fromEP == nil || toEP == nil {
t.Fatalf("invalid test case: non-unifyable endpoints\nfrom: %s\nto: %s", fromEPLocal, toEPLocal)
}
receiverAddr := RootModuleInstance
if test.Reciever != "" {
var diags tfdiags.Diagnostics
receiverAddr, diags = ParseModuleInstanceStr(test.Reciever)
if diags.HasErrors() {
t.Fatalf("invalid reciever address: %s", diags.Err().Error())
}
}
gotAddr, gotMatch := receiverAddr.MoveDestination(fromEP, toEP)
if !test.WantMatch {
if gotMatch {
t.Errorf("unexpected match\nreciever: %s\nfrom: %s\nto: %s\nresult: %s", test.Reciever, fromEP, toEP, gotAddr)
}
return
}
if !gotMatch {
t.Errorf("unexpected non-match\nreciever: %s\nfrom: %s\nto: %s", test.Reciever, fromEP, toEP)
}
if gotStr, wantStr := gotAddr.String(), test.WantResult; gotStr != wantStr {
t.Errorf("wrong result\ngot: %s\nwant: %s", gotStr, wantStr)
}
},
)
}
}
func TestAbsResourceInstanceMoveDestination(t *testing.T) {
tests := []struct {
DeclModule string
StmtFrom, StmtTo string
Reciever string
WantMatch bool
WantResult string
}{
{
``,
`module.foo`,
`module.bar`,
`module.foo.test_object.beep`,
true,
`module.bar.test_object.beep`,
},
{
``,
`module.foo`,
`module.bar`,
`module.foo[1].test_object.beep`,
true,
`module.bar[1].test_object.beep`,
},
{
``,
`module.foo`,
`module.bar`,
`module.foo["a"].test_object.beep`,
true,
`module.bar["a"].test_object.beep`,
},
{
``,
`module.foo`,
`module.bar.module.foo`,
`module.foo.test_object.beep`,
true,
`module.bar.module.foo.test_object.beep`,
},
{
``,
`module.foo.module.bar`,
`module.bar`,
`module.foo.module.bar.test_object.beep`,
true,
`module.bar.test_object.beep`,
},
{
``,
`module.foo[1]`,
`module.foo[2]`,
`module.foo[1].test_object.beep`,
true,
`module.foo[2].test_object.beep`,
},
{
``,
`module.foo[1]`,
`module.foo`,
`module.foo[1].test_object.beep`,
true,
`module.foo.test_object.beep`,
},
{
``,
`module.foo`,
`module.foo[1]`,
`module.foo.test_object.beep`,
true,
`module.foo[1].test_object.beep`,
},
{
``,
`module.foo`,
`module.foo[1]`,
`module.foo.module.bar.test_object.beep`,
true,
`module.foo[1].module.bar.test_object.beep`,
},
{
``,
`module.foo`,
`module.foo[1]`,
`module.foo.module.bar[0].test_object.beep`,
true,
`module.foo[1].module.bar[0].test_object.beep`,
},
{
``,
`module.foo`,
`module.bar.module.foo`,
`module.foo[0].test_object.beep`,
true,
`module.bar.module.foo[0].test_object.beep`,
},
{
``,
`module.foo.module.bar`,
`module.bar`,
`module.foo.module.bar[0].test_object.beep`,
true,
`module.bar[0].test_object.beep`,
},
{
`foo`,
`module.bar`,
`module.baz`,
`module.foo.module.bar.test_object.beep`,
true,
`module.foo.module.baz.test_object.beep`,
},
{
`foo`,
`module.bar`,
`module.baz`,
`module.foo[1].module.bar.test_object.beep`,
true,
`module.foo[1].module.baz.test_object.beep`,
},
{
`foo`,
`module.bar`,
`module.bar[1]`,
`module.foo[1].module.bar.test_object.beep`,
true,
`module.foo[1].module.bar[1].test_object.beep`,
},
{
``,
`module.foo[1]`,
`module.foo[2]`,
`module.foo.test_object.beep`,
false, // the receiver module has a non-matching instance key (NoKey)
``,
},
{
``,
`module.foo[1]`,
`module.foo[2]`,
`module.foo[2].test_object.beep`,
false, // the receiver is already at the "to" address
``,
},
{
`foo`,
`module.bar`,
`module.bar[1]`,
`module.boz.test_object.beep`,
false, // the receiver module is outside the declaration module
``,
},
{
`foo.bar`,
`module.bar`,
`module.bar[1]`,
`module.boz.test_object.beep`,
false, // the receiver module is outside the declaration module
``,
},
{
`foo.bar`,
`module.a`,
`module.b`,
`module.boz.test_object.beep`,
false, // the receiver module is outside the declaration module
``,
},
{
``,
`module.a1.module.a2`,
`module.b1.module.b2`,
`module.c.test_object.beep`,
false, // the receiver module is outside the declaration module
``,
},
{
``,
`module.a1.module.a2[0]`,
`module.b1.module.b2[1]`,
`module.c.test_object.beep`,
false, // the receiver module is outside the declaration module
``,
},
{
``,
`module.a1.module.a2`,
`module.b1.module.b2`,
`module.a1.module.b2.test_object.beep`,
false, // the receiver module is outside the declaration module
``,
},
{
``,
`module.a1.module.a2`,
`module.b1.module.b2`,
`module.b1.module.a2.test_object.beep`,
false, // the receiver module is outside the declaration module
``,
},
{
``,
`module.a1.module.a2[0]`,
`module.b1.module.b2[1]`,
`module.a1.module.b2[0].test_object.beep`,
false, // the receiver module is outside the declaration module
``,
},
{
``,
`foo_instance.bar`,
`foo_instance.baz`,
`module.foo.test_object.beep`,
false, // the resource address is unrelated to the move statements
``,
},
}
for _, test := range tests {
t.Run(
fmt.Sprintf(
"%s: %s to %s with %s",
test.DeclModule,
test.StmtFrom, test.StmtTo,
test.Reciever,
),
func(t *testing.T) {
parseStmtEP := func(t *testing.T, input string) *MoveEndpoint {
t.Helper()
traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(input), "", hcl.InitialPos)
if hclDiags.HasErrors() {
// We're not trying to test the HCL parser here, so any
// failures at this point are likely to be bugs in the
// test case itself.
t.Fatalf("syntax error: %s", hclDiags.Error())
}
moveEp, diags := ParseMoveEndpoint(traversal)
if diags.HasErrors() {
t.Fatalf("unexpected error: %s", diags.Err().Error())
}
return moveEp
}
fromEPLocal := parseStmtEP(t, test.StmtFrom)
toEPLocal := parseStmtEP(t, test.StmtTo)
declModule := RootModule
if test.DeclModule != "" {
declModule = strings.Split(test.DeclModule, ".")
}
fromEP, toEP := UnifyMoveEndpoints(declModule, fromEPLocal, toEPLocal)
if fromEP == nil || toEP == nil {
t.Fatalf("invalid test case: non-unifyable endpoints\nfrom: %s\nto: %s", fromEPLocal, toEPLocal)
}
receiverAddr, diags := ParseAbsResourceInstanceStr(test.Reciever)
if diags.HasErrors() {
t.Fatalf("invalid reciever address: %s", diags.Err().Error())
}
gotAddr, gotMatch := receiverAddr.MoveDestination(fromEP, toEP)
if !test.WantMatch {
if gotMatch {
t.Errorf("unexpected match\nreciever: %s\nfrom: %s\nto: %s\nresult: %s", test.Reciever, fromEP, toEP, gotAddr)
}
return
}
if !gotMatch {
t.Errorf("unexpected non-match\nreciever: %s\nfrom: %s\nto: %s", test.Reciever, fromEP, toEP)
}
if gotStr, wantStr := gotAddr.String(), test.WantResult; gotStr != wantStr {
t.Errorf("wrong result\ngot: %s\nwant: %s", gotStr, wantStr)
}
},
)
}
}
func TestAbsResourceMoveDestination(t *testing.T) {
tests := []struct {
DeclModule string
StmtFrom, StmtTo string
Reciever string
WantMatch bool
WantResult string
}{
{
``,
`module.foo`,
`module.bar`,
`module.foo.test_object.beep`,
true,
`module.bar.test_object.beep`,
},
{
``,
`module.foo`,
`module.bar`,
`module.foo[1].test_object.beep`,
true,
`module.bar[1].test_object.beep`,
},
{
``,
`module.foo`,
`module.bar`,
`module.foo["a"].test_object.beep`,
true,
`module.bar["a"].test_object.beep`,
},
{
``,
`module.foo`,
`module.bar.module.foo`,
`module.foo.test_object.beep`,
true,
`module.bar.module.foo.test_object.beep`,
},
{
``,
`module.foo.module.bar`,
`module.bar`,
`module.foo.module.bar.test_object.beep`,
true,
`module.bar.test_object.beep`,
},
{
``,
`module.foo[1]`,
`module.foo[2]`,
`module.foo[1].test_object.beep`,
true,
`module.foo[2].test_object.beep`,
},
{
``,
`module.foo[1]`,
`module.foo`,
`module.foo[1].test_object.beep`,
true,
`module.foo.test_object.beep`,
},
{
``,
`module.foo`,
`module.foo[1]`,
`module.foo.test_object.beep`,
true,
`module.foo[1].test_object.beep`,
},
{
``,
`module.foo`,
`module.foo[1]`,
`module.foo.module.bar.test_object.beep`,
true,
`module.foo[1].module.bar.test_object.beep`,
},
{
``,
`module.foo`,
`module.foo[1]`,
`module.foo.module.bar[0].test_object.beep`,
true,
`module.foo[1].module.bar[0].test_object.beep`,
},
{
``,
`module.foo`,
`module.bar.module.foo`,
`module.foo[0].test_object.beep`,
true,
`module.bar.module.foo[0].test_object.beep`,
},
{
``,
`module.foo.module.bar`,
`module.bar`,
`module.foo.module.bar[0].test_object.beep`,
true,
`module.bar[0].test_object.beep`,
},
{
`foo`,
`module.bar`,
`module.baz`,
`module.foo.module.bar.test_object.beep`,
true,
`module.foo.module.baz.test_object.beep`,
},
{
`foo`,
`module.bar`,
`module.baz`,
`module.foo[1].module.bar.test_object.beep`,
true,
`module.foo[1].module.baz.test_object.beep`,
},
{
`foo`,
`module.bar`,
`module.bar[1]`,
`module.foo[1].module.bar.test_object.beep`,
true,
`module.foo[1].module.bar[1].test_object.beep`,
},
{
``,
`module.foo[1]`,
`module.foo[2]`,
`module.foo.test_object.beep`,
false, // the receiver module has a non-matching instance key (NoKey)
``,
},
{
``,
`module.foo[1]`,
`module.foo[2]`,
`module.foo[2].test_object.beep`,
false, // the receiver is already at the "to" address
``,
},
{
`foo`,
`module.bar`,
`module.bar[1]`,
`module.boz.test_object.beep`,
false, // the receiver module is outside the declaration module
``,
},
{
`foo.bar`,
`module.bar`,
`module.bar[1]`,
`module.boz.test_object.beep`,
false, // the receiver module is outside the declaration module
``,
},
{
`foo.bar`,
`module.a`,
`module.b`,
`module.boz.test_object.beep`,
false, // the receiver module is outside the declaration module
``,
},
{
``,
`module.a1.module.a2`,
`module.b1.module.b2`,
`module.c.test_object.beep`,
false, // the receiver module is outside the declaration module
``,
},
{
``,
`module.a1.module.a2[0]`,
`module.b1.module.b2[1]`,
`module.c.test_object.beep`,
false, // the receiver module is outside the declaration module
``,
},
{
``,
`module.a1.module.a2`,
`module.b1.module.b2`,
`module.a1.module.b2.test_object.beep`,
false, // the receiver module is outside the declaration module
``,
},
{
``,
`module.a1.module.a2`,
`module.b1.module.b2`,
`module.b1.module.a2.test_object.beep`,
false, // the receiver module is outside the declaration module
``,
},
{
``,
`module.a1.module.a2[0]`,
`module.b1.module.b2[1]`,
`module.a1.module.b2[0].test_object.beep`,
false, // the receiver module is outside the declaration module
``,
},
{
``,
`foo_instance.bar`,
`foo_instance.baz`,
`module.foo.test_object.beep`,
false, // the resource address is unrelated to the move statements
``,
},
}
for _, test := range tests {
t.Run(
fmt.Sprintf(
"%s: %s to %s with %s",
test.DeclModule,
test.StmtFrom, test.StmtTo,
test.Reciever,
),
func(t *testing.T) {
parseStmtEP := func(t *testing.T, input string) *MoveEndpoint {
t.Helper()
traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(input), "", hcl.InitialPos)
if hclDiags.HasErrors() {
// We're not trying to test the HCL parser here, so any
// failures at this point are likely to be bugs in the
// test case itself.
t.Fatalf("syntax error: %s", hclDiags.Error())
}
moveEp, diags := ParseMoveEndpoint(traversal)
if diags.HasErrors() {
t.Fatalf("unexpected error: %s", diags.Err().Error())
}
return moveEp
}
fromEPLocal := parseStmtEP(t, test.StmtFrom)
toEPLocal := parseStmtEP(t, test.StmtTo)
declModule := RootModule
if test.DeclModule != "" {
declModule = strings.Split(test.DeclModule, ".")
}
fromEP, toEP := UnifyMoveEndpoints(declModule, fromEPLocal, toEPLocal)
if fromEP == nil || toEP == nil {
t.Fatalf("invalid test case: non-unifyable endpoints\nfrom: %s\nto: %s", fromEPLocal, toEPLocal)
}
// We only have an AbsResourceInstance parser, not an
// AbsResourceParser, and so we'll just cheat and parse this
// as a resource instance but fail if it includes an instance
// key.
receiverInstanceAddr, diags := ParseAbsResourceInstanceStr(test.Reciever)
if diags.HasErrors() {
t.Fatalf("invalid reciever address: %s", diags.Err().Error())
}
if receiverInstanceAddr.Resource.Key != NoKey {
t.Fatalf("invalid reciever address: must be a resource, not a resource instance")
}
receiverAddr := receiverInstanceAddr.ContainingResource()
gotAddr, gotMatch := receiverAddr.MoveDestination(fromEP, toEP)
if !test.WantMatch {
if gotMatch {
t.Errorf("unexpected match\nreciever: %s\nfrom: %s\nto: %s\nresult: %s", test.Reciever, fromEP, toEP, gotAddr)
}
return
}
if !gotMatch {
t.Errorf("unexpected non-match\nreciever: %s\nfrom: %s\nto: %s", test.Reciever, fromEP, toEP)
}
if gotStr, wantStr := gotAddr.String(), test.WantResult; gotStr != wantStr {
t.Errorf("wrong result\ngot: %s\nwant: %s", gotStr, wantStr)
}
},
)
}
}

View File

@ -5,7 +5,6 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
)
@ -339,245 +338,127 @@ func TestParseMoveEndpoint(t *testing.T) {
func TestUnifyMoveEndpoints(t *testing.T) {
tests := []struct {
InputFrom, InputTo string
Module ModuleInstance
WantFrom, WantTo AbsMoveable
Module Module
WantFrom, WantTo string
}{
{
InputFrom: `foo.bar`,
InputTo: `foo.baz`,
Module: RootModuleInstance,
WantFrom: AbsResource{
Module: RootModuleInstance,
Resource: Resource{
Mode: ManagedResourceMode,
Type: "foo",
Name: "bar",
},
},
WantTo: AbsResource{
Module: RootModuleInstance,
Resource: Resource{
Mode: ManagedResourceMode,
Type: "foo",
Name: "baz",
},
},
Module: RootModule,
WantFrom: `foo.bar[*]`,
WantTo: `foo.baz[*]`,
},
{
InputFrom: `foo.bar`,
InputTo: `foo.baz`,
Module: RootModuleInstance.Child("a", NoKey),
WantFrom: AbsResource{
Module: RootModuleInstance.Child("a", NoKey),
Resource: Resource{
Mode: ManagedResourceMode,
Type: "foo",
Name: "bar",
},
},
WantTo: AbsResource{
Module: RootModuleInstance.Child("a", NoKey),
Resource: Resource{
Mode: ManagedResourceMode,
Type: "foo",
Name: "baz",
},
},
Module: RootModule.Child("a"),
WantFrom: `module.a[*].foo.bar[*]`,
WantTo: `module.a[*].foo.baz[*]`,
},
{
InputFrom: `foo.bar`,
InputTo: `module.b[0].foo.baz`,
Module: RootModuleInstance.Child("a", NoKey),
WantFrom: AbsResource{
Module: RootModuleInstance.Child("a", NoKey),
Resource: Resource{
Mode: ManagedResourceMode,
Type: "foo",
Name: "bar",
},
},
WantTo: AbsResource{
Module: RootModuleInstance.Child("a", NoKey).Child("b", IntKey(0)),
Resource: Resource{
Mode: ManagedResourceMode,
Type: "foo",
Name: "baz",
},
},
Module: RootModule.Child("a"),
WantFrom: `module.a[*].foo.bar[*]`,
WantTo: `module.a[*].module.b[0].foo.baz[*]`,
},
{
InputFrom: `foo.bar`,
InputTo: `foo.bar["thing"]`,
Module: RootModuleInstance,
WantFrom: AbsResourceInstance{
Module: RootModuleInstance,
Resource: ResourceInstance{
Resource: Resource{
Mode: ManagedResourceMode,
Type: "foo",
Name: "bar",
},
},
},
WantTo: AbsResourceInstance{
Module: RootModuleInstance,
Resource: ResourceInstance{
Resource: Resource{
Mode: ManagedResourceMode,
Type: "foo",
Name: "bar",
},
Key: StringKey("thing"),
},
},
Module: RootModule,
WantFrom: `foo.bar`,
WantTo: `foo.bar["thing"]`,
},
{
InputFrom: `foo.bar["thing"]`,
InputTo: `foo.bar`,
Module: RootModuleInstance,
WantFrom: AbsResourceInstance{
Module: RootModuleInstance,
Resource: ResourceInstance{
Resource: Resource{
Mode: ManagedResourceMode,
Type: "foo",
Name: "bar",
},
Key: StringKey("thing"),
},
},
WantTo: AbsResourceInstance{
Module: RootModuleInstance,
Resource: ResourceInstance{
Resource: Resource{
Mode: ManagedResourceMode,
Type: "foo",
Name: "bar",
},
},
},
Module: RootModule,
WantFrom: `foo.bar["thing"]`,
WantTo: `foo.bar`,
},
{
InputFrom: `foo.bar["a"]`,
InputTo: `foo.bar["b"]`,
Module: RootModuleInstance,
WantFrom: AbsResourceInstance{
Module: RootModuleInstance,
Resource: ResourceInstance{
Resource: Resource{
Mode: ManagedResourceMode,
Type: "foo",
Name: "bar",
},
Key: StringKey("a"),
},
},
WantTo: AbsResourceInstance{
Module: RootModuleInstance,
Resource: ResourceInstance{
Resource: Resource{
Mode: ManagedResourceMode,
Type: "foo",
Name: "bar",
},
Key: StringKey("b"),
},
},
Module: RootModule,
WantFrom: `foo.bar["a"]`,
WantTo: `foo.bar["b"]`,
},
{
InputFrom: `module.foo`,
InputTo: `module.bar`,
Module: RootModuleInstance,
WantFrom: AbsModuleCall{
Module: RootModuleInstance,
Call: ModuleCall{Name: "foo"},
},
WantTo: AbsModuleCall{
Module: RootModuleInstance,
Call: ModuleCall{Name: "bar"},
},
Module: RootModule,
WantFrom: `module.foo[*]`,
WantTo: `module.bar[*]`,
},
{
InputFrom: `module.foo`,
InputTo: `module.bar.module.baz`,
Module: RootModuleInstance,
WantFrom: AbsModuleCall{
Module: RootModuleInstance,
Call: ModuleCall{Name: "foo"},
},
WantTo: AbsModuleCall{
Module: RootModuleInstance.Child("bar", NoKey),
Call: ModuleCall{Name: "baz"},
},
Module: RootModule,
WantFrom: `module.foo[*]`,
WantTo: `module.bar.module.baz[*]`,
},
{
InputFrom: `module.foo`,
InputTo: `module.bar.module.baz`,
Module: RootModuleInstance.Child("bloop", StringKey("hi")),
WantFrom: AbsModuleCall{
Module: RootModuleInstance.Child("bloop", StringKey("hi")),
Call: ModuleCall{Name: "foo"},
},
WantTo: AbsModuleCall{
Module: RootModuleInstance.Child("bloop", StringKey("hi")).Child("bar", NoKey),
Call: ModuleCall{Name: "baz"},
},
Module: RootModule.Child("bloop"),
WantFrom: `module.bloop[*].module.foo[*]`,
WantTo: `module.bloop[*].module.bar.module.baz[*]`,
},
{
InputFrom: `module.foo[0]`,
InputTo: `module.foo["a"]`,
Module: RootModuleInstance,
WantFrom: RootModuleInstance.Child("foo", IntKey(0)),
WantTo: RootModuleInstance.Child("foo", StringKey("a")),
Module: RootModule,
WantFrom: `module.foo[0]`,
WantTo: `module.foo["a"]`,
},
{
InputFrom: `module.foo`,
InputTo: `module.foo["a"]`,
Module: RootModuleInstance,
WantFrom: RootModuleInstance.Child("foo", NoKey),
WantTo: RootModuleInstance.Child("foo", StringKey("a")),
Module: RootModule,
WantFrom: `module.foo`,
WantTo: `module.foo["a"]`,
},
{
InputFrom: `module.foo[0]`,
InputTo: `module.foo`,
Module: RootModuleInstance,
WantFrom: RootModuleInstance.Child("foo", IntKey(0)),
WantTo: RootModuleInstance.Child("foo", NoKey),
Module: RootModule,
WantFrom: `module.foo[0]`,
WantTo: `module.foo`,
},
{
InputFrom: `module.foo[0]`,
InputTo: `module.foo`,
Module: RootModuleInstance.Child("bloop", NoKey),
WantFrom: RootModuleInstance.Child("bloop", NoKey).Child("foo", IntKey(0)),
WantTo: RootModuleInstance.Child("bloop", NoKey).Child("foo", NoKey),
Module: RootModule.Child("bloop"),
WantFrom: `module.bloop[*].module.foo[0]`,
WantTo: `module.bloop[*].module.foo`,
},
{
InputFrom: `module.foo`,
InputTo: `foo.bar`,
Module: RootModuleInstance,
WantFrom: nil, // Can't unify module call with resource
WantTo: nil,
Module: RootModule,
WantFrom: ``, // Can't unify module call with resource
WantTo: ``,
},
{
InputFrom: `module.foo[0]`,
InputTo: `foo.bar`,
Module: RootModuleInstance,
WantFrom: nil, // Can't unify module instance with resource
WantTo: nil,
Module: RootModule,
WantFrom: ``, // Can't unify module instance with resource
WantTo: ``,
},
{
InputFrom: `module.foo`,
InputTo: `foo.bar[0]`,
Module: RootModuleInstance,
WantFrom: nil, // Can't unify module call with resource instance
WantTo: nil,
Module: RootModule,
WantFrom: ``, // Can't unify module call with resource instance
WantTo: ``,
},
{
InputFrom: `module.foo[0]`,
InputTo: `foo.bar[0]`,
Module: RootModuleInstance,
WantFrom: nil, // Can't unify module instance with resource instance
WantTo: nil,
Module: RootModule,
WantFrom: ``, // Can't unify module instance with resource instance
WantTo: ``,
},
}
@ -604,13 +485,12 @@ func TestUnifyMoveEndpoints(t *testing.T) {
fromEp := parseInput(test.InputFrom)
toEp := parseInput(test.InputTo)
diffOpts := cmpopts.IgnoreUnexported(ModuleCall{})
gotFrom, gotTo := UnifyMoveEndpoints(test.Module, fromEp, toEp)
if diff := cmp.Diff(test.WantFrom, gotFrom, diffOpts); diff != "" {
t.Errorf("wrong 'from' address\n%s", diff)
if got, want := gotFrom.String(), test.WantFrom; got != want {
t.Errorf("wrong 'from' result\ngot: %s\nwant: %s", got, want)
}
if diff := cmp.Diff(test.WantTo, gotTo, diffOpts); diff != "" {
t.Errorf("wrong 'to' address\n%s", diff)
if got, want := gotTo.String(), test.WantTo; got != want {
t.Errorf("wrong 'to' result\ngot: %s\nwant: %s", got, want)
}
})
}

View File

@ -9,7 +9,6 @@ package addrs
// of the configuration, which is different than the direct representation
// of these in configuration where the author gives an address relative to
// the current module where the address is defined. The type MoveEndpoint
type AbsMoveable interface {
absMoveableSigil()

View File

@ -0,0 +1,29 @@
// Code generated by "stringer -type MoveEndpointKind"; DO NOT EDIT.
package addrs
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[MoveEndpointModule-77]
_ = x[MoveEndpointResource-82]
}
const (
_MoveEndpointKind_name_0 = "MoveEndpointModule"
_MoveEndpointKind_name_1 = "MoveEndpointResource"
)
func (i MoveEndpointKind) String() string {
switch {
case i == 77:
return _MoveEndpointKind_name_0
case i == 82:
return _MoveEndpointKind_name_1
default:
return "MoveEndpointKind(" + strconv.FormatInt(int64(i), 10) + ")"
}
}

View File

@ -10,3 +10,9 @@ type PathAttr struct {
func (pa PathAttr) String() string {
return "path." + pa.Name
}
func (pa PathAttr) UniqueKey() UniqueKey {
return pa // A PathAttr is its own UniqueKey
}
func (pa PathAttr) uniqueKeySigil() {}

View File

@ -7,6 +7,9 @@ type Referenceable interface {
// in lang.Scope.buildEvalContext.
referenceableSigil()
// All Referenceable address types must have unique keys.
UniqueKeyer
// String produces a string representation of the address that could be
// parsed as a HCL traversal and passed to ParseRef to produce an identical
// result.

View File

@ -32,6 +32,12 @@ func (r Resource) Equal(o Resource) bool {
return r.Mode == o.Mode && r.Name == o.Name && r.Type == o.Type
}
func (r Resource) UniqueKey() UniqueKey {
return r // A Resource is its own UniqueKey
}
func (r Resource) uniqueKeySigil() {}
// Instance produces the address for a specific instance of the receiver
// that is idenfied by the given key.
func (r Resource) Instance(key InstanceKey) ResourceInstance {
@ -94,6 +100,12 @@ func (r ResourceInstance) Equal(o ResourceInstance) bool {
return r.Key == o.Key && r.Resource.Equal(o.Resource)
}
func (r ResourceInstance) UniqueKey() UniqueKey {
return r // A ResourceInstance is its own UniqueKey
}
func (r ResourceInstance) uniqueKeySigil() {}
// Absolute returns an AbsResourceInstance from the receiver and the given module
// instance address.
func (r ResourceInstance) Absolute(module ModuleInstance) AbsResourceInstance {
@ -280,6 +292,14 @@ func (r AbsResourceInstance) Less(o AbsResourceInstance) bool {
}
}
type absResourceInstanceKey string
func (r AbsResourceInstance) UniqueKey() UniqueKey {
return absResourceInstanceKey(r.String())
}
func (r absResourceInstanceKey) uniqueKeySigil() {}
func (r AbsResourceInstance) absMoveableSigil() {
// AbsResourceInstance is moveable
}

View File

@ -44,6 +44,12 @@ func (rp ResourceInstancePhase) String() string {
return fmt.Sprintf("%s#%s", rp.ResourceInstance, rp.Phase)
}
func (rp ResourceInstancePhase) UniqueKey() UniqueKey {
return rp // A ResourceInstancePhase is its own UniqueKey
}
func (rp ResourceInstancePhase) uniqueKeySigil() {}
// ResourceInstancePhaseType is an enumeration used with ResourceInstancePhase.
type ResourceInstancePhaseType string
@ -103,3 +109,9 @@ func (rp ResourcePhase) String() string {
// because this special address type should never be exposed in the UI.
return fmt.Sprintf("%s#%s", rp.Resource, rp.Phase)
}
func (rp ResourcePhase) UniqueKey() UniqueKey {
return rp // A ResourcePhase is its own UniqueKey
}
func (rp ResourcePhase) uniqueKeySigil() {}

View File

@ -12,3 +12,9 @@ func (s selfT) referenceableSigil() {
func (s selfT) String() string {
return "self"
}
func (s selfT) UniqueKey() UniqueKey {
return Self // Self is its own UniqueKey
}
func (s selfT) uniqueKeySigil() {}

43
internal/addrs/set.go Normal file
View File

@ -0,0 +1,43 @@
package addrs
// Set represents a set of addresses of types that implement UniqueKeyer.
type Set map[UniqueKey]UniqueKeyer
func (s Set) Has(addr UniqueKeyer) bool {
_, exists := s[addr.UniqueKey()]
return exists
}
func (s Set) Add(addr UniqueKeyer) {
s[addr.UniqueKey()] = addr
}
func (s Set) Remove(addr UniqueKeyer) {
delete(s, addr.UniqueKey())
}
func (s Set) Union(other Set) Set {
ret := make(Set)
for k, addr := range s {
ret[k] = addr
}
for k, addr := range other {
ret[k] = addr
}
return ret
}
func (s Set) Intersection(other Set) Set {
ret := make(Set)
for k, addr := range s {
if _, exists := other[k]; exists {
ret[k] = addr
}
}
for k, addr := range other {
if _, exists := s[k]; exists {
ret[k] = addr
}
}
return ret
}

View File

@ -10,3 +10,9 @@ type TerraformAttr struct {
func (ta TerraformAttr) String() string {
return "terraform." + ta.Name
}
func (ta TerraformAttr) UniqueKey() UniqueKey {
return ta // A TerraformAttr is its own UniqueKey
}
func (ta TerraformAttr) uniqueKeySigil() {}

View File

@ -0,0 +1,23 @@
package addrs
// UniqueKey is an interface implemented by values that serve as unique map
// keys for particular addresses.
//
// All implementations of UniqueKey are comparable and can thus be used as
// map keys. Unique keys generated from different address types are always
// distinct. All functionally-equivalent keys for the same address type
// always compare equal, and likewise functionally-different values do not.
type UniqueKey interface {
uniqueKeySigil()
}
// UniqueKeyer is an interface implemented by types that can be represented
// by a unique key.
//
// Some address types naturally comply with the expectations of a UniqueKey
// and may thus be their own unique key type. However, address types that
// are not naturally comparable can implement this interface by returning
// proxy values.
type UniqueKeyer interface {
UniqueKey() UniqueKey
}

View File

@ -0,0 +1,72 @@
package addrs
import (
"fmt"
"testing"
)
// TestUniqueKeyer aims to ensure that all of the types that have unique keys
// will continue to meet the UniqueKeyer contract under future changes.
//
// If you add a new implementation of UniqueKey, consider adding a test case
// for it here.
func TestUniqueKeyer(t *testing.T) {
tests := []UniqueKeyer{
CountAttr{Name: "index"},
ForEachAttr{Name: "key"},
TerraformAttr{Name: "workspace"},
PathAttr{Name: "module"},
InputVariable{Name: "foo"},
ModuleCall{Name: "foo"},
ModuleCallInstance{
Call: ModuleCall{Name: "foo"},
Key: StringKey("a"),
},
ModuleCallOutput{
Call: ModuleCall{Name: "foo"},
Name: "bar",
},
ModuleCallInstanceOutput{
Call: ModuleCallInstance{
Call: ModuleCall{Name: "foo"},
Key: StringKey("a"),
},
Name: "bar",
},
Resource{
Mode: ManagedResourceMode,
Type: "foo",
Name: "bar",
},
ResourceInstance{
Resource: Resource{
Mode: ManagedResourceMode,
Type: "foo",
Name: "bar",
},
Key: IntKey(1),
},
RootModuleInstance,
RootModuleInstance.Child("foo", NoKey),
RootModuleInstance.ResourceInstance(
DataResourceMode,
"boop",
"beep",
NoKey,
),
Self,
}
for _, test := range tests {
t.Run(fmt.Sprintf("%s", test), func(t *testing.T) {
a := test.UniqueKey()
b := test.UniqueKey()
// The following comparison will panic if the unique key is not
// of a comparable type.
if a != b {
t.Fatalf("the two unique keys are not equal\na: %#v\b: %#v", a, b)
}
})
}
}

View File

@ -6,11 +6,11 @@ import (
"context"
"strings"
etcdapi "github.com/coreos/etcd/client"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/legacy/helper/schema"
"github.com/hashicorp/terraform/internal/states/remote"
"github.com/hashicorp/terraform/internal/states/statemgr"
etcdapi "go.etcd.io/etcd/client"
)
func New() backend.Backend {

View File

@ -5,8 +5,8 @@ import (
"crypto/md5"
"fmt"
etcdapi "github.com/coreos/etcd/client"
"github.com/hashicorp/terraform/internal/states/remote"
etcdapi "go.etcd.io/etcd/client"
)
// EtcdClient is a remote client that stores data in etcd.

View File

@ -3,10 +3,10 @@ package etcd
import (
"context"
etcdv3 "github.com/coreos/etcd/clientv3"
"github.com/coreos/etcd/pkg/transport"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/legacy/helper/schema"
etcdv3 "go.etcd.io/etcd/clientv3"
"go.etcd.io/etcd/pkg/transport"
)
const (
@ -15,6 +15,7 @@ const (
usernameEnvVarName = "ETCDV3_USERNAME"
passwordKey = "password"
passwordEnvVarName = "ETCDV3_PASSWORD"
maxRequestBytesKey = "max_request_bytes"
prefixKey = "prefix"
lockKey = "lock"
cacertPathKey = "cacert_path"
@ -49,6 +50,13 @@ func New() backend.Backend {
DefaultFunc: schema.EnvDefaultFunc(passwordEnvVarName, ""),
},
maxRequestBytesKey: &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Description: "The max request size to send to etcd.",
Default: 0,
},
prefixKey: &schema.Schema{
Type: schema.TypeString,
Optional: true,
@ -128,6 +136,9 @@ func (b *Backend) rawClient() (*etcdv3.Client, error) {
if v, ok := b.data.GetOk(passwordKey); ok && v.(string) != "" {
config.Password = v.(string)
}
if v, ok := b.data.GetOk(maxRequestBytesKey); ok && v.(int) != 0 {
config.MaxCallSendMsgSize = v.(int)
}
if v, ok := b.data.GetOk(cacertPathKey); ok && v.(string) != "" {
tlsInfo.TrustedCAFile = v.(string)
}

View File

@ -6,7 +6,7 @@ import (
"sort"
"strings"
etcdv3 "github.com/coreos/etcd/clientv3"
etcdv3 "go.etcd.io/etcd/clientv3"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/states"
@ -58,16 +58,8 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
lockInfo := statemgr.NewLockInfo()
lockInfo.Operation = "init"
lockId, err := stateMgr.Lock(lockInfo)
if err != nil {
return nil, fmt.Errorf("Failed to lock state in etcd: %s.", err)
}
lockUnlock := func(parent error) error {
if err := stateMgr.Unlock(lockId); err != nil {
return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
}
return parent
return nil
}
if err := stateMgr.RefreshState(); err != nil {
@ -76,6 +68,18 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
}
if v := stateMgr.State(); v == nil {
lockId, err := stateMgr.Lock(lockInfo)
if err != nil {
return nil, fmt.Errorf("Failed to lock state in etcd: %s.", err)
}
lockUnlock = func(parent error) error {
if err := stateMgr.Unlock(lockId); err != nil {
return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
}
return parent
}
if err := stateMgr.WriteState(states.NewState()); err != nil {
err = lockUnlock(err)
return nil, err

View File

@ -9,8 +9,8 @@ import (
"testing"
"time"
etcdv3 "github.com/coreos/etcd/clientv3"
"github.com/hashicorp/terraform/internal/backend"
etcdv3 "go.etcd.io/etcd/clientv3"
)
var (

View File

@ -8,11 +8,11 @@ import (
"sync"
"time"
etcdv3 "github.com/coreos/etcd/clientv3"
etcdv3sync "github.com/coreos/etcd/clientv3/concurrency"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/internal/states/remote"
"github.com/hashicorp/terraform/internal/states/statemgr"
etcdv3 "go.etcd.io/etcd/clientv3"
etcdv3sync "go.etcd.io/etcd/clientv3/concurrency"
)
const (

View File

@ -319,20 +319,14 @@ func (b *Backend) configure(ctx context.Context) error {
}
if endpoint == "" {
endpointItem, _ := b.getOSSEndpointByRegion(accessKey, secretKey, securityToken, region)
if endpointItem != nil && len(endpointItem.Endpoint) > 0 {
if len(endpointItem.Protocols.Protocols) > 0 {
// HTTP or HTTPS
schma = strings.ToLower(endpointItem.Protocols.Protocols[0])
for _, p := range endpointItem.Protocols.Protocols {
if strings.ToLower(p) == "https" {
schma = strings.ToLower(p)
break
}
}
endpointsResponse, _ := b.getOSSEndpointByRegion(accessKey, secretKey, securityToken, region)
for _, endpointItem := range endpointsResponse.Endpoints.Endpoint {
if endpointItem.Type == "openAPI" {
endpoint = endpointItem.Endpoint
break
}
endpoint = endpointItem.Endpoint
} else {
}
if endpoint == "" {
endpoint = fmt.Sprintf("oss-%s.aliyuncs.com", region)
}
}
@ -367,8 +361,8 @@ func (b *Backend) configure(ctx context.Context) error {
return err
}
func (b *Backend) getOSSEndpointByRegion(access_key, secret_key, security_token, region string) (*location.DescribeEndpointResponse, error) {
args := location.CreateDescribeEndpointRequest()
func (b *Backend) getOSSEndpointByRegion(access_key, secret_key, security_token, region string) (*location.DescribeEndpointsResponse, error) {
args := location.CreateDescribeEndpointsRequest()
args.ServiceCode = "oss"
args.Id = region
args.Domain = "location-readonly.aliyuncs.com"
@ -379,7 +373,7 @@ func (b *Backend) getOSSEndpointByRegion(access_key, secret_key, security_token,
}
locationClient.AppendUserAgent(TerraformUA, TerraformVersion)
endpointsResponse, err := locationClient.DescribeEndpoint(args)
endpointsResponse, err := locationClient.DescribeEndpoints(args)
if err != nil {
return nil, fmt.Errorf("Describe oss endpoint using region: %#v got an error: %#v.", region, err)
}

View File

@ -2,6 +2,7 @@ package oss
import (
"fmt"
"math/rand"
"os"
"testing"
"time"
@ -69,9 +70,10 @@ func TestBackendConfig(t *testing.T) {
func TestBackendConfigWorkSpace(t *testing.T) {
testACC(t)
bucketName := fmt.Sprintf("terraform-backend-oss-test-%d", rand.Intn(1000))
config := map[string]interface{}{
"region": "cn-beijing",
"bucket": "terraform-backend-oss-test",
"bucket": bucketName,
"prefix": "mystate",
"key": "first.tfstate",
"tablestore_endpoint": "https://terraformstate.cn-beijing.ots.aliyuncs.com",
@ -79,15 +81,15 @@ func TestBackendConfigWorkSpace(t *testing.T) {
}
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(config)).(*Backend)
createOSSBucket(t, b.ossClient, "terraform-backend-oss-test")
defer deleteOSSBucket(t, b.ossClient, "terraform-backend-oss-test")
createOSSBucket(t, b.ossClient, bucketName)
defer deleteOSSBucket(t, b.ossClient, bucketName)
if _, err := b.Workspaces(); err != nil {
t.Fatal(err.Error())
}
if !strings.HasPrefix(b.ossClient.Config.Endpoint, "https://oss-cn-beijing") {
t.Fatalf("Incorrect region was provided")
}
if b.bucketName != "terraform-backend-oss-test" {
if b.bucketName != bucketName {
t.Fatalf("Incorrect bucketName was provided")
}
if b.statePrefix != "mystate" {

View File

@ -111,7 +111,7 @@ func (b *Backend) configure(ctx context.Context) error {
query = `CREATE TABLE IF NOT EXISTS %s.%s (
id bigint NOT NULL DEFAULT nextval('public.global_states_id_seq') PRIMARY KEY,
name text,
name text UNIQUE,
data text
)`
if _, err := db.Exec(fmt.Sprintf(query, b.schemaName, statesTableName)); err != nil {

View File

@ -89,11 +89,13 @@ func TestBackendConfigSkipOptions(t *testing.T) {
SkipSchemaCreation bool
SkipTableCreation bool
SkipIndexCreation bool
TestIndexIsPresent bool
Setup func(t *testing.T, db *sql.DB, schemaName string)
}{
{
Name: "skip_schema_creation",
SkipSchemaCreation: true,
TestIndexIsPresent: true,
Setup: func(t *testing.T, db *sql.DB, schemaName string) {
// create the schema as a prerequisites
_, err := db.Query(fmt.Sprintf(`CREATE SCHEMA IF NOT EXISTS %s`, schemaName))
@ -103,8 +105,9 @@ func TestBackendConfigSkipOptions(t *testing.T) {
},
},
{
Name: "skip_table_creation",
SkipTableCreation: true,
Name: "skip_table_creation",
SkipTableCreation: true,
TestIndexIsPresent: true,
Setup: func(t *testing.T, db *sql.DB, schemaName string) {
// since the table needs to be already created the schema must be too
_, err := db.Query(fmt.Sprintf(`CREATE SCHEMA %s`, schemaName))
@ -122,8 +125,9 @@ func TestBackendConfigSkipOptions(t *testing.T) {
},
},
{
Name: "skip_index_creation",
SkipIndexCreation: true,
Name: "skip_index_creation",
SkipIndexCreation: true,
TestIndexIsPresent: true,
Setup: func(t *testing.T, db *sql.DB, schemaName string) {
// Everything need to exists for the index to be created
_, err := db.Query(fmt.Sprintf(`CREATE SCHEMA %s`, schemaName))
@ -144,6 +148,10 @@ func TestBackendConfigSkipOptions(t *testing.T) {
}
},
},
{
Name: "missing_index",
SkipIndexCreation: true,
},
}
for _, tc := range testCases {
@ -163,7 +171,9 @@ func TestBackendConfigSkipOptions(t *testing.T) {
t.Fatal(err)
}
tc.Setup(t, db, schemaName)
if tc.Setup != nil {
tc.Setup(t, db, schemaName)
}
defer db.Query(fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", schemaName))
b := backend.TestBackendConfig(t, New(), config).(*Backend)
@ -179,14 +189,16 @@ func TestBackendConfigSkipOptions(t *testing.T) {
if err != nil {
t.Fatal(err)
}
// Make sure that the index exists
query := `select count(*) from pg_indexes where schemaname=$1 and tablename=$2 and indexname=$3;`
var count int
if err := b.db.QueryRow(query, tc.Name, statesTableName, statesIndexName).Scan(&count); err != nil {
t.Fatal(err)
}
if count != 1 {
t.Fatalf("The index has not been created (%d)", count)
if tc.TestIndexIsPresent {
// Make sure that the index exists
query := `select count(*) from pg_indexes where schemaname=$1 and tablename=$2 and indexname=$3;`
var count int
if err := b.db.QueryRow(query, tc.Name, statesTableName, statesIndexName).Scan(&count); err != nil {
t.Fatal(err)
}
if count != 1 {
t.Fatalf("The index has not been created (%d)", count)
}
}
_, err = b.StateMgr(backend.DefaultStateName)
@ -202,6 +214,16 @@ func TestBackendConfigSkipOptions(t *testing.T) {
if c.Name != backend.DefaultStateName {
t.Fatal("RemoteClient name is not configured")
}
// Make sure that all workspace must have a unique name
_, err = db.Exec(fmt.Sprintf(`INSERT INTO %s.%s VALUES (100, 'unique_name_test', '')`, schemaName, statesTableName))
if err != nil {
t.Fatal(err)
}
_, err = db.Exec(fmt.Sprintf(`INSERT INTO %s.%s VALUES (101, 'unique_name_test', '')`, schemaName, statesTableName))
if err == nil {
t.Fatal("Creating two workspaces with the same name did not raise an error")
}
})
}

View File

@ -660,6 +660,12 @@ func (p *blockBodyDiffPrinter) writeNestedAttrDiff(
p.buf.WriteString("]")
case configschema.NestingMap:
// For the sake of handling nested blocks, we'll treat a null map
// the same as an empty map since the config language doesn't
// distinguish these anyway.
old = ctyNullBlockMapAsEmpty(old)
new = ctyNullBlockMapAsEmpty(new)
oldItems := old.AsValueMap()
newItems := new.AsValueMap()

View File

@ -2869,6 +2869,53 @@ func TestResourceChange_nestedSet(t *testing.T) {
func TestResourceChange_nestedMap(t *testing.T) {
testCases := map[string]testCase{
"creation from null": {
Action: plans.Update,
Mode: addrs.ManagedResourceMode,
Before: cty.ObjectVal(map[string]cty.Value{
"id": cty.NullVal(cty.String),
"ami": cty.NullVal(cty.String),
"disks": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{
"mount_point": cty.String,
"size": cty.String,
}))),
"root_block_device": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{
"volume_type": cty.String,
}))),
}),
After: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("i-02ae66f368e8518a9"),
"ami": cty.StringVal("ami-AFTER"),
"disks": cty.MapVal(map[string]cty.Value{
"disk_a": cty.ObjectVal(map[string]cty.Value{
"mount_point": cty.StringVal("/var/diska"),
"size": cty.NullVal(cty.String),
}),
}),
"root_block_device": cty.MapVal(map[string]cty.Value{
"a": cty.ObjectVal(map[string]cty.Value{
"volume_type": cty.StringVal("gp2"),
}),
}),
}),
RequiredReplace: cty.NewPathSet(),
Schema: testSchema(configschema.NestingMap),
ExpectedOutput: ` # test_instance.example will be updated in-place
~ resource "test_instance" "example" {
+ ami = "ami-AFTER"
+ disks = {
+ "disk_a" = {
+ mount_point = "/var/diska"
},
}
+ id = "i-02ae66f368e8518a9"
+ root_block_device "a" {
+ volume_type = "gp2"
}
}
`,
},
"in-place update - creation": {
Action: plans.Update,
Mode: addrs.ManagedResourceMode,

View File

@ -303,12 +303,16 @@ func testJSONViewOutputEqualsFull(t *testing.T, output string, want []map[string
gotLines := strings.Split(output, "\n")
if len(gotLines) != len(want) {
t.Fatalf("unexpected number of messages. got %d, want %d", len(gotLines), len(want))
t.Errorf("unexpected number of messages. got %d, want %d", len(gotLines), len(want))
}
// Unmarshal each line and compare to the expected value
for i := range gotLines {
var gotStruct map[string]interface{}
if i >= len(want) {
t.Error("reached end of want messages too soon")
break
}
wantStruct := want[i]
if err := json.Unmarshal([]byte(gotLines[i]), &gotStruct); err != nil {
@ -323,12 +327,12 @@ func testJSONViewOutputEqualsFull(t *testing.T, output string, want []map[string
// Verify the timestamp format
if _, err := time.Parse("2006-01-02T15:04:05.000000Z07:00", timestamp.(string)); err != nil {
t.Fatalf("error parsing timestamp: %s", err)
t.Errorf("error parsing timestamp on line %d: %s", i, err)
}
}
if !cmp.Equal(wantStruct, gotStruct) {
t.Fatalf("unexpected output on line %d:\n%s", i, cmp.Diff(wantStruct, gotStruct))
t.Errorf("unexpected output on line %d:\n%s", i, cmp.Diff(wantStruct, gotStruct))
}
}
}

View File

@ -3,6 +3,7 @@ package views
import (
"bytes"
"fmt"
"sort"
"strings"
"github.com/hashicorp/terraform/internal/addrs"
@ -202,6 +203,7 @@ func (v *OperationJSON) resourceDrift(oldState, newState *states.State, schemas
// resource instances.
return nil
}
var changes []*json.ResourceInstanceChange
for _, ms := range oldState.Modules {
for _, rs := range ms.Resources {
if rs.Addr.Resource.Mode != addrs.ManagedResourceMode {
@ -266,10 +268,18 @@ func (v *OperationJSON) resourceDrift(oldState, newState *states.State, schemas
Action: action,
},
}
v.view.ResourceDrift(json.NewResourceInstanceChange(change))
changes = append(changes, json.NewResourceInstanceChange(change))
}
}
}
// Sort the change structs lexically by address to give stable output
sort.Slice(changes, func(i, j int) bool { return changes[i].Resource.Addr < changes[j].Resource.Addr })
for _, change := range changes {
v.view.ResourceDrift(change)
}
return nil
}

View File

@ -517,103 +517,10 @@ func TestOperationJSON_plan(t *testing.T) {
},
},
},
PrevRunState: states.BuildState(func(state *states.SyncState) {
// Update
state.SetResourceInstanceCurrent(
boop.Instance(addrs.IntKey(0)).Absolute(root),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"foo":"bar"}`),
},
root.ProviderConfigDefault(addrs.NewDefaultProvider("test")),
)
// Delete
state.SetResourceInstanceCurrent(
boop.Instance(addrs.IntKey(1)).Absolute(root),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"foo":"boop"}`),
},
root.ProviderConfigDefault(addrs.NewDefaultProvider("test")),
)
// No-op
state.SetResourceInstanceCurrent(
beep.Instance(addrs.NoKey).Absolute(root),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"foo":"boop"}`),
},
root.ProviderConfigDefault(addrs.NewDefaultProvider("test")),
)
}),
PriorState: states.BuildState(func(state *states.SyncState) {
// Update
state.SetResourceInstanceCurrent(
boop.Instance(addrs.IntKey(0)).Absolute(root),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"foo":"baz"}`),
},
root.ProviderConfigDefault(addrs.NewDefaultProvider("test")),
)
// Delete
state.SetResourceInstanceCurrent(
boop.Instance(addrs.IntKey(1)).Absolute(root),
nil,
root.ProviderConfigDefault(addrs.NewDefaultProvider("test")),
)
// No-op
state.SetResourceInstanceCurrent(
beep.Instance(addrs.NoKey).Absolute(root),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"foo":"boop"}`),
},
root.ProviderConfigDefault(addrs.NewDefaultProvider("test")),
)
}),
}
v.Plan(plan, testSchemas())
want := []map[string]interface{}{
// Drift detected: update
{
"@level": "info",
"@message": "test_resource.boop[0]: Drift detected (update)",
"@module": "terraform.ui",
"type": "resource_drift",
"change": map[string]interface{}{
"action": "update",
"resource": map[string]interface{}{
"addr": "test_resource.boop[0]",
"implied_provider": "test",
"module": "",
"resource": "test_resource.boop[0]",
"resource_key": float64(0),
"resource_name": "boop",
"resource_type": "test_resource",
},
},
},
// Drift detected: delete
{
"@level": "info",
"@message": "test_resource.boop[1]: Drift detected (delete)",
"@module": "terraform.ui",
"type": "resource_drift",
"change": map[string]interface{}{
"action": "delete",
"resource": map[string]interface{}{
"addr": "test_resource.boop[1]",
"implied_provider": "test",
"module": "",
"resource": "test_resource.boop[1]",
"resource_key": float64(1),
"resource_name": "boop",
"resource_type": "test_resource",
},
},
},
// Create-then-delete should result in replace
{
"@level": "info",
@ -728,6 +635,134 @@ func TestOperationJSON_plan(t *testing.T) {
testJSONViewOutputEquals(t, done(t).Stdout(), want)
}
func TestOperationJSON_planDrift(t *testing.T) {
streams, done := terminal.StreamsForTesting(t)
v := &OperationJSON{view: NewJSONView(NewView(streams))}
root := addrs.RootModuleInstance
boop := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_resource", Name: "boop"}
beep := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_resource", Name: "beep"}
derp := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_resource", Name: "derp"}
plan := &plans.Plan{
Changes: &plans.Changes{
Resources: []*plans.ResourceInstanceChangeSrc{},
},
PrevRunState: states.BuildState(func(state *states.SyncState) {
// Update
state.SetResourceInstanceCurrent(
boop.Instance(addrs.NoKey).Absolute(root),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"foo":"bar"}`),
},
root.ProviderConfigDefault(addrs.NewDefaultProvider("test")),
)
// Delete
state.SetResourceInstanceCurrent(
beep.Instance(addrs.NoKey).Absolute(root),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"foo":"boop"}`),
},
root.ProviderConfigDefault(addrs.NewDefaultProvider("test")),
)
// No-op
state.SetResourceInstanceCurrent(
derp.Instance(addrs.NoKey).Absolute(root),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"foo":"boop"}`),
},
root.ProviderConfigDefault(addrs.NewDefaultProvider("test")),
)
}),
PriorState: states.BuildState(func(state *states.SyncState) {
// Update
state.SetResourceInstanceCurrent(
boop.Instance(addrs.NoKey).Absolute(root),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"foo":"baz"}`),
},
root.ProviderConfigDefault(addrs.NewDefaultProvider("test")),
)
// Delete
state.SetResourceInstanceCurrent(
beep.Instance(addrs.NoKey).Absolute(root),
nil,
root.ProviderConfigDefault(addrs.NewDefaultProvider("test")),
)
// No-op
state.SetResourceInstanceCurrent(
derp.Instance(addrs.NoKey).Absolute(root),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"foo":"boop"}`),
},
root.ProviderConfigDefault(addrs.NewDefaultProvider("test")),
)
}),
}
v.Plan(plan, testSchemas())
want := []map[string]interface{}{
// Drift detected: delete
{
"@level": "info",
"@message": "test_resource.beep: Drift detected (delete)",
"@module": "terraform.ui",
"type": "resource_drift",
"change": map[string]interface{}{
"action": "delete",
"resource": map[string]interface{}{
"addr": "test_resource.beep",
"implied_provider": "test",
"module": "",
"resource": "test_resource.beep",
"resource_key": nil,
"resource_name": "beep",
"resource_type": "test_resource",
},
},
},
// Drift detected: update
{
"@level": "info",
"@message": "test_resource.boop: Drift detected (update)",
"@module": "terraform.ui",
"type": "resource_drift",
"change": map[string]interface{}{
"action": "update",
"resource": map[string]interface{}{
"addr": "test_resource.boop",
"implied_provider": "test",
"module": "",
"resource": "test_resource.boop",
"resource_key": nil,
"resource_name": "boop",
"resource_type": "test_resource",
},
},
},
// No changes
{
"@level": "info",
"@message": "Plan: 0 to add, 0 to change, 0 to destroy.",
"@module": "terraform.ui",
"type": "change_summary",
"changes": map[string]interface{}{
"operation": "plan",
"add": float64(0),
"change": float64(0),
"remove": float64(0),
},
},
}
testJSONViewOutputEquals(t, done(t).Stdout(), want)
}
func TestOperationJSON_plannedChange(t *testing.T) {
streams, done := terminal.StreamsForTesting(t)
v := &OperationJSON{view: NewJSONView(NewView(streams))}

View File

@ -211,9 +211,15 @@ func (a *Attribute) decoderSpec(name string) hcldec.Spec {
// belong to their own cty.Object definitions. It is used in other functions
// which themselves handle that recursion.
func listOptionalAttrsFromObject(o *Object) []string {
var ret []string
ret := make([]string, 0)
// This is unlikely to happen outside of tests.
if o == nil {
return ret
}
for name, attr := range o.Attributes {
if attr.Optional == true {
if attr.Optional || attr.Computed {
ret = append(ret, name)
}
}

View File

@ -1,10 +1,12 @@
package configschema
import (
"sort"
"testing"
"github.com/apparentlymart/go-dump/dump"
"github.com/davecgh/go-spew/spew"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hcldec"
@ -885,3 +887,43 @@ func TestAttributeDecoderSpec_panic(t *testing.T) {
attrS.decoderSpec("attr")
t.Errorf("expected panic")
}
func TestListOptionalAttrsFromObject(t *testing.T) {
tests := []struct {
input *Object
want []string
}{
{
nil,
[]string{},
},
{
&Object{},
[]string{},
},
{
&Object{
Nesting: NestingSingle,
Attributes: map[string]*Attribute{
"optional": {Type: cty.String, Optional: true},
"required": {Type: cty.Number, Required: true},
"computed": {Type: cty.List(cty.Bool), Computed: true},
"optional_computed": {Type: cty.Map(cty.Bool), Optional: true, Computed: true},
},
},
[]string{"optional", "computed", "optional_computed"},
},
}
for _, test := range tests {
got := listOptionalAttrsFromObject(test.input)
// order is irrelevant
sort.Strings(got)
sort.Strings(test.want)
if diff := cmp.Diff(got, test.want); diff != "" {
t.Fatalf("wrong result: %s\n", diff)
}
}
}

View File

@ -79,7 +79,7 @@ func (o *Object) ImpliedType() cty.Type {
case NestingSet:
return cty.Set(ret)
default: // Should never happen
panic("invalid Nesting")
return cty.EmptyObject
}
}

View File

@ -37,6 +37,7 @@ func TestBlockImpliedType(t *testing.T) {
"optional_computed": {
Type: cty.Map(cty.Bool),
Optional: true,
Computed: true,
},
},
},
@ -132,26 +133,18 @@ func TestObjectImpliedType(t *testing.T) {
nil,
cty.EmptyObject,
},
"empty": {
&Object{},
cty.EmptyObject,
},
"attributes": {
&Object{
Nesting: NestingSingle,
Attributes: map[string]*Attribute{
"optional": {
Type: cty.String,
Optional: true,
},
"required": {
Type: cty.Number,
Required: true,
},
"computed": {
Type: cty.List(cty.Bool),
Computed: true,
},
"optional_computed": {
Type: cty.Map(cty.Bool),
Optional: true,
},
"optional": {Type: cty.String, Optional: true},
"required": {Type: cty.Number, Required: true},
"computed": {Type: cty.List(cty.Bool), Computed: true},
"optional_computed": {Type: cty.Map(cty.Bool), Optional: true, Computed: true},
},
},
cty.ObjectWithOptionalAttrs(
@ -161,7 +154,7 @@ func TestObjectImpliedType(t *testing.T) {
"computed": cty.List(cty.Bool),
"optional_computed": cty.Map(cty.Bool),
},
[]string{"optional", "optional_computed"},
[]string{"optional", "computed", "optional_computed"},
),
},
"nested attributes": {
@ -172,21 +165,42 @@ func TestObjectImpliedType(t *testing.T) {
NestedType: &Object{
Nesting: NestingSingle,
Attributes: map[string]*Attribute{
"optional": {
Type: cty.String,
Optional: true,
},
"required": {
Type: cty.Number,
Required: true,
},
"computed": {
Type: cty.List(cty.Bool),
Computed: true,
},
"optional_computed": {
Type: cty.Map(cty.Bool),
Optional: true,
"optional": {Type: cty.String, Optional: true},
"required": {Type: cty.Number, Required: true},
"computed": {Type: cty.List(cty.Bool), Computed: true},
"optional_computed": {Type: cty.Map(cty.Bool), Optional: true, Computed: true},
},
},
Optional: true,
},
},
},
cty.ObjectWithOptionalAttrs(map[string]cty.Type{
"nested_type": cty.ObjectWithOptionalAttrs(map[string]cty.Type{
"optional": cty.String,
"required": cty.Number,
"computed": cty.List(cty.Bool),
"optional_computed": cty.Map(cty.Bool),
}, []string{"optional", "computed", "optional_computed"}),
}, []string{"nested_type"}),
},
"nested object-type attributes": {
&Object{
Nesting: NestingSingle,
Attributes: map[string]*Attribute{
"nested_type": {
NestedType: &Object{
Nesting: NestingSingle,
Attributes: map[string]*Attribute{
"optional": {Type: cty.String, Optional: true},
"required": {Type: cty.Number, Required: true},
"computed": {Type: cty.List(cty.Bool), Computed: true},
"optional_computed": {Type: cty.Map(cty.Bool), Optional: true, Computed: true},
"object": {
Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
"optional": cty.String,
"required": cty.Number,
}, []string{"optional"}),
},
},
},
@ -200,7 +214,8 @@ func TestObjectImpliedType(t *testing.T) {
"required": cty.Number,
"computed": cty.List(cty.Bool),
"optional_computed": cty.Map(cty.Bool),
}, []string{"optional", "optional_computed"}),
"object": cty.ObjectWithOptionalAttrs(map[string]cty.Type{"optional": cty.String, "required": cty.Number}, []string{"optional"}),
}, []string{"optional", "computed", "optional_computed"}),
}, []string{"nested_type"}),
},
"NestingList": {

View File

@ -7,13 +7,19 @@ import (
// AttributeByPath looks up the Attribute schema which corresponds to the given
// cty.Path. A nil value is returned if the given path does not correspond to a
// specific attribute.
// TODO: this will need to be updated for nested attributes
func (b *Block) AttributeByPath(path cty.Path) *Attribute {
block := b
for _, step := range path {
for i, step := range path {
switch step := step.(type) {
case cty.GetAttrStep:
if attr := block.Attributes[step.Name]; attr != nil {
// If the Attribute is defined with a NestedType and there's
// more to the path, descend into the NestedType
if attr.NestedType != nil && i < len(path)-1 {
return attr.NestedType.AttributeByPath(path[i+1:])
} else if i < len(path)-1 { // There's more to the path, but not more to this Attribute.
return nil
}
return attr
}
@ -27,3 +33,23 @@ func (b *Block) AttributeByPath(path cty.Path) *Attribute {
}
return nil
}
// AttributeByPath recurses through a NestedType to look up the Attribute scheme
// which corresponds to the given cty.Path. A nil value is returned if the given
// path does not correspond to a specific attribute.
func (o *Object) AttributeByPath(path cty.Path) *Attribute {
for i, step := range path {
switch step := step.(type) {
case cty.GetAttrStep:
if attr := o.Attributes[step.Name]; attr != nil {
if attr.NestedType != nil && i < len(path)-1 {
return attr.NestedType.AttributeByPath(path[i+1:])
} else if i < len(path)-1 { // There's more to the path, but not more to this Attribute.
return nil
}
return attr
}
}
}
return nil
}

View File

@ -11,6 +11,24 @@ func TestAttributeByPath(t *testing.T) {
Attributes: map[string]*Attribute{
"a1": {Description: "a1"},
"a2": {Description: "a2"},
"a3": {
Description: "a3",
NestedType: &Object{
Nesting: NestingList,
Attributes: map[string]*Attribute{
"nt1": {Description: "nt1"},
"nt2": {
Description: "nt2",
NestedType: &Object{
Nesting: NestingSingle,
Attributes: map[string]*Attribute{
"deeply_nested": {Description: "deeply_nested"},
},
},
},
},
},
},
},
BlockTypes: map[string]*NestedBlock{
"b1": {
@ -66,6 +84,16 @@ func TestAttributeByPath(t *testing.T) {
"a2",
true,
},
{
cty.GetAttrPath("a3").IndexInt(1).GetAttr("nt2"),
"nt2",
true,
},
{
cty.GetAttrPath("a3").IndexInt(1).GetAttr("b2").IndexString("foo").GetAttr("no"),
"missing",
false,
},
{
cty.GetAttrPath("b1"),
"block",
@ -119,3 +147,83 @@ func TestAttributeByPath(t *testing.T) {
})
}
}
func TestObject_AttributeByPath(t *testing.T) {
obj := &Object{
Nesting: NestingList,
Attributes: map[string]*Attribute{
"a1": {Description: "a1"},
"a2": {
Description: "a2",
NestedType: &Object{
Nesting: NestingSingle,
Attributes: map[string]*Attribute{
"n1": {Description: "n1"},
"n2": {
Description: "n2",
NestedType: &Object{
Attributes: map[string]*Attribute{
"dn1": {Description: "dn1"},
},
},
},
},
},
},
},
}
tests := []struct {
path cty.Path
attrDescription string
exists bool
}{
{
cty.GetAttrPath("a2"),
"a2",
true,
},
{
cty.GetAttrPath("a3"),
"missing",
false,
},
{
cty.GetAttrPath("a2").IndexString("foo").GetAttr("n1"),
"n1",
true,
},
{
cty.GetAttrPath("a2").IndexString("foo").GetAttr("n2").IndexInt(11).GetAttr("dn1"),
"dn1",
true,
},
{
cty.GetAttrPath("a2").IndexString("foo").GetAttr("n2").IndexInt(11).GetAttr("dn1").IndexString("hello").GetAttr("nope"),
"missing_nested",
false,
},
}
for _, tc := range tests {
t.Run(tc.attrDescription, func(t *testing.T) {
attr := obj.AttributeByPath(tc.path)
if !tc.exists && attr == nil {
return
}
if !tc.exists && attr != nil {
t.Fatalf("found Attribute, expected nil from path %#v\n", tc.path)
}
if attr == nil {
t.Fatalf("missing attribute from path %#v\n", tc.path)
}
if attr.Description != tc.attrDescription {
t.Fatalf("expected Attribute for %q, got %#v\n", tc.attrDescription, attr)
}
})
}
}

View File

@ -58,7 +58,7 @@ func TestStaticValidateTraversal(t *testing.T) {
},
{
`obj.str.nonexist`,
`Unsupported attribute: This value does not have any attributes.`,
`Unsupported attribute: Can't access attributes on a primitive-typed value (string).`,
},
{
`obj.list`,

View File

@ -55,7 +55,7 @@ func init() {
configureRequestTimeout()
}
var SupportedPluginProtocols = MustParseVersionConstraints("~> 5")
var SupportedPluginProtocols = MustParseVersionConstraints(">= 5, <7")
// registryClient is a client for the provider registry protocol that is
// specialized only for the needs of this package. It's not intended as a

View File

@ -218,6 +218,10 @@ func fakeRegistryHandler(resp http.ResponseWriter, req *http.Request) {
resp.Header().Set("Content-Type", "application/json")
resp.WriteHeader(200)
resp.Write([]byte(`{"versions":[{"version":"1.0.0","protocols":["0.1"]}]}`))
case "weaksauce/protocol-six":
resp.Header().Set("Content-Type", "application/json")
resp.WriteHeader(200)
resp.Write([]byte(`{"versions":[{"version":"1.0.0","protocols":["6.0"]}]}`))
case "weaksauce/no-versions":
resp.Header().Set("Content-Type", "application/json")
resp.WriteHeader(200)
@ -412,6 +416,12 @@ func TestFindClosestProtocolCompatibleVersion(t *testing.T) {
versions.Unspecified,
``,
},
"provider protocol six": {
addrs.MustParseProviderSourceString("example.com/weaksauce/protocol-six"),
MustParseVersion("1.0.0"),
MustParseVersion("1.0.0"),
``,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {

View File

@ -8,6 +8,11 @@ import (
"strings"
"syscall"
// go.etcd.io/etcd imports capnslog, which calls log.SetOutput in its
// init() function, so importing it here means that our log.SetOutput
// wins. this is fixed in coreos v3.5, which is not released yet. See
// https://github.com/etcd-io/etcd/issues/12498 for more information.
_ "github.com/coreos/pkg/capnslog"
"github.com/hashicorp/go-hclog"
)

View File

@ -1457,7 +1457,7 @@ func TestProposedNew(t *testing.T) {
"computed": cty.String,
"optional_computed": cty.String,
"required": cty.String,
}, []string{"optional", "optional_computed"}),
}, []string{"computed", "optional", "optional_computed"}),
}))),
}),
},

View File

@ -0,0 +1,171 @@
package refactoring
import (
"fmt"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/dag"
"github.com/hashicorp/terraform/internal/states"
)
type MoveResult struct {
From, To addrs.AbsResourceInstance
}
// ApplyMoves modifies in-place the given state object so that any existing
// objects that are matched by a "from" argument of one of the move statements
// will be moved to instead appear at the "to" argument of that statement.
//
// The result is a map from the unique key of each absolute address that was
// either the source or destination of a move to a MoveResult describing
// what happened at that address.
//
// ApplyMoves does not have any error situations itself, and will instead just
// ignore any unresolvable move statements. Validation of a set of moves is
// a separate concern applied to the configuration, because validity of
// moves is always dependent only on the configuration, not on the state.
//
// ApplyMoves expects exclusive access to the given state while it's running.
// Don't read or write any part of the state structure until ApplyMoves returns.
func ApplyMoves(stmts []MoveStatement, state *states.State) map[addrs.UniqueKey]MoveResult {
// The methodology here is to construct a small graph of all of the move
// statements where the edges represent where a particular statement
// is either chained from or nested inside the effect of another statement.
// That then means we can traverse the graph in topological sort order
// to gradually move objects through potentially multiple moves each.
g := buildMoveStatementGraph(stmts)
// If there are any cycles in the graph then we'll not take any action
// at all. The separate validation step should detect this and return
// an error.
if len(g.Cycles()) != 0 {
return nil
}
// The starting nodes are the ones that don't depend on any other nodes.
startNodes := make(dag.Set, len(stmts))
for _, v := range g.Vertices() {
if len(g.UpEdges(v)) == 0 {
startNodes.Add(v)
}
}
results := make(map[addrs.UniqueKey]MoveResult)
g.DepthFirstWalk(startNodes, func(v dag.Vertex, depth int) error {
stmt := v.(*MoveStatement)
for _, ms := range state.Modules {
modAddr := ms.Addr
if !stmt.From.SelectsModule(modAddr) {
continue
}
// We now know that the current module is relevant but what
// we'll do with it depends on the object kind.
switch kind := stmt.ObjectKind(); kind {
case addrs.MoveEndpointModule:
// For a module endpoint we just try the module address
// directly.
if newAddr, matches := modAddr.MoveDestination(stmt.From, stmt.To); matches {
// We need to visit all of the resource instances in the
// module and record them individually as results.
for _, rs := range ms.Resources {
relAddr := rs.Addr.Resource
for key := range rs.Instances {
oldInst := relAddr.Instance(key).Absolute(modAddr)
newInst := relAddr.Instance(key).Absolute(newAddr)
result := MoveResult{
From: oldInst,
To: newInst,
}
results[oldInst.UniqueKey()] = result
results[newInst.UniqueKey()] = result
}
}
state.MoveModuleInstance(modAddr, newAddr)
continue
}
case addrs.MoveEndpointResource:
// For a resource endpoint we need to search each of the
// resources and resource instances in the module.
for _, rs := range ms.Resources {
rAddr := rs.Addr
if newAddr, matches := rAddr.MoveDestination(stmt.From, stmt.To); matches {
for key := range rs.Instances {
oldInst := rAddr.Instance(key)
newInst := newAddr.Instance(key)
result := MoveResult{
From: oldInst,
To: newInst,
}
results[oldInst.UniqueKey()] = result
results[newInst.UniqueKey()] = result
}
state.MoveAbsResource(rAddr, newAddr)
continue
}
for key := range rs.Instances {
iAddr := rAddr.Instance(key)
if newAddr, matches := iAddr.MoveDestination(stmt.From, stmt.To); matches {
result := MoveResult{From: iAddr, To: newAddr}
results[iAddr.UniqueKey()] = result
results[newAddr.UniqueKey()] = result
state.MoveAbsResourceInstance(iAddr, newAddr)
continue
}
}
}
default:
panic(fmt.Sprintf("unhandled move object kind %s", kind))
}
}
return nil
})
// FIXME: In the case of either chained or nested moves, "results" will
// be left in a pretty interesting shape where the "old" address will
// refer to a result that describes only the first step, while the "new"
// address will refer to a result that describes only the last step.
// To make that actually useful we'll need a different strategy where
// the result describes the _effective_ source and destination, skipping
// over any intermediate steps we took to get there, so that ultimately
// we'll have enough information to annotate items in the plan with the
// addresses the originally moved from.
return results
}
// buildMoveStatementGraph constructs a dependency graph of the given move
// statements, where the nodes are all pointers to statements in the given
// slice and the edges represent either chaining or nesting relationships.
//
// buildMoveStatementGraph doesn't do any validation of the graph, so it
// may contain cycles and other sorts of invalidity.
func buildMoveStatementGraph(stmts []MoveStatement) *dag.AcyclicGraph {
g := &dag.AcyclicGraph{}
for _, stmt := range stmts {
// The graph nodes are pointers to the actual statements directly.
g.Add(&stmt)
}
// Now we'll add the edges representing chaining and nesting relationships.
// We assume that a reasonable configuration will have at most tens of
// move statements and thus this N*M algorithm is acceptable.
for dependerI := range stmts {
depender := &stmts[dependerI]
for dependeeI := range stmts {
dependee := &stmts[dependeeI]
dependeeTo := dependee.To
dependerFrom := depender.From
if dependerFrom.CanChainFrom(dependeeTo) || dependerFrom.NestedWithin(dependeeTo) {
g.Connect(dag.BasicEdge(depender, dependee))
}
}
}
return g
}

View File

@ -0,0 +1,213 @@
package refactoring
import (
"fmt"
"sort"
"strings"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/states"
)
func TestApplyMoves(t *testing.T) {
// TODO: Renable this once we're ready to implement the intended behaviors
// it is describing.
t.Skip("ApplyMoves is not yet fully implemented")
providerAddr := addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.MustParseProviderSourceString("example.com/foo/bar"),
}
rootNoKeyResourceAddr := [...]addrs.AbsResourceInstance{
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "foo",
Name: "from",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "foo",
Name: "to",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
}
rootIntKeyResourceAddr := [...]addrs.AbsResourceInstance{
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "foo",
Name: "from",
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "foo",
Name: "to",
}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),
}
tests := map[string]struct {
Stmts []MoveStatement
State *states.State
WantResults map[addrs.UniqueKey]MoveResult
WantInstanceAddrs []string
}{
"no moves and empty state": {
[]MoveStatement{},
states.NewState(),
nil,
nil,
},
"no moves": {
[]MoveStatement{},
states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
rootNoKeyResourceAddr[0],
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{}`),
},
providerAddr,
)
}),
nil,
[]string{
`foo.from`,
},
},
"single move of whole singleton resource": {
[]MoveStatement{
testMoveStatement(t, "", "foo.from", "foo.to"),
},
states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
rootNoKeyResourceAddr[0],
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{}`),
},
providerAddr,
)
}),
map[addrs.UniqueKey]MoveResult{
rootNoKeyResourceAddr[0].UniqueKey(): {
From: rootNoKeyResourceAddr[0],
To: rootNoKeyResourceAddr[1],
},
rootNoKeyResourceAddr[1].UniqueKey(): {
From: rootNoKeyResourceAddr[1],
To: rootNoKeyResourceAddr[1],
},
},
[]string{
`foo.to`,
},
},
"single move of whole 'count' resource": {
[]MoveStatement{
testMoveStatement(t, "", "foo.from", "foo.to"),
},
states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
rootIntKeyResourceAddr[0],
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{}`),
},
providerAddr,
)
}),
map[addrs.UniqueKey]MoveResult{
rootNoKeyResourceAddr[0].UniqueKey(): {
From: rootIntKeyResourceAddr[0],
To: rootIntKeyResourceAddr[1],
},
rootNoKeyResourceAddr[1].UniqueKey(): {
From: rootIntKeyResourceAddr[0],
To: rootIntKeyResourceAddr[1],
},
},
[]string{
`foo.to[0]`,
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
var stmtsBuf strings.Builder
for _, stmt := range test.Stmts {
fmt.Fprintf(&stmtsBuf, "- from: %s\n to: %s", stmt.From, stmt.To)
}
t.Logf("move statements:\n%s", stmtsBuf.String())
t.Logf("resource instances in prior state:\n%s", spew.Sdump(allResourceInstanceAddrsInState(test.State)))
state := test.State.DeepCopy() // don't modify the test case in-place
gotResults := ApplyMoves(test.Stmts, state)
if diff := cmp.Diff(test.WantResults, gotResults); diff != "" {
t.Errorf("wrong results\n%s", diff)
}
gotInstAddrs := allResourceInstanceAddrsInState(state)
if diff := cmp.Diff(test.WantInstanceAddrs, gotInstAddrs); diff != "" {
t.Errorf("wrong resource instances in final state\n%s", diff)
}
})
}
}
func testMoveStatement(t *testing.T, module string, from string, to string) MoveStatement {
t.Helper()
moduleAddr := addrs.RootModule
if len(module) != 0 {
moduleAddr = addrs.Module(strings.Split(module, "."))
}
fromTraversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(from), "from", hcl.InitialPos)
if hclDiags.HasErrors() {
t.Fatalf("invalid 'from' argument: %s", hclDiags.Error())
}
fromAddr, diags := addrs.ParseMoveEndpoint(fromTraversal)
if diags.HasErrors() {
t.Fatalf("invalid 'from' argument: %s", diags.Err().Error())
}
toTraversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(to), "to", hcl.InitialPos)
if diags.HasErrors() {
t.Fatalf("invalid 'to' argument: %s", hclDiags.Error())
}
toAddr, diags := addrs.ParseMoveEndpoint(toTraversal)
if diags.HasErrors() {
t.Fatalf("invalid 'from' argument: %s", diags.Err().Error())
}
fromInModule, toInModule := addrs.UnifyMoveEndpoints(moduleAddr, fromAddr, toAddr)
if fromInModule == nil || toInModule == nil {
t.Fatalf("incompatible endpoints")
}
return MoveStatement{
From: fromInModule,
To: toInModule,
// DeclRange not populated because it's unimportant for our tests
}
}
func allResourceInstanceAddrsInState(state *states.State) []string {
var ret []string
for _, ms := range state.Modules {
for _, rs := range ms.Resources {
for key := range rs.Instances {
ret = append(ret, rs.Addr.Instance(key).String())
}
}
}
sort.Strings(ret)
return ret
}

View File

@ -0,0 +1,52 @@
package refactoring
import (
"fmt"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/tfdiags"
)
type MoveStatement struct {
From, To *addrs.MoveEndpointInModule
DeclRange tfdiags.SourceRange
}
// FindMoveStatements recurses through the modules of the given configuration
// and returns a flat set of all "moved" blocks defined within, in a
// deterministic but undefined order.
func FindMoveStatements(rootCfg *configs.Config) []MoveStatement {
return findMoveStatements(rootCfg, nil)
}
func findMoveStatements(cfg *configs.Config, into []MoveStatement) []MoveStatement {
modAddr := cfg.Path
for _, mc := range cfg.Module.Moved {
fromAddr, toAddr := addrs.UnifyMoveEndpoints(modAddr, mc.From, mc.To)
if fromAddr == nil || toAddr == nil {
// Invalid combination should've been caught during original
// configuration decoding, in the configs package.
panic(fmt.Sprintf("incompatible move endpoints in %s", mc.DeclRange))
}
into = append(into, MoveStatement{
From: fromAddr,
To: toAddr,
DeclRange: tfdiags.SourceRangeFromHCL(mc.DeclRange),
})
}
for _, childCfg := range cfg.Children {
into = findMoveStatements(childCfg, into)
}
return into
}
func (s *MoveStatement) ObjectKind() addrs.MoveEndpointKind {
// addrs.UnifyMoveEndpoints guarantees that both of our addresses have
// the same kind, so we can just arbitrary use From and assume To will
// match it.
return s.From.ObjectKind()
}

View File

@ -0,0 +1,40 @@
package refactoring
import (
"fmt"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/instances"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// ValidateMoves tests whether all of the given move statements comply with
// both the single-statement validation rules and the "big picture" rules
// that constrain statements in relation to one another.
//
// The validation rules are primarily in terms of the configuration, but
// ValidateMoves also takes the expander that resulted from creating a plan
// so that it can see which instances are defined for each module and resource,
// to precisely validate move statements involving specific-instance addresses.
//
// Because validation depends on the planning result but move execution must
// happen _before_ planning, we have the unusual situation where sibling
// function ApplyMoves must run before ValidateMoves and must therefore
// tolerate and ignore any invalid statements. The plan walk will then
// construct in incorrect plan (because it'll be starting from the wrong
// prior state) but ValidateMoves will block actually showing that invalid
// plan to the user.
func ValidateMoves(stmts []MoveStatement, rootCfg *configs.Config, expander *instances.Expander) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
g := buildMoveStatementGraph(stmts)
if len(g.Cycles()) != 0 {
// TODO: proper error messages for this
diags = diags.Append(fmt.Errorf("move statement cycles"))
}
// TODO: Various other validation rules
return diags
}

View File

@ -15,6 +15,7 @@ import (
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/provisioners"
"github.com/hashicorp/terraform/internal/refactoring"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
@ -608,6 +609,33 @@ The -target option is not for routine use, and is provided only for exceptional
))
}
moveStmts := refactoring.FindMoveStatements(c.config)
if len(moveStmts) != 0 {
// TEMP: we haven't fully implemented moving yet, so we'll just
// reject it outright for now to reduce confusion.
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Moves are not yet supported",
"There is currently no handling of \"moved\" blocks in the configuration.",
))
}
moveResults := refactoring.ApplyMoves(moveStmts, c.prevRunState)
if len(c.targets) > 0 {
for _, result := range moveResults {
matchesTarget := false
for _, targetAddr := range c.targets {
if targetAddr.TargetContains(result.From) {
matchesTarget = true
break
}
}
if !matchesTarget {
// TODO: Return an error stating that a targeted plan is
// only valid if it includes this address that was moved.
}
}
}
var plan *plans.Plan
var planDiags tfdiags.Diagnostics
switch c.planMode {
@ -625,6 +653,10 @@ The -target option is not for routine use, and is provided only for exceptional
return nil, diags
}
// TODO: Call refactoring.ValidateMoves, but need to figure out how to
// get hold of the plan's expander here, or somehow otherwise export
// the necessary subset of its data for ValidateMoves to do its work.
// convert the variables into the format expected for the plan
varVals := make(map[string]plans.DynamicValue, len(c.variables))
for k, iv := range c.variables {

View File

@ -508,3 +508,69 @@ output "out" {
}
}
}
func TestContext2Apply_ignoreImpureFunctionChanges(t *testing.T) {
// The impure function call should not cause a planned change with
// ignore_changes
m := testModuleInline(t, map[string]string{
"main.tf": `
variable "pw" {
sensitive = true
default = "foo"
}
resource "test_object" "x" {
test_map = {
string = "X${bcrypt(var.pw)}"
}
lifecycle {
ignore_changes = [ test_map["string"] ]
}
}
resource "test_object" "y" {
test_map = {
string = "X${bcrypt(var.pw)}"
}
lifecycle {
ignore_changes = [ test_map ]
}
}
`,
})
p := simpleMockProvider()
ctx := testContext2(t, &ContextOpts{
Config: m,
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
})
_, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatal(diags.ErrWithWarnings())
}
_, diags = ctx.Apply()
if diags.HasErrors() {
t.Fatal(diags.ErrWithWarnings())
}
// FINAL PLAN:
plan, diags := ctx.Plan()
if diags.HasErrors() {
t.Fatal(diags.ErrWithWarnings())
}
// make sure the same marks are compared in the next plan as well
for _, c := range plan.Changes.Resources {
if c.Action != plans.NoOp {
t.Logf("marks before: %#v", c.BeforeValMarks)
t.Logf("marks after: %#v", c.AfterValMarks)
t.Errorf("Unexpcetd %s change for %s", c.Action, c.Addr)
}
}
}

View File

@ -4794,7 +4794,7 @@ func TestContext2Plan_ignoreChangesSensitive(t *testing.T) {
checkVals(t, objectVal(t, schema, map[string]cty.Value{
"id": cty.StringVal("bar"),
"ami": cty.StringVal("ami-abcd1234").Mark(marks.Sensitive),
"ami": cty.StringVal("ami-abcd1234"),
"type": cty.StringVal("aws_instance"),
}), ric.After)
}

View File

@ -687,26 +687,24 @@ func (n *NodeAbstractResourceInstance) plan(
priorVal = cty.NullVal(schema.ImpliedType())
}
// Create an unmarked version of our config val and our prior val.
// Store the paths for the config val to re-markafter
// we've sent things over the wire.
unmarkedConfigVal, unmarkedPaths := origConfigVal.UnmarkDeepWithPaths()
unmarkedPriorVal, priorPaths := priorVal.UnmarkDeepWithPaths()
log.Printf("[TRACE] Re-validating config for %q", n.Addr)
// Allow the provider to validate the final set of values.
// The config was statically validated early on, but there may have been
// unknown values which the provider could not validate at the time.
// Allow the provider to validate the final set of values. The config was
// statically validated early on, but there may have been unknown values
// which the provider could not validate at the time.
//
// TODO: It would be more correct to validate the config after
// ignore_changes has been applied, but the current implementation cannot
// exclude computed-only attributes when given the `all` option.
// we must unmark and use the original config, since the ignore_changes
// handling below needs access to the marks.
unmarkedConfigVal, _ := origConfigVal.UnmarkDeep()
validateResp := provider.ValidateResourceConfig(
providers.ValidateResourceConfigRequest{
TypeName: n.Addr.Resource.Resource.Type,
Config: unmarkedConfigVal,
},
)
diags = diags.Append(validateResp.Diagnostics.InConfigBody(config.Config, n.Addr.String()))
if diags.HasErrors() {
return plan, state, diags
@ -717,13 +715,21 @@ func (n *NodeAbstractResourceInstance) plan(
// the proposed value, the proposed value itself, and the config presented
// to the provider in the PlanResourceChange request all agree on the
// starting values.
configValIgnored, ignoreChangeDiags := n.processIgnoreChanges(unmarkedPriorVal, unmarkedConfigVal)
// Here we operate on the marked values, so as to revert any changes to the
// marks as well as the value.
configValIgnored, ignoreChangeDiags := n.processIgnoreChanges(priorVal, origConfigVal)
diags = diags.Append(ignoreChangeDiags)
if ignoreChangeDiags.HasErrors() {
return plan, state, diags
}
proposedNewVal := objchange.ProposedNew(schema, unmarkedPriorVal, configValIgnored)
// Create an unmarked version of our config val and our prior val.
// Store the paths for the config val to re-mark after we've sent things
// over the wire.
unmarkedConfigVal, unmarkedPaths := configValIgnored.UnmarkDeepWithPaths()
unmarkedPriorVal, priorPaths := priorVal.UnmarkDeepWithPaths()
proposedNewVal := objchange.ProposedNew(schema, unmarkedPriorVal, unmarkedConfigVal)
// Call pre-diff hook
diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) {
@ -735,7 +741,7 @@ func (n *NodeAbstractResourceInstance) plan(
resp := provider.PlanResourceChange(providers.PlanResourceChangeRequest{
TypeName: n.Addr.Resource.Resource.Type,
Config: configValIgnored,
Config: unmarkedConfigVal,
PriorState: unmarkedPriorVal,
ProposedNewState: proposedNewVal,
PriorPrivate: priorPrivate,
@ -774,7 +780,7 @@ func (n *NodeAbstractResourceInstance) plan(
return plan, state, diags
}
if errs := objchange.AssertPlanValid(schema, unmarkedPriorVal, configValIgnored, plannedNewVal); len(errs) > 0 {
if errs := objchange.AssertPlanValid(schema, unmarkedPriorVal, unmarkedConfigVal, plannedNewVal); len(errs) > 0 {
if resp.LegacyTypeSystem {
// The shimming of the old type system in the legacy SDK is not precise
// enough to pass this consistency check, so we'll give it a pass here,
@ -1085,7 +1091,7 @@ func (n *NodeAbstractResource) processIgnoreChanges(prior, config cty.Value) (ct
return config, nil
}
ignoreChanges := n.Config.Managed.IgnoreChanges
ignoreChanges := traversalsToPaths(n.Config.Managed.IgnoreChanges)
ignoreAll := n.Config.Managed.IgnoreAllChanges
if len(ignoreChanges) == 0 && !ignoreAll {
@ -1100,14 +1106,16 @@ func (n *NodeAbstractResource) processIgnoreChanges(prior, config cty.Value) (ct
return config, nil
}
return processIgnoreChangesIndividual(prior, config, ignoreChanges)
ret, diags := processIgnoreChangesIndividual(prior, config, ignoreChanges)
return ret, diags
}
func processIgnoreChangesIndividual(prior, config cty.Value, ignoreChanges []hcl.Traversal) (cty.Value, tfdiags.Diagnostics) {
// When we walk below we will be using cty.Path values for comparison, so
// we'll convert our traversals here so we can compare more easily.
ignoreChangesPath := make([]cty.Path, len(ignoreChanges))
for i, traversal := range ignoreChanges {
// Convert the hcl.Traversal values we get form the configuration to the
// cty.Path values we need to operate on the cty.Values
func traversalsToPaths(traversals []hcl.Traversal) []cty.Path {
paths := make([]cty.Path, len(traversals))
for i, traversal := range traversals {
path := make(cty.Path, len(traversal))
for si, step := range traversal {
switch ts := step.(type) {
@ -1127,9 +1135,12 @@ func processIgnoreChangesIndividual(prior, config cty.Value, ignoreChanges []hcl
panic(fmt.Sprintf("unsupported traversal step %#v", step))
}
}
ignoreChangesPath[i] = path
paths[i] = path
}
return paths
}
func processIgnoreChangesIndividual(prior, config cty.Value, ignoreChangesPath []cty.Path) (cty.Value, tfdiags.Diagnostics) {
type ignoreChange struct {
// Path is the full path, minus any trailing map index
path cty.Path
@ -1175,8 +1186,7 @@ func processIgnoreChangesIndividual(prior, config cty.Value, ignoreChanges []hcl
// here even if our ignored key doesn't change. That is OK since it
// won't cause any changes in the transformation, but allows us to skip
// breaking up the maps and checking for key existence here too.
eq := p.Equals(c)
if !eq.IsKnown() || eq.False() {
if !p.RawEquals(c) {
// there a change to ignore at this path, store the prior value
ignoredValues = append(ignoredValues, ignoreChange{icPath, p, key})
}
@ -1205,6 +1215,10 @@ func processIgnoreChangesIndividual(prior, config cty.Value, ignoreChanges []hcl
return v, nil
}
// The map values will remain as cty values, so we only need to store
// the marks from the outer map itself
v, vMarks := v.Unmark()
// The configMap is the current configuration value, which we will
// mutate based on the ignored paths and the prior map value.
var configMap map[string]cty.Value
@ -1234,11 +1248,17 @@ func processIgnoreChangesIndividual(prior, config cty.Value, ignoreChanges []hcl
// return null for a key with a null value and for a non-existent
// key.
var priorMap map[string]cty.Value
// We need to drop the marks from the ignored map for handling. We
// don't need to store these, as we now know the ignored value is
// only within the map, not the map itself.
ignoredVal, _ := ignored.value.Unmark()
switch {
case ignored.value.IsNull() || ignored.value.LengthInt() == 0:
case ignored.value.IsNull() || ignoredVal.LengthInt() == 0:
priorMap = map[string]cty.Value{}
default:
priorMap = ignored.value.AsValueMap()
priorMap = ignoredVal.AsValueMap()
}
key := ignored.key.AsString()
@ -1254,11 +1274,18 @@ func processIgnoreChangesIndividual(prior, config cty.Value, ignoreChanges []hcl
}
}
var newVal cty.Value
if len(configMap) == 0 {
return cty.MapValEmpty(v.Type().ElementType()), nil
newVal = cty.MapValEmpty(v.Type().ElementType())
} else {
newVal = cty.MapVal(configMap)
}
return cty.MapVal(configMap), nil
if len(vMarks) > 0 {
newVal = v.WithMarks(vMarks)
}
return newVal, nil
})
return ret, nil
}

View File

@ -382,7 +382,7 @@ func TestProcessIgnoreChangesIndividual(t *testing.T) {
ignore[i] = trav
}
ret, diags := processIgnoreChangesIndividual(test.Old, test.New, ignore)
ret, diags := processIgnoreChangesIndividual(test.Old, test.New, traversalsToPaths(ignore))
if diags.HasErrors() {
t.Fatal(diags.Err())
}

View File

@ -33,6 +33,10 @@ The command-line flags are all optional. The list of available flags are:
* `-state=path` - Path to the state file. Defaults to "terraform.tfstate".
Ignored when [remote state](/docs/language/state/remote.html) is used.
-> **Note:** When using the `-json` or `-raw` command-line flag, any sensitive
values in Terraform state will be displayed in plain text. For more information,
see [Sensitive Data in State](/docs/language/state/sensitive-data.html).
## Examples
These examples assume the following Terraform output snippet.

View File

@ -71,7 +71,7 @@ stream. The top-level JSON object will have the following properties:
`false` if it detected any errors.
* `error_count` (number): A zero or positive whole number giving the count
of errors Terraform detected. If `valid` is `false` then `error_count` will
of errors Terraform detected. If `valid` is `true` then `error_count` will
always be zero, because it is the presence of errors that indicates that
a configuration is invalid.

View File

@ -358,6 +358,10 @@ Terraform will never itself delete a plugin from the plugin cache once it has
been placed there. Over time, as plugins are upgraded, the cache directory may
grow to contain several unused versions which you must delete manually.
-> **Note:** The plugin cache directory is not guaranteed to be concurrency
safe. The provider installer's behavior in environments with multiple `terraform
init` calls is undefined.
### Development Overrides for Provider Developers
-> **Note:** Development overrides work only in Terraform v0.14 and later.

View File

@ -62,6 +62,7 @@ export TF_VAR_amap='{ foo = "bar", baz = "qux" }'
For more on how to use `TF_VAR_name` in context, check out the section on [Variable Configuration](/docs/language/values/variables.html).
## TF_CLI_ARGS and TF_CLI_ARGS_name
<a id="tf-cli-args"></a>
The value of `TF_CLI_ARGS` will specify additional arguments to the
command-line. This allows easier automation in CI environments as well as

View File

@ -21,7 +21,7 @@ might prefer to [install Terraform from our Yum repositories](yum.html).
-> **Note:** The APT repositories discussed on this page are generic HashiCorp
repositories that contain packages for a variety of different HashiCorp
products, rather than just Terraform. Adding these repositories to your
system will, by default, therefore make a number of other non-Terraform
system will, by default, therefore make several other non-Terraform
packages available for installation. That might then mask some packages that
are available for some HashiCorp products in the main Debian and Ubuntu
package repositories.
@ -51,8 +51,8 @@ The above command line uses the following sub-shell commands:
system, such as `buster`, `groovy`, or `sid`.
`apt-add-repository` usually automatically runs `apt update` as part of its
work in order to fetch the new package indices, but if it does not then you
will need to so manually before the packages will be available.
work to fetch the new package indices, but if it does not, you will need to
manually do so before the packages will be available.
To install Terraform from the new repository:
@ -83,7 +83,7 @@ following distribution releases:
* Ubuntu 20.04 (`focal`)
* Ubuntu 20.10 (`groovy`)
No repositories are available for other Debian or Ubuntu versions or for
No repositories are available for other Debian or Ubuntu versions or
any other APT-based Linux distributions. If you add the repository using
the above commands on other systems then `apt update` will report the
repository index as missing.
@ -116,7 +116,7 @@ apt policy terraform
There may be multiple package releases for a particular Terraform version if
we need to publish an updated package for any reason. In that case, the
subsequent releases will have an additional suffix, like `0.13.4-2`. In these
cases the Terraform executable inside the package should be unchanged, but its
cases, the Terraform executable inside the package should be unchanged, but its
metadata and other contents may be different.
You can select a specific version to install by including it in the

View File

@ -451,6 +451,8 @@ Each unevaluated expression in the configuration is represented with an `<expres
}
```
-> **Note:** Expressions in `dynamic` blocks are not included in the configuration representation.
### Block Expressions Representation
In some cases, it is the entire content of a block (possibly after certain special arguments have already been handled and removed) that must be represented. For that, we have an `<block-expressions-representation>` structure:

View File

@ -2,13 +2,12 @@
layout: "language"
page_title: "Data Sources - Configuration Language"
sidebar_current: "docs-config-data-sources"
description: |-
Data sources allow data to be fetched or computed for use elsewhere in Terraform configuration.
description: "Data sources allow Terraform to use external data, function output, and data from other configurations. Learn data resource arguments, behavior, and lifecycle."
---
# Data Sources
_Data sources_ allow Terraform use information defined outside of Terraform,
_Data sources_ allow Terraform to use information defined outside of Terraform,
defined by another separate Terraform configuration, or modified by functions.
> **Hands-on:** Try the [Query Data Sources](https://learn.hashicorp.com/tutorials/terraform/data-sources) tutorial on HashiCorp Learn.

View File

@ -1,6 +1,7 @@
---
layout: "language"
page_title: "Dependency Lock File (.terraform.lock.hcl) - Configuration Language"
description: "Terraform uses the dependency lock file `.teraform.lock.hcl` to track and select provider versions. Learn about dependency installation and lock file changes."
---
# Dependency Lock File

View File

@ -1,6 +1,7 @@
---
layout: "language"
page_title: "Conditional Expressions - Configuration Language"
description: "Conditional expressions select one of two values. You can use them to define defaults to replace invalid values."
---
# Conditional Expressions

View File

@ -1,6 +1,7 @@
---
layout: "language"
page_title: "Dynamic Blocks - Configuration Language"
description: "`dynamic` blocks automatically construct multi-level, nested block structures. Learn to configure `dynamic` blocks and understand their behavior."
---

View File

@ -1,6 +1,7 @@
---
layout: "language"
page_title: "For Expressions - Configuration Language"
description: "`for` expressions transform complex input values into complex output values. Learn how to filter inputs and how to group results."
---
# `for` Expressions

View File

@ -1,6 +1,7 @@
---
layout: "language"
page_title: "Function Calls - Configuration Language"
description: "Functions transform and combine values. Learn about Terraform's built-in functions."
---
# Function Calls

View File

@ -1,6 +1,7 @@
---
layout: "language"
page_title: "Expressions - Configuration Language"
description: "An overview of expressions to reference or compute values in Terraform configurations, including types, operators, and functions."
---
# Expressions

View File

@ -1,6 +1,7 @@
---
layout: "language"
page_title: "Operators - Configuration Language"
description: "Operators transform or combine expressions. Learn about arithmetic, logical, equality, and comparison operators."
---
# Arithmetic and Logical Operators

View File

@ -1,6 +1,7 @@
---
layout: "language"
page_title: "References to Values - Configuration Language"
description: "Reference values in configurations, including resources, input variables, local and block-local values, module outputs, data sources, and workspace data."
---
# References to Named Values
@ -151,7 +152,7 @@ or some other value if not:
module "example" {
# ...
name_prefix = "app-${terraform-workspace}"
name_prefix = "app-${terraform.workspace}"
}
```

View File

@ -1,6 +1,7 @@
---
layout: "language"
page_title: "Splat Expressions - Configuration Language"
description: "Splat expressions concisely represent common operations. In Terraform, they also transform single, non-null values into a single-element tuple."
---
# Splat Expressions
@ -61,9 +62,7 @@ tuple value. If the value is _null_ then the splat expression will return an
empty tuple.
This special behavior can be useful for modules that accept optional input
variables whose default value is `null` to represent the absense of any value,
to adapt the variable value to work with other Terraform language features that
are designed to work with collections. For example:
variables whose default value is `null` to represent the absence of any value. This allows the module to adapt the variable value for Terraform language features designed to work with collections. For example:
```
variable "website" {

View File

@ -1,6 +1,8 @@
---
layout: "language"
page_title: "Strings and Templates - Configuration Language"
description: "String literals and template sequences interpolate values and manipulate text. Learn about both quoted and heredoc string syntax."
---
# Strings and Templates

View File

@ -2,9 +2,7 @@
layout: "language"
page_title: "Type Constraints - Configuration Language"
sidebar_current: "docs-config-types"
description: |-
Terraform module authors and provider developers can use detailed type
constraints to validate the inputs of their modules and resources.
description: "Learn how to use type constraints to validate user inputs to modules and resources."
---
# Type Constraints

View File

@ -1,6 +1,7 @@
---
layout: "language"
page_title: "Types and Values - Configuration Language"
description: "Learn about value types and syntax, including string, number, bool, list, and map. Also learn about complex types and type conversion."
---
# Types and Values

View File

@ -1,6 +1,7 @@
---
layout: "language"
page_title: "Version Constraints - Configuration Language"
description: "Version constraint strings specify a range of acceptable versions for modules, providers, and Terraform itself. Learn version constraint syntax and behavior."
---
# Version Constraints

View File

@ -1,6 +1,7 @@
---
layout: "language"
page_title: "Files and Directories - Configuration Language"
description: "Learn how to name, organize, and store Terraform configuration files. Also learn how Terraform evaluates modules."
---
# Files and Directories

View File

@ -2,9 +2,7 @@
layout: "language"
page_title: "Override Files - Configuration Language"
sidebar_current: "docs-config-override"
description: |-
Override files allow additional settings to be merged into existing
configuration objects.
description: "Override files merge additional settings into existing configuration objects. Learn how to use override files and about merging behavior."
---
# Override Files

View File

@ -26,6 +26,10 @@ Supported pattern matches:
- `[CLASS]` - matches any single non-separator character inside a class of characters (see below)
- `[^CLASS]` - matches any single non-separator character outside a class of characters (see below)
Note that the doublestar (`**`) must appear as a path component by itself. A
pattern such as /path** is invalid and will be treated the same as /path*, but
/path*/** should achieve the desired result.
Character classes support the following:
- `[abc]` - matches any single character within the set

View File

@ -2,9 +2,7 @@
layout: "language"
page_title: "Functions - Configuration Language"
sidebar_current: "docs-config-functions"
description: |-
The Terraform language has a number of built-in functions that can be called
from within expressions to transform and combine values.
description: "An introduction to the built-in functions that you can use to transform and combine values in expressions."
---
# Built-in Functions

View File

@ -1,6 +1,8 @@
---
layout: "language"
page_title: "Overview - Configuration Language"
description: "You can use the Terraform language to write configuration files that tell Terraform how to manage a collection of infrastructure."
---
# Terraform Language Documentation

View File

@ -1,6 +1,7 @@
---
layout: "language"
page_title: "The count Meta-Argument - Configuration Language"
description: "`count` helps you efficiently manage nearly identical infrastructure resources without writing a separate block for each one."
---
# The `count` Meta-Argument

View File

@ -1,6 +1,7 @@
---
layout: "language"
page_title: "The depends_on Meta-Argument - Configuration Language"
description: "`depends_on` allows you to handle hidden resource or module dependencies."
---
# The `depends_on` Meta-Argument

View File

@ -1,6 +1,7 @@
---
layout: "language"
page_title: "The for_each Meta-Argument - Configuration Language"
description: "`for_each` allows you to efficiently manage similar infrastructure resources without writing a separate block for each one."
---
# The `for_each` Meta-Argument

View File

@ -1,6 +1,7 @@
---
layout: "language"
page_title: "The lifecycle Meta-Argument - Configuration Language"
description: "The meta-arguments in a `lifecycle` block allow you to customize resource behavior. For example, preventing Terraform from destroying associated infrastructure."
---
# The `lifecycle` Meta-Argument

View File

@ -1,6 +1,7 @@
---
layout: "language"
page_title: "The Module providers Meta-Argument - Configuration Language"
description: "`providers` specifies which provider configurations from a parent module are available in a child module."
---
# The Module `providers` Meta-Argument

View File

@ -1,6 +1,7 @@
---
layout: "language"
page_title: "The Resource provider Meta-Argument - Configuration Language"
description: "`provider` specifies the provider configuration Terraform should use for a resource, overriding Terraform's default behavior."
---
# The Resource `provider` Meta-Argument

View File

@ -2,8 +2,7 @@
layout: "language"
page_title: "Creating Modules"
sidebar_current: "docs-modules"
description: |-
A module is a container for multiple resources that are used together.
description: "Modules are containers for multiple resources that are used together in a configuration. Learn when to create modules and about module structure."
---
# Creating Modules

View File

@ -1,6 +1,7 @@
---
layout: "language"
page_title: "Modules Overview - Configuration Language"
description: "Modules are containers for multiple resources that are used together in a configuration. Find resources for using, developing, and publishing modules."
---
# Modules

View File

@ -2,7 +2,7 @@
layout: "language"
page_title: "Module Sources"
sidebar_current: "docs-modules-sources"
description: The source argument within a module block specifies the location of the source code of a child module.
description: "`source` tells Terraform where to find child modules in locations like GitHub, the Terraform Registry, Bitbucket, Git, Mercurial, S3, and GCS."
---
# Module Sources

View File

@ -2,8 +2,7 @@
layout: "language"
page_title: "Modules - Configuration Language"
sidebar_current: "docs-config-modules"
description: |-
Modules allow multiple resources to be grouped together and encapsulated.
description: "Modules are containers for multiple resources that are used together in configurations. Learn how to call one module from another and access module output."
---
# Module Blocks

View File

@ -2,14 +2,12 @@
layout: "language"
page_title: "Provider Configuration - Configuration Language"
sidebar_current: "docs-config-providers"
description: |-
Learn how to configure provider settings and alias providers to use multiple
different provider configurations in the same Terraform project.
description: "Learn how to set up providers, including how to use the `alias` meta-argument to specify multiple configurations for a single provider."
---
# Provider Configuration
Providers alow Terraform to interact with cloud providers, SaaS providers, and
Providers allow Terraform to interact with cloud providers, SaaS providers, and
other APIs.
Some providers require you to configure them with endpoint URLs, cloud regions,

View File

@ -1,10 +1,7 @@
---
layout: "language"
page_title: "Providers - Configuration Language"
description: |-
Terraform providers are plugins that allow Terraform to create resources and
use data sources from services, cloud providers, and other APIs. Read about
how to discover, install, and use providers.
description: "An overview of how to install and use providers, Terraform plugins that interact with services, cloud providers, and other APIs."
---
# Providers
@ -139,6 +136,6 @@ develops and maintains a given provider.
Providers are written in Go, using the Terraform Plugin SDK. For more
information on developing providers, see:
- The [Extending Terraform](/docs/extend/index.html) documentation
- The [Plugin Development](/docs/extend/index.html) documentation
- The [Call APIs with Terraform Providers](https://learn.hashicorp.com/collections/terraform/providers?utm_source=WEBSITE&utm_medium=WEB_IO&utm_offer=ARTICLE_PAGE&utm_content=DOCS)
collection on HashiCorp Learn

View File

@ -1,6 +1,7 @@
---
layout: "language"
page_title: "Provider Requirements - Configuration Language"
description: "Providers are plugins that allow Terraform to interact with services, cloud providers, and other APIs. Learn how to declare providers in a configuration."
---
# Provider Requirements

Some files were not shown because too many files have changed in this diff Show More