Merge remote-tracking branch 'origin/main' into update-consul
This commit is contained in:
commit
da6717761e
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
28
go.mod
|
@ -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
106
go.sum
|
@ -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=
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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) + ")"
|
||||
}
|
||||
}
|
|
@ -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() {}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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() {}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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" {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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))}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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`,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
@ -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"}),
|
||||
}))),
|
||||
}),
|
||||
},
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."
|
||||
---
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}"
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -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" {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue