diff --git a/backend/remote-state/artifactory/backend.go b/backend/remote-state/artifactory/backend.go index 2062968af..8f504a610 100644 --- a/backend/remote-state/artifactory/backend.go +++ b/backend/remote-state/artifactory/backend.go @@ -5,7 +5,7 @@ import ( cleanhttp "github.com/hashicorp/go-cleanhttp" "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" "github.com/hashicorp/terraform/states/remote" "github.com/hashicorp/terraform/states/statemgr" artifactory "github.com/lusis/go-artifactory/src/artifactory.v401" diff --git a/backend/remote-state/azure/backend.go b/backend/remote-state/azure/backend.go index 00995d97a..7e899ad9d 100644 --- a/backend/remote-state/azure/backend.go +++ b/backend/remote-state/azure/backend.go @@ -5,7 +5,7 @@ import ( "fmt" "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) // New creates a new backend for Azure remote state. diff --git a/backend/remote-state/consul/backend.go b/backend/remote-state/consul/backend.go index 271a60b63..ebe62471b 100644 --- a/backend/remote-state/consul/backend.go +++ b/backend/remote-state/consul/backend.go @@ -8,7 +8,7 @@ import ( consulapi "github.com/hashicorp/consul/api" "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) // New creates a new backend for Consul remote state. diff --git a/backend/remote-state/cos/backend.go b/backend/remote-state/cos/backend.go index ce502e5cc..fa358aa10 100644 --- a/backend/remote-state/cos/backend.go +++ b/backend/remote-state/cos/backend.go @@ -9,7 +9,7 @@ import ( "time" "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" tag "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tag/v20180813" diff --git a/backend/remote-state/etcdv2/backend.go b/backend/remote-state/etcdv2/backend.go index 9f9fa0904..ee0f0bda5 100644 --- a/backend/remote-state/etcdv2/backend.go +++ b/backend/remote-state/etcdv2/backend.go @@ -8,7 +8,7 @@ import ( etcdapi "github.com/coreos/etcd/client" "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" "github.com/hashicorp/terraform/states/remote" "github.com/hashicorp/terraform/states/statemgr" ) diff --git a/backend/remote-state/etcdv3/backend.go b/backend/remote-state/etcdv3/backend.go index fb3f5e202..1bf5809bf 100644 --- a/backend/remote-state/etcdv3/backend.go +++ b/backend/remote-state/etcdv3/backend.go @@ -6,7 +6,7 @@ import ( etcdv3 "github.com/coreos/etcd/clientv3" "github.com/coreos/etcd/pkg/transport" "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) const ( diff --git a/backend/remote-state/gcs/backend.go b/backend/remote-state/gcs/backend.go index 9e943d5e3..8184dafbd 100644 --- a/backend/remote-state/gcs/backend.go +++ b/backend/remote-state/gcs/backend.go @@ -11,8 +11,8 @@ import ( "cloud.google.com/go/storage" "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/httpclient" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" "golang.org/x/oauth2" "golang.org/x/oauth2/jwt" "google.golang.org/api/option" diff --git a/backend/remote-state/http/backend.go b/backend/remote-state/http/backend.go index 12076e01a..dee59f48c 100644 --- a/backend/remote-state/http/backend.go +++ b/backend/remote-state/http/backend.go @@ -11,7 +11,7 @@ import ( "github.com/hashicorp/go-cleanhttp" "github.com/hashicorp/go-retryablehttp" "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" "github.com/hashicorp/terraform/states/remote" "github.com/hashicorp/terraform/states/statemgr" ) diff --git a/backend/remote-state/inmem/backend.go b/backend/remote-state/inmem/backend.go index 1a974a05b..035f3c973 100644 --- a/backend/remote-state/inmem/backend.go +++ b/backend/remote-state/inmem/backend.go @@ -9,7 +9,7 @@ import ( "time" "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" statespkg "github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/states/remote" "github.com/hashicorp/terraform/states/statemgr" diff --git a/backend/remote-state/kubernetes/backend.go b/backend/remote-state/kubernetes/backend.go index eed598f8e..12530b0bf 100644 --- a/backend/remote-state/kubernetes/backend.go +++ b/backend/remote-state/kubernetes/backend.go @@ -8,7 +8,7 @@ import ( "os" "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" "github.com/hashicorp/terraform/version" "github.com/mitchellh/cli" "github.com/mitchellh/go-homedir" diff --git a/backend/remote-state/kubernetes/log.txt b/backend/remote-state/kubernetes/log.txt deleted file mode 100644 index 38cae58d0..000000000 --- a/backend/remote-state/kubernetes/log.txt +++ /dev/null @@ -1,1995 +0,0 @@ -=== RUN TestBackendLocksSoak - TestBackendLocksSoak: backend_test.go:122: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:94: Error creating state manager: the state is already locked by another terraform client - Lock Info: - ID: 3e2169be-df88-a073-df20-d5d9863abd5d - Path: - Operation: init - Who: john@Johns-MacBook-Pro.local - Version: 0.13.0 - Created: 2020-06-05 16:54:18.347493 +0000 UTC - Info: - TestBackendLocksSoak: backend_test.go:94: Error creating state manager: the state is already locked by another terraform client - Lock Info: - ID: 3e2169be-df88-a073-df20-d5d9863abd5d - Path: - Operation: init - Who: john@Johns-MacBook-Pro.local - Version: 0.13.0 - Created: 2020-06-05 16:54:18.347493 +0000 UTC - Info: - TestBackendLocksSoak: backend_test.go:94: Error creating state manager: the state is already locked by another terraform client - Lock Info: - ID: 3e2169be-df88-a073-df20-d5d9863abd5d - Path: - Operation: init - Who: john@Johns-MacBook-Pro.local - Version: 0.13.0 - Created: 2020-06-05 16:54:18.347493 +0000 UTC - Info: - TestBackendLocksSoak: backend_test.go:94: Error creating state manager: the state is already locked by another terraform client - Lock Info: - ID: 3e2169be-df88-a073-df20-d5d9863abd5d - Path: - Operation: init - Who: john@Johns-MacBook-Pro.local - Version: 0.13.0 - Created: 2020-06-05 16:54:18.347493 +0000 UTC - Info: - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} - TestBackendLocksSoak: backend_test.go:88: TestBackendConfig on *kubernetes.Backend with configs.synthBody{Filename:"", Values:map[string]cty.Value{"secret_suffix":cty.StringVal("test-state")}} -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -2020/06/05 12:54:18 [INFO] Successfully loaded config file (/Users/john/.kube/config; default context) -panic: interface conversion: interface is nil, not statemgr.Locker - -goroutine 203 [running]: -github.com/hashicorp/terraform/backend/remote-state/kubernetes.TestBackendLocksSoak.func1(0xc000157200, 0xc00038cc00, 0xc00038cc10, 0x85) - /Users/john/dev/hashicorp/terraform/backend/remote-state/kubernetes/backend_test.go:103 +0x290 -created by github.com/hashicorp/terraform/backend/remote-state/kubernetes.TestBackendLocksSoak - /Users/john/dev/hashicorp/terraform/backend/remote-state/kubernetes/backend_test.go:87 +0xef -FAIL github.com/hashicorp/terraform/backend/remote-state/kubernetes 3.487s -FAIL diff --git a/backend/remote-state/manta/backend.go b/backend/remote-state/manta/backend.go index 9189ad890..c7e32403b 100644 --- a/backend/remote-state/manta/backend.go +++ b/backend/remote-state/manta/backend.go @@ -11,7 +11,7 @@ import ( "github.com/hashicorp/errwrap" "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" triton "github.com/joyent/triton-go" "github.com/joyent/triton-go/authentication" "github.com/joyent/triton-go/storage" diff --git a/backend/remote-state/oss/backend.go b/backend/remote-state/oss/backend.go index 9b1227634..f87c5f467 100644 --- a/backend/remote-state/oss/backend.go +++ b/backend/remote-state/oss/backend.go @@ -25,7 +25,7 @@ import ( "github.com/aliyun/aliyun-tablestore-go-sdk/tablestore" "github.com/hashicorp/go-cleanhttp" "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" "github.com/hashicorp/terraform/version" "github.com/jmespath/go-jmespath" "github.com/mitchellh/go-homedir" diff --git a/backend/remote-state/pg/backend.go b/backend/remote-state/pg/backend.go index 0191176a9..db1b01461 100644 --- a/backend/remote-state/pg/backend.go +++ b/backend/remote-state/pg/backend.go @@ -6,7 +6,7 @@ import ( "fmt" "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" "github.com/lib/pq" ) diff --git a/backend/remote-state/s3/backend.go b/backend/remote-state/s3/backend.go index b656e3953..525c28b81 100644 --- a/backend/remote-state/s3/backend.go +++ b/backend/remote-state/s3/backend.go @@ -12,7 +12,7 @@ import ( "github.com/aws/aws-sdk-go/service/s3" awsbase "github.com/hashicorp/aws-sdk-go-base" "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" "github.com/hashicorp/terraform/internal/logging" "github.com/hashicorp/terraform/version" ) diff --git a/backend/remote-state/swift/backend.go b/backend/remote-state/swift/backend.go index 3a22438f5..ca7571c71 100644 --- a/backend/remote-state/swift/backend.go +++ b/backend/remote-state/swift/backend.go @@ -12,7 +12,7 @@ import ( "github.com/gophercloud/utils/terraform/auth" "github.com/hashicorp/terraform/backend" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" "github.com/hashicorp/terraform/version" ) diff --git a/builtin/providers/test/data_source_label_test.go b/builtin/providers/test/data_source_label_test.go deleted file mode 100644 index d98a27b06..000000000 --- a/builtin/providers/test/data_source_label_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package test - -import ( - "errors" - "fmt" - "strings" - "testing" - - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" -) - -func TestProviderLabelDataSource(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: func(s *terraform.State) error { - return nil - }, - Steps: []resource.TestStep{ - { - Config: strings.TrimSpace(` -provider "test" { - label = "foo" -} - -data "test_provider_label" "test" { -} - `), - Check: func(s *terraform.State) error { - res, hasRes := s.RootModule().Resources["data.test_provider_label.test"] - if !hasRes { - return errors.New("No test_provider_label in state") - } - if got, want := res.Primary.ID, "foo"; got != want { - return fmt.Errorf("wrong id %q; want %q", got, want) - } - if got, want := res.Primary.Attributes["label"], "foo"; got != want { - return fmt.Errorf("wrong id %q; want %q", got, want) - } - return nil - }, - }, - }, - }) -} diff --git a/builtin/providers/test/data_source_test.go b/builtin/providers/test/data_source_test.go deleted file mode 100644 index c0a1ae57c..000000000 --- a/builtin/providers/test/data_source_test.go +++ /dev/null @@ -1,291 +0,0 @@ -package test - -import ( - "errors" - "fmt" - "regexp" - "strings" - "testing" - - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" -) - -func TestDataSource_dataSourceCount(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: func(s *terraform.State) error { - return nil - }, - Steps: []resource.TestStep{ - { - Config: strings.TrimSpace(` -data "test_data_source" "test" { - count = 3 - input = "count-${count.index}" -} - -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - - list = "${data.test_data_source.test.*.output}" -} - `), - Check: func(s *terraform.State) error { - res, hasRes := s.RootModule().Resources["test_resource.foo"] - if !hasRes { - return errors.New("No test_resource.foo in state") - } - if res.Primary.Attributes["list.#"] != "3" { - return errors.New("Wrong list.#, expected 3") - } - if res.Primary.Attributes["list.0"] != "count-0" { - return errors.New("Wrong list.0, expected count-0") - } - if res.Primary.Attributes["list.1"] != "count-1" { - return errors.New("Wrong list.0, expected count-1") - } - if res.Primary.Attributes["list.2"] != "count-2" { - return errors.New("Wrong list.0, expected count-2") - } - return nil - }, - }, - }, - }) -} - -// Test that the output of a data source can be used as the value for -// a "count" in a real resource. This would fail with "count cannot be computed" -// at some point. -func TestDataSource_valueAsResourceCount(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: func(s *terraform.State) error { - return nil - }, - Steps: []resource.TestStep{ - { - Config: strings.TrimSpace(` -data "test_data_source" "test" { - input = "4" -} - -resource "test_resource" "foo" { - count = "${data.test_data_source.test.output}" - - required = "yep" - required_map = { - key = "value" - } -} - `), - Check: func(s *terraform.State) error { - count := 0 - for k, _ := range s.RootModule().Resources { - if strings.HasPrefix(k, "test_resource.foo.") { - count++ - } - } - - if count != 4 { - return fmt.Errorf("bad count: %d", count) - } - return nil - }, - }, - }, - }) -} - -// TestDataSource_dataSourceCountGrandChild tests that a grandchild data source -// that is based off of count works, ie: dependency chain foo -> bar -> baz. -// This was failing because CountBoundaryTransformer is being run during apply -// instead of plan, which meant that it wasn't firing after data sources were -// potentially changing state and causing diff/interpolation issues. -// -// This happens after the initial apply, after state is saved. -func TestDataSource_dataSourceCountGrandChild(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: func(s *terraform.State) error { - return nil - }, - Steps: []resource.TestStep{ - { - Config: dataSourceCountGrandChildConfig, - }, - { - Config: dataSourceCountGrandChildConfig, - Check: func(s *terraform.State) error { - for _, v := range []string{"foo", "bar", "baz"} { - count := 0 - for k := range s.RootModule().Resources { - if strings.HasPrefix(k, fmt.Sprintf("data.test_data_source.%s.", v)) { - count++ - } - } - - if count != 2 { - return fmt.Errorf("bad count for data.test_data_source.%s: %d", v, count) - } - } - return nil - }, - }, - }, - }) -} - -const dataSourceCountGrandChildConfig = ` -data "test_data_source" "foo" { - count = 2 - input = "one" -} - -data "test_data_source" "bar" { - count = "${length(data.test_data_source.foo.*.id)}" - input = "${data.test_data_source.foo.*.output[count.index]}" -} - -data "test_data_source" "baz" { - count = "${length(data.test_data_source.bar.*.id)}" - input = "${data.test_data_source.bar.*.output[count.index]}" -} -` - -func TestDataSource_nilComputedValues(t *testing.T) { - check := func(s *terraform.State) error { - return nil - } - - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Check: check, - Config: ` -variable "index" { - default = "d" -} - -locals { - name = { - a = "something" - b = "else" - } -} - -data "test_data_source" "x" { - input = "${lookup(local.name, var.index, local.name["a"])}" -} - -data "test_data_source" "y" { - input = data.test_data_source.x.nil == "something" ? "something" : "else" -}`, - }, - }, - }) -} - -// referencing test_data_source.one.output_map["a"] should produce an error when -// there's a count. -func TestDataSource_indexedCountOfOne(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: strings.TrimSpace(` -data "test_data_source" "one" { - count = 1 - input_map = { - "a" = "b" - } -} - -data "test_data_source" "two" { - input_map = { - "x" = data.test_data_source.one.output_map["a"] - } -} - `), - ExpectError: regexp.MustCompile("Because data.test_data_source.one has \"count\" set, its attributes must be accessed on specific instances"), - }, - }, - }) -} - -// Verify that we can destroy when a data source references something with a -// count of 1. -func TestDataSource_countRefDestroyError(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: strings.TrimSpace(` -data "test_data_source" "one" { - count = 1 - input = "a" -} - -data "test_data_source" "two" { - input = data.test_data_source.one[0].output -} - `), - }, - }, - }) -} - -func TestDataSource_planUpdate(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: strings.TrimSpace(` -resource "test_resource" "a" { - required = "first" - required_map = { - key = "1" - } - optional_force_new = "first" -} - -data "test_data_source" "a" { - input = "${test_resource.a.computed_from_required}" -} - -output "out" { - value = "${data.test_data_source.a.output}" -} - `), - }, - { - Config: strings.TrimSpace(` -resource "test_resource" "a" { - required = "second" - required_map = { - key = "1" - } - optional_force_new = "second" -} - -data "test_data_source" "a" { - input = "${test_resource.a.computed_from_required}" -} - -output "out" { - value = "${data.test_data_source.a.output}" -} - `), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("data.test_data_source.a", "output", "second"), - resource.TestCheckOutput("out", "second"), - ), - }, - }, - }) -} diff --git a/builtin/providers/test/diff_apply_test.go b/builtin/providers/test/diff_apply_test.go deleted file mode 100644 index b28e110e0..000000000 --- a/builtin/providers/test/diff_apply_test.go +++ /dev/null @@ -1,144 +0,0 @@ -package test - -import ( - "reflect" - "testing" - - "github.com/davecgh/go-spew/spew" - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/terraform" -) - -func TestDiffApply_set(t *testing.T) { - priorAttrs := map[string]string{ - "id": "testID", - "egress.#": "1", - "egress.2129912301.cidr_blocks.#": "1", - "egress.2129912301.cidr_blocks.0": "10.0.0.0/8", - "egress.2129912301.description": "Egress description", - "egress.2129912301.from_port": "80", - "egress.2129912301.ipv6_cidr_blocks.#": "0", - "egress.2129912301.prefix_list_ids.#": "0", - "egress.2129912301.protocol": "tcp", - "egress.2129912301.security_groups.#": "0", - "egress.2129912301.self": "false", - "egress.2129912301.to_port": "8000", - } - - diff := &terraform.InstanceDiff{ - Attributes: map[string]*terraform.ResourceAttrDiff{ - "egress.2129912301.cidr_blocks.#": {Old: "1", New: "0", NewComputed: false, NewRemoved: false}, - "egress.2129912301.cidr_blocks.0": {Old: "10.0.0.0/8", New: "", NewComputed: false, NewRemoved: true}, - "egress.2129912301.description": {Old: "Egress description", New: "", NewComputed: false, NewRemoved: true}, - "egress.2129912301.from_port": {Old: "80", New: "0", NewComputed: false, NewRemoved: true}, - "egress.2129912301.ipv6_cidr_blocks.#": {Old: "0", New: "0", NewComputed: false, NewRemoved: false}, - "egress.2129912301.prefix_list_ids.#": {Old: "0", New: "0", NewComputed: false, NewRemoved: false}, - "egress.2129912301.protocol": {Old: "tcp", New: "", NewComputed: false, NewRemoved: true}, - "egress.2129912301.security_groups.#": {Old: "0", New: "0", NewComputed: false, NewRemoved: false}, - "egress.2129912301.self": {Old: "false", New: "false", NewComputed: false, NewRemoved: true}, - "egress.2129912301.to_port": {Old: "8000", New: "0", NewComputed: false, NewRemoved: true}, - "egress.746197026.cidr_blocks.#": {Old: "", New: "1", NewComputed: false, NewRemoved: false}, - "egress.746197026.cidr_blocks.0": {Old: "", New: "10.0.0.0/8", NewComputed: false, NewRemoved: false}, - "egress.746197026.description": {Old: "", New: "New egress description", NewComputed: false, NewRemoved: false}, - "egress.746197026.from_port": {Old: "", New: "80", NewComputed: false, NewRemoved: false}, - "egress.746197026.ipv6_cidr_blocks.#": {Old: "", New: "0", NewComputed: false, NewRemoved: false}, - "egress.746197026.prefix_list_ids.#": {Old: "", New: "0", NewComputed: false, NewRemoved: false}, - "egress.746197026.protocol": {Old: "", New: "tcp", NewComputed: false, NewRemoved: false, NewExtra: "tcp"}, - "egress.746197026.security_groups.#": {Old: "", New: "0", NewComputed: false, NewRemoved: false}, - "egress.746197026.self": {Old: "", New: "false", NewComputed: false, NewRemoved: false}, - "egress.746197026.to_port": {Old: "", New: "8000", NewComputed: false, NewRemoved: false}, - // an erroneous nil diff should do nothing - "egress.111111111.to_port": nil, - }, - } - - resSchema := map[string]*schema.Schema{ - "egress": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - ConfigMode: schema.SchemaConfigModeAttr, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "from_port": { - Type: schema.TypeInt, - Required: true, - }, - - "to_port": { - Type: schema.TypeInt, - Required: true, - }, - - "protocol": { - Type: schema.TypeString, - Required: true, - }, - - "cidr_blocks": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - - "ipv6_cidr_blocks": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - - "prefix_list_ids": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - - "security_groups": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, - }, - - "self": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - - "description": { - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, - } - - expected := map[string]string{ - "egress.#": "1", - "egress.746197026.cidr_blocks.#": "1", - "egress.746197026.cidr_blocks.0": "10.0.0.0/8", - "egress.746197026.description": "New egress description", - "egress.746197026.from_port": "80", "egress.746197026.ipv6_cidr_blocks.#": "0", - "egress.746197026.prefix_list_ids.#": "0", - "egress.746197026.protocol": "tcp", - "egress.746197026.security_groups.#": "0", - "egress.746197026.self": "false", - "egress.746197026.to_port": "8000", - "id": "testID", - } - - attrs, err := diff.Apply(priorAttrs, (&schema.Resource{Schema: resSchema}).CoreConfigSchema()) - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(attrs, expected) { - t.Fatalf("wrong result\ngot: %s\nwant: %s\n", spew.Sdump(attrs), spew.Sdump(expected)) - } -} diff --git a/builtin/providers/test/provider_test.go b/builtin/providers/test/provider_test.go deleted file mode 100644 index 40defefac..000000000 --- a/builtin/providers/test/provider_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package test - -import ( - "testing" - - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/terraform" -) - -var testAccProviders map[string]terraform.ResourceProvider -var testAccProvider *schema.Provider - -func TestProvider(t *testing.T) { - if err := Provider().(*schema.Provider).InternalValidate(); err != nil { - t.Fatalf("err: %s", err) - } -} - -func init() { - testAccProvider = Provider().(*schema.Provider) - testAccProviders = map[string]terraform.ResourceProvider{ - "test": testAccProvider, - } -} diff --git a/builtin/providers/test/resource_computed_set_test.go b/builtin/providers/test/resource_computed_set_test.go deleted file mode 100644 index 06e608235..000000000 --- a/builtin/providers/test/resource_computed_set_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package test - -import ( - "strings" - "testing" - - "github.com/hashicorp/terraform/helper/resource" -) - -func TestResourceComputedSet_update(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_computed_set" "foo" { -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_computed_set.foo", "string_set.#", "3", - ), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_computed_set" "foo" { - set_count = 5 -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_computed_set.foo", "string_set.#", "5", - ), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_computed_set" "foo" { - set_count = 2 -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_computed_set.foo", "string_set.#", "2", - ), - ), - }, - }, - }) -} - -func TestResourceComputedSet_ruleTest(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_computed_set" "foo" { - rule { - ip_protocol = "udp" - cidr = "0.0.0.0/0" - } -} - `), - }, - }, - }) -} diff --git a/builtin/providers/test/resource_config_mode_test.go b/builtin/providers/test/resource_config_mode_test.go deleted file mode 100644 index f73adc8ff..000000000 --- a/builtin/providers/test/resource_config_mode_test.go +++ /dev/null @@ -1,120 +0,0 @@ -package test - -import ( - "strings" - "testing" - - "github.com/hashicorp/terraform/helper/resource" -) - -func TestResourceConfigMode(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_config_mode" "foo" { - resource_as_attr = [ - { - foo = "resource_as_attr 0" - }, - { - foo = "resource_as_attr 1" - }, - ] -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr.#", "2"), - resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr.0.foo", "resource_as_attr 0"), - resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr.1.foo", "resource_as_attr 1"), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_config_mode" "foo" { - # Due to a preprocessing fixup we do in lang.EvalBlock, it's allowed - # to specify resource_as_attr members using one or more nested blocks - # instead of attribute syntax, if desired. This should be equivalent - # to the previous config. - # - # This allowance is made for backward-compatibility with existing providers - # before Terraform v0.12 that were expecting nested block types to also - # support attribute syntax; it should not be used for any new use-cases. - resource_as_attr { - foo = "resource_as_attr 0" - } - resource_as_attr { - foo = "resource_as_attr 1" - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr.#", "2"), - resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr.0.foo", "resource_as_attr 0"), - resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr.1.foo", "resource_as_attr 1"), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_config_mode" "foo" { - resource_as_attr = [ - { - foo = "resource_as_attr 0 updated" - }, - ] -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr.#", "1"), - resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr.0.foo", "resource_as_attr 0 updated"), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_config_mode" "foo" { - resource_as_attr = [] -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource_config_mode.foo", "resource_as_attr.#", "0"), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_config_mode" "foo" { -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckNoResourceAttr("test_resource_config_mode.foo", "resource_as_attr.#"), - ), - }, - }, - }) -} - -func TestResourceConfigMode_nestedSet(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_config_mode" "foo" { - resource_as_attr = [] - - nested_set { - value = "a" - } - nested_set { - value = "b" - set = [] - } -} - `), - Check: resource.ComposeTestCheckFunc(), - }, - }, - }) -} diff --git a/builtin/providers/test/resource_data_dep_test.go b/builtin/providers/test/resource_data_dep_test.go deleted file mode 100644 index 0cd773d53..000000000 --- a/builtin/providers/test/resource_data_dep_test.go +++ /dev/null @@ -1,224 +0,0 @@ -package test - -import ( - "fmt" - "testing" - - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" -) - -// TestResourceDataDep_alignedCountScaleOut tests to make sure interpolation -// works (namely without index errors) when a data source and a resource share -// the same count variable during scale-out with an existing state. -func TestResourceDataDep_alignedCountScaleOut(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: func(s *terraform.State) error { - return nil - }, - Steps: []resource.TestStep{ - { - Config: testResourceDataDepConfig(2), - }, - { - Config: testResourceDataDepConfig(4), - Check: resource.TestCheckOutput("out", "value_from_api,value_from_api,value_from_api,value_from_api"), - }, - }, - }) -} - -// TestResourceDataDep_alignedCountScaleIn tests to make sure interpolation -// works (namely without index errors) when a data source and a resource share -// the same count variable during scale-in with an existing state. -func TestResourceDataDep_alignedCountScaleIn(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: func(s *terraform.State) error { - return nil - }, - Steps: []resource.TestStep{ - { - Config: testResourceDataDepConfig(4), - }, - { - Config: testResourceDataDepConfig(2), - Check: resource.TestCheckOutput("out", "value_from_api,value_from_api"), - }, - }, - }) -} - -// TestDataResourceDep_alignedCountScaleOut functions like -// TestResourceDataDep_alignedCountScaleOut, but with the dependencies swapped -// (resource now depends on data source, a pretty regular use case, but -// included here to check for regressions). -func TestDataResourceDep_alignedCountScaleOut(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: func(s *terraform.State) error { - return nil - }, - Steps: []resource.TestStep{ - { - Config: testDataResourceDepConfig(2), - }, - { - Config: testDataResourceDepConfig(4), - Check: resource.TestCheckOutput("out", "test,test,test,test"), - }, - }, - }) -} - -// TestDataResourceDep_alignedCountScaleIn functions like -// TestResourceDataDep_alignedCountScaleIn, but with the dependencies swapped -// (resource now depends on data source, a pretty regular use case, but -// included here to check for regressions). -func TestDataResourceDep_alignedCountScaleIn(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: func(s *terraform.State) error { - return nil - }, - Steps: []resource.TestStep{ - { - Config: testDataResourceDepConfig(4), - }, - { - Config: testDataResourceDepConfig(2), - Check: resource.TestCheckOutput("out", "test,test"), - }, - }, - }) -} - -// TestResourceResourceDep_alignedCountScaleOut functions like -// TestResourceDataDep_alignedCountScaleOut, but with a resource-to-resource -// dependency instead, a pretty regular use case, but included here to check -// for regressions. -func TestResourceResourceDep_alignedCountScaleOut(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: func(s *terraform.State) error { - return nil - }, - Steps: []resource.TestStep{ - { - Config: testResourceResourceDepConfig(2), - }, - { - Config: testResourceResourceDepConfig(4), - Check: resource.TestCheckOutput("out", "test,test,test,test"), - }, - }, - }) -} - -// TestResourceResourceDep_alignedCountScaleIn functions like -// TestResourceDataDep_alignedCountScaleIn, but with a resource-to-resource -// dependency instead, a pretty regular use case, but included here to check -// for regressions. -func TestResourceResourceDep_alignedCountScaleIn(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: func(s *terraform.State) error { - return nil - }, - Steps: []resource.TestStep{ - { - Config: testResourceResourceDepConfig(4), - }, - { - Config: testResourceResourceDepConfig(2), - Check: resource.TestCheckOutput("out", "test,test"), - }, - }, - }) -} - -func testResourceDataDepConfig(count int) string { - return fmt.Sprintf(` -variable num { - default = "%d" -} - -resource "test_resource" "foo" { - count = "${var.num}" - required = "yes" - - required_map = { - "foo" = "bar" - } -} - -data "test_data_source" "bar" { - count = "${var.num}" - input = "${test_resource.foo.*.computed_read_only[count.index]}" -} - -output "out" { - value = "${join(",", data.test_data_source.bar.*.output)}" -} -`, count) -} - -func testDataResourceDepConfig(count int) string { - return fmt.Sprintf(` -variable num { - default = "%d" -} - -data "test_data_source" "foo" { - count = "${var.num}" - input = "test" -} - -resource "test_resource" "bar" { - count = "${var.num}" - required = "yes" - optional = "${data.test_data_source.foo.*.output[count.index]}" - - required_map = { - "foo" = "bar" - } -} - -output "out" { - value = "${join(",", test_resource.bar.*.optional)}" -} -`, count) -} - -func testResourceResourceDepConfig(count int) string { - return fmt.Sprintf(` -variable num { - default = "%d" -} - -resource "test_resource" "foo" { - count = "${var.num}" - required = "yes" - optional = "test" - - required_map = { - "foo" = "bar" - } -} - -resource "test_resource" "bar" { - count = "${var.num}" - required = "yes" - optional = "${test_resource.foo.*.optional[count.index]}" - - required_map = { - "foo" = "bar" - } -} - -output "out" { - value = "${join(",", test_resource.bar.*.optional)}" -} -`, count) -} diff --git a/builtin/providers/test/resource_dataproc_cluster_test.go b/builtin/providers/test/resource_dataproc_cluster_test.go deleted file mode 100644 index 3d5a2282f..000000000 --- a/builtin/providers/test/resource_dataproc_cluster_test.go +++ /dev/null @@ -1,491 +0,0 @@ -package test - -import ( - "reflect" - "testing" - - "github.com/google/go-cmp/cmp" - - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/terraform" -) - -var dataprocClusterSchema = map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "project": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - }, - - "region": { - Type: schema.TypeString, - Optional: true, - Default: "global", - ForceNew: true, - }, - - "labels": { - Type: schema.TypeMap, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - // GCP automatically adds two labels - // 'goog-dataproc-cluster-uuid' - // 'goog-dataproc-cluster-name' - Computed: true, - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - if old != "" { - return true - } - return false - }, - }, - - "tag_set": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, - }, - - "cluster_config": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - - "delete_autogen_bucket": { - Type: schema.TypeBool, - Optional: true, - Default: false, - Removed: "If you need a bucket that can be deleted, please create" + - "a new one and set the `staging_bucket` field", - }, - - "staging_bucket": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - "bucket": { - Type: schema.TypeString, - Computed: true, - }, - - "gce_cluster_config": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - - "zone": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - }, - - "network": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - ConflictsWith: []string{"cluster_config.0.gce_cluster_config.0.subnetwork"}, - }, - - "subnetwork": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"cluster_config.0.gce_cluster_config.0.network"}, - }, - - "tags": { - Type: schema.TypeSet, - Optional: true, - ForceNew: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - - "service_account": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - - "service_account_scopes": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - ForceNew: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - - "internal_ip_only": { - Type: schema.TypeBool, - Optional: true, - ForceNew: true, - Default: false, - }, - - "metadata": { - Type: schema.TypeMap, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - ForceNew: true, - }, - }, - }, - }, - - "master_config": &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "num_instances": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - - "image_uri": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - }, - - "machine_type": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - }, - - "disk_config": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "num_local_ssds": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - ForceNew: true, - }, - - "boot_disk_size_gb": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - ForceNew: true, - }, - - "boot_disk_type": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: "pd-standard", - }, - }, - }, - }, - "accelerators": { - Type: schema.TypeSet, - Optional: true, - ForceNew: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "accelerator_type": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "accelerator_count": { - Type: schema.TypeInt, - Required: true, - ForceNew: true, - }, - }, - }, - }, - "instance_names": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - }, - }, - }, - "preemptible_worker_config": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "num_instances": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - }, - "disk_config": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "num_local_ssds": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - ForceNew: true, - }, - - "boot_disk_size_gb": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - ForceNew: true, - }, - - "boot_disk_type": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: "pd-standard", - }, - }, - }, - }, - - "instance_names": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - }, - }, - }, - - "software_config": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "image_version": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - }, - - "override_properties": { - Type: schema.TypeMap, - Optional: true, - ForceNew: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - - "properties": { - Type: schema.TypeMap, - Computed: true, - }, - }, - }, - }, - - "initialization_action": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "script": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "timeout_sec": { - Type: schema.TypeInt, - Optional: true, - Default: 300, - ForceNew: true, - }, - }, - }, - }, - "encryption_config": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "kms_key_name": { - Type: schema.TypeString, - Required: true, - }, - }, - }, - }, - }, - }, - }, -} - -func TestDiffApply_dataprocCluster(t *testing.T) { - priorAttrs := map[string]string{ - "cluster_config.#": "1", - "cluster_config.0.bucket": "dataproc-1dc18cb2-116e-4e92-85ea-ff63a1bf2745-us-central1", - "cluster_config.0.delete_autogen_bucket": "false", - "cluster_config.0.encryption_config.#": "0", - "cluster_config.0.gce_cluster_config.#": "1", - "cluster_config.0.gce_cluster_config.0.internal_ip_only": "false", - "cluster_config.0.gce_cluster_config.0.metadata.%": "0", - "cluster_config.0.gce_cluster_config.0.network": "https://www.googleapis.com/compute/v1/projects/hc-terraform-testing/global/networks/default", - "cluster_config.0.gce_cluster_config.0.service_account": "", - "cluster_config.0.gce_cluster_config.0.service_account_scopes.#": "7", - "cluster_config.0.gce_cluster_config.0.service_account_scopes.1245378569": "https://www.googleapis.com/auth/bigtable.admin.table", - "cluster_config.0.gce_cluster_config.0.service_account_scopes.1328717722": "https://www.googleapis.com/auth/devstorage.read_write", - "cluster_config.0.gce_cluster_config.0.service_account_scopes.1693978638": "https://www.googleapis.com/auth/devstorage.full_control", - "cluster_config.0.gce_cluster_config.0.service_account_scopes.172152165": "https://www.googleapis.com/auth/logging.write", - "cluster_config.0.gce_cluster_config.0.service_account_scopes.2401844655": "https://www.googleapis.com/auth/bigquery", - "cluster_config.0.gce_cluster_config.0.service_account_scopes.299921284": "https://www.googleapis.com/auth/bigtable.data", - "cluster_config.0.gce_cluster_config.0.service_account_scopes.3804780973": "https://www.googleapis.com/auth/cloud.useraccounts.readonly", - "cluster_config.0.gce_cluster_config.0.subnetwork": "", - "cluster_config.0.gce_cluster_config.0.tags.#": "0", - "cluster_config.0.gce_cluster_config.0.zone": "us-central1-f", - "cluster_config.0.initialization_action.#": "0", - "cluster_config.0.master_config.#": "1", - "cluster_config.0.master_config.0.accelerators.#": "0", - "cluster_config.0.master_config.0.disk_config.#": "1", - "cluster_config.0.master_config.0.disk_config.0.boot_disk_size_gb": "500", - "cluster_config.0.master_config.0.disk_config.0.boot_disk_type": "pd-standard", - "cluster_config.0.master_config.0.disk_config.0.num_local_ssds": "0", - "cluster_config.0.master_config.0.image_uri": "https://www.googleapis.com/compute/v1/projects/cloud-dataproc/global/images/dataproc-1-3-deb9-20190228-000000-rc01", - "cluster_config.0.master_config.0.instance_names.#": "1", - "cluster_config.0.master_config.0.instance_names.0": "dproc-cluster-test-2ww3c60iww-m", - "cluster_config.0.master_config.0.machine_type": "n1-standard-4", - "cluster_config.0.master_config.0.num_instances": "1", - "cluster_config.0.preemptible_worker_config.#": "1", - "cluster_config.0.preemptible_worker_config.0.disk_config.#": "1", - "cluster_config.0.preemptible_worker_config.0.instance_names.#": "0", - "cluster_config.0.preemptible_worker_config.0.num_instances": "0", - "cluster_config.0.software_config.#": "1", - "cluster_config.0.software_config.0.image_version": "1.3.28-deb9", - "cluster_config.0.software_config.0.override_properties.%": "0", - "cluster_config.0.software_config.0.properties.%": "14", - "cluster_config.0.software_config.0.properties.capacity-scheduler:yarn.scheduler.capacity.root.default.ordering-policy": "fair", - "cluster_config.0.software_config.0.properties.core:fs.gs.block.size": "134217728", - "cluster_config.0.software_config.0.properties.core:fs.gs.metadata.cache.enable": "false", - "cluster_config.0.software_config.0.properties.core:hadoop.ssl.enabled.protocols": "TLSv1,TLSv1.1,TLSv1.2", - "cluster_config.0.software_config.0.properties.distcp:mapreduce.map.java.opts": "-Xmx768m", - "cluster_config.0.software_config.0.properties.distcp:mapreduce.map.memory.mb": "1024", - "cluster_config.0.software_config.0.properties.distcp:mapreduce.reduce.java.opts": "-Xmx768m", - "cluster_config.0.software_config.0.properties.distcp:mapreduce.reduce.memory.mb": "1024", - "cluster_config.0.software_config.0.properties.hdfs:dfs.datanode.address": "0.0.0.0:9866", - "cluster_config.0.software_config.0.properties.hdfs:dfs.datanode.http.address": "0.0.0.0:9864", - "cluster_config.0.software_config.0.properties.hdfs:dfs.datanode.https.address": "0.0.0.0:9865", - "cluster_config.0.software_config.0.properties.hdfs:dfs.datanode.ipc.address": "0.0.0.0:9867", - "cluster_config.0.software_config.0.properties.hdfs:dfs.namenode.handler.count": "20", - "cluster_config.0.software_config.0.properties.hdfs:dfs.namenode.http-address": "0.0.0.0:9870", - "cluster_config.0.software_config.0.properties.hdfs:dfs.namenode.https-address": "0.0.0.0:9871", - "cluster_config.0.software_config.0.properties.hdfs:dfs.namenode.lifeline.rpc-address": "dproc-cluster-test-2ww3c60iww-m:8050", - "cluster_config.0.software_config.0.properties.hdfs:dfs.namenode.secondary.http-address": "0.0.0.0:9868", - "cluster_config.0.software_config.0.properties.hdfs:dfs.namenode.secondary.https-address": "0.0.0.0:9869", - "cluster_config.0.software_config.0.properties.hdfs:dfs.namenode.service.handler.count": "10", - "cluster_config.0.software_config.0.properties.hdfs:dfs.namenode.servicerpc-address": "dproc-cluster-test-2ww3c60iww-m:8051", - "cluster_config.0.software_config.0.properties.mapred-env:HADOOP_JOB_HISTORYSERVER_HEAPSIZE": "3840", - "cluster_config.0.software_config.0.properties.mapred:mapreduce.job.maps": "21", - "cluster_config.0.software_config.0.properties.mapred:mapreduce.job.reduce.slowstart.completedmaps": "0.95", - "cluster_config.0.software_config.0.properties.mapred:mapreduce.job.reduces": "7", - "cluster_config.0.software_config.0.properties.mapred:mapreduce.map.cpu.vcores": "1", - "cluster_config.0.software_config.0.properties.mapred:mapreduce.map.java.opts": "-Xmx2457m", - "cluster_config.0.software_config.0.properties.mapred:mapreduce.map.memory.mb": "3072", - "cluster_config.0.software_config.0.properties.mapred:mapreduce.reduce.cpu.vcores": "1", - "cluster_config.0.software_config.0.properties.mapred:mapreduce.reduce.java.opts": "-Xmx2457m", - "cluster_config.0.software_config.0.properties.mapred:mapreduce.reduce.memory.mb": "3072", - "cluster_config.0.software_config.0.properties.mapred:mapreduce.task.io.sort.mb": "256", - "cluster_config.0.software_config.0.properties.mapred:yarn.app.mapreduce.am.command-opts": "-Xmx2457m", - "cluster_config.0.software_config.0.properties.mapred:yarn.app.mapreduce.am.resource.cpu-vcores": "1", - "cluster_config.0.software_config.0.properties.mapred:yarn.app.mapreduce.am.resource.mb": "3072", - "cluster_config.0.software_config.0.properties.presto-jvm:MaxHeapSize": "12288m", - "cluster_config.0.software_config.0.properties.presto:query.max-memory-per-node": "7372MB", - "cluster_config.0.software_config.0.properties.presto:query.max-total-memory-per-node": "7372MB", - "cluster_config.0.software_config.0.properties.spark-env:SPARK_DAEMON_MEMORY": "3840m", - "cluster_config.0.software_config.0.properties.spark:spark.driver.maxResultSize": "1920m", - "cluster_config.0.software_config.0.properties.spark:spark.driver.memory": "3840m", - "cluster_config.0.software_config.0.properties.spark:spark.executor.cores": "2", - "cluster_config.0.software_config.0.properties.spark:spark.executor.instances": "2", - "cluster_config.0.software_config.0.properties.spark:spark.executor.memory": "5586m", - "cluster_config.0.software_config.0.properties.spark:spark.executorEnv.OPENBLAS_NUM_THREADS": "1", - "cluster_config.0.software_config.0.properties.spark:spark.scheduler.mode": "FAIR", - "cluster_config.0.software_config.0.properties.spark:spark.sql.cbo.enabled": "true", - "cluster_config.0.software_config.0.properties.spark:spark.yarn.am.memory": "640m", - "cluster_config.0.software_config.0.properties.yarn-env:YARN_TIMELINESERVER_HEAPSIZE": "3840", - "cluster_config.0.software_config.0.properties.yarn:yarn.nodemanager.resource.memory-mb": "12288", - "cluster_config.0.software_config.0.properties.yarn:yarn.resourcemanager.nodemanager-graceful-decommission-timeout-secs": "86400", - "cluster_config.0.software_config.0.properties.yarn:yarn.scheduler.maximum-allocation-mb": "12288", - "cluster_config.0.software_config.0.properties.yarn:yarn.scheduler.minimum-allocation-mb": "1024", - "cluster_config.0.staging_bucket": "", - "id": "dproc-cluster-test-ktbyrniu4e", - "labels.%": "4", - "labels.goog-dataproc-cluster-name": "dproc-cluster-test-ktbyrniu4e", - "labels.goog-dataproc-cluster-uuid": "d576c4e0-8fda-4ad1-abf5-ec951ab25855", - "labels.goog-dataproc-location": "us-central1", - "labels.key1": "value1", - "tag_set.#": "0", - } - - diff := &terraform.InstanceDiff{ - Attributes: map[string]*terraform.ResourceAttrDiff{ - "labels.%": &terraform.ResourceAttrDiff{Old: "4", New: "1", NewComputed: false, NewRemoved: false, NewExtra: interface{}(nil), RequiresNew: false, Sensitive: false, Type: 0x0}, - "labels.goog-dataproc-cluster-name": &terraform.ResourceAttrDiff{Old: "dproc-cluster-test-ktbyrniu4e", New: "", NewComputed: false, NewRemoved: true, NewExtra: interface{}(nil), RequiresNew: false, Sensitive: false, Type: 0x0}, - "labels.goog-dataproc-cluster-uuid": &terraform.ResourceAttrDiff{Old: "d576c4e0-8fda-4ad1-abf5-ec951ab25855", New: "", NewComputed: false, NewRemoved: true, NewExtra: interface{}(nil), RequiresNew: false, Sensitive: false, Type: 0x0}, - "labels.goog-dataproc-location": &terraform.ResourceAttrDiff{Old: "us-central1", New: "", NewComputed: false, NewRemoved: true, NewExtra: interface{}(nil), RequiresNew: false, Sensitive: false, Type: 0x0}, - }, - } - - newAttrs, err := diff.Apply(priorAttrs, (&schema.Resource{Schema: dataprocClusterSchema}).CoreConfigSchema()) - if err != nil { - t.Fatal(err) - } - - // the diff'ed labale elements should be removed - delete(priorAttrs, "labels.goog-dataproc-cluster-name") - delete(priorAttrs, "labels.goog-dataproc-cluster-uuid") - delete(priorAttrs, "labels.goog-dataproc-location") - priorAttrs["labels.%"] = "1" - - // the missing required "name" should be added - priorAttrs["name"] = "" - - if !reflect.DeepEqual(priorAttrs, newAttrs) { - t.Fatal(cmp.Diff(priorAttrs, newAttrs)) - } -} diff --git a/builtin/providers/test/resource_defaults_test.go b/builtin/providers/test/resource_defaults_test.go deleted file mode 100644 index 8aabd4482..000000000 --- a/builtin/providers/test/resource_defaults_test.go +++ /dev/null @@ -1,168 +0,0 @@ -package test - -import ( - "strings" - "testing" - - "github.com/hashicorp/terraform/helper/resource" -) - -func TestResourceDefaults_basic(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_defaults" "foo" { -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_defaults.foo", "default_string", "default string", - ), - resource.TestCheckResourceAttr( - "test_resource_defaults.foo", "default_bool", "1", - ), - resource.TestCheckNoResourceAttr( - "test_resource_defaults.foo", "nested.#", - ), - ), - }, - }, - }) -} - -func TestResourceDefaults_change(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - { - Config: strings.TrimSpace(` -resource "test_resource_defaults" "foo" { -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_defaults.foo", "default_string", "default string", - ), - resource.TestCheckResourceAttr( - "test_resource_defaults.foo", "default_bool", "1", - ), - resource.TestCheckNoResourceAttr( - "test_resource_defaults.foo", "nested.#", - ), - ), - }, - { - Config: strings.TrimSpace(` -resource "test_resource_defaults" "foo" { - default_string = "new" - default_bool = false - nested { - optional = "nested" - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_defaults.foo", "default_string", "new", - ), - resource.TestCheckResourceAttr( - "test_resource_defaults.foo", "default_bool", "false", - ), - resource.TestCheckResourceAttr( - "test_resource_defaults.foo", "nested.#", "1", - ), - resource.TestCheckResourceAttr( - "test_resource_defaults.foo", "nested.2950978312.optional", "nested", - ), - resource.TestCheckResourceAttr( - "test_resource_defaults.foo", "nested.2950978312.string", "default nested", - ), - ), - }, - { - Config: strings.TrimSpace(` -resource "test_resource_defaults" "foo" { - default_string = "new" - default_bool = false - nested { - optional = "nested" - string = "new" - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_defaults.foo", "default_string", "new", - ), - resource.TestCheckResourceAttr( - "test_resource_defaults.foo", "default_bool", "false", - ), - resource.TestCheckResourceAttr( - "test_resource_defaults.foo", "nested.#", "1", - ), - resource.TestCheckResourceAttr( - "test_resource_defaults.foo", "nested.782850362.optional", "nested", - ), - resource.TestCheckResourceAttr( - "test_resource_defaults.foo", "nested.782850362.string", "new", - ), - ), - }, - }, - }) -} - -func TestResourceDefaults_inSet(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_defaults" "foo" { - nested { - optional = "val" - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_defaults.foo", "default_string", "default string", - ), - resource.TestCheckResourceAttr( - "test_resource_defaults.foo", "default_bool", "1", - ), - resource.TestCheckResourceAttr( - "test_resource_defaults.foo", "nested.2826070548.optional", "val", - ), - resource.TestCheckResourceAttr( - "test_resource_defaults.foo", "nested.2826070548.string", "default nested", - ), - ), - }, - }, - }) -} - -func TestDefaults_emptyString(t *testing.T) { - config := ` -resource "test_resource_defaults" "test" { - default_string = "" -} -` - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource_defaults.test", "default_string", ""), - ), - }, - }, - }) -} diff --git a/builtin/providers/test/resource_deprecated_test.go b/builtin/providers/test/resource_deprecated_test.go deleted file mode 100644 index 8817567d9..000000000 --- a/builtin/providers/test/resource_deprecated_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package test - -import ( - "regexp" - "strings" - "testing" - - "github.com/hashicorp/terraform/helper/resource" -) - -// an empty config should be ok, because no deprecated/removed fields are set. -func TestResourceDeprecated_empty(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_deprecated" "foo" { -} - `), - }, - }, - }) -} - -// Deprecated fields should still work -func TestResourceDeprecated_deprecatedOK(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_deprecated" "foo" { - map_deprecated = { - "a" = "b", - } - set_block_deprecated { - value = "1" - } - list_block_deprecated { - value = "2" - } -} - `), - }, - }, - }) -} - -// Declaring an empty block should trigger the error -func TestResourceDeprecated_removedBlocks(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_deprecated" "foo" { - set_block_removed { - } - list_block_removed { - } -} - `), - ExpectError: regexp.MustCompile("REMOVED"), - }, - }, - }) -} diff --git a/builtin/providers/test/resource_diff_suppress_test.go b/builtin/providers/test/resource_diff_suppress_test.go deleted file mode 100644 index 89416f32a..000000000 --- a/builtin/providers/test/resource_diff_suppress_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package test - -import ( - "errors" - "strings" - "testing" - - "github.com/hashicorp/terraform/addrs" - - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" -) - -func TestResourceDiffSuppress_create(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_diff_suppress" "foo" { - val_to_upper = "foo" -} - `), - }, - }, - }) -} -func TestResourceDiffSuppress_update(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_diff_suppress" "foo" { - val_to_upper = "foo" -} - `), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_diff_suppress" "foo" { - val_to_upper = "bar" - optional = "more" -} - `), - }, - }, - }) -} - -func TestResourceDiffSuppress_updateIgnoreChanges(t *testing.T) { - // None of these steps should replace the instance - id := "" - checkFunc := func(s *terraform.State) error { - root := s.ModuleByPath(addrs.RootModuleInstance) - res := root.Resources["test_resource_diff_suppress.foo"] - if id != "" && res.Primary.ID != id { - return errors.New("expected no resource replacement") - } - id = res.Primary.ID - return nil - } - - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_diff_suppress" "foo" { - val_to_upper = "foo" - - network = "foo" - subnetwork = "foo" - - node_pool { - name = "default-pool" - } - lifecycle { - ignore_changes = ["node_pool"] - } -} - `), - Check: checkFunc, - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_diff_suppress" "foo" { - val_to_upper = "foo" - - network = "ignored" - subnetwork = "ignored" - - node_pool { - name = "default-pool" - } - lifecycle { - ignore_changes = ["node_pool"] - } -} - `), - Check: checkFunc, - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_diff_suppress" "foo" { - val_to_upper = "foo" - - network = "ignored" - subnetwork = "ignored" - - node_pool { - name = "ignored" - } - lifecycle { - ignore_changes = ["node_pool"] - } -} - `), - Check: checkFunc, - }, - }, - }) -} diff --git a/builtin/providers/test/resource_force_new_test.go b/builtin/providers/test/resource_force_new_test.go deleted file mode 100644 index 3e0bf19c3..000000000 --- a/builtin/providers/test/resource_force_new_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package test - -import ( - "strings" - "testing" - - "github.com/hashicorp/terraform/helper/resource" -) - -func TestResourceForceNew_create(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_force_new" "foo" { - triggers = { - "a" = "foo" - } -}`), - }, - }, - }) -} -func TestResourceForceNew_update(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_force_new" "foo" { - triggers = { - "a" = "foo" - } -}`), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_force_new" "foo" { - triggers = { - "a" = "bar" - } -}`), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_force_new" "foo" { - triggers = { - "b" = "bar" - } -}`), - }, - }, - }) -} - -func TestResourceForceNew_remove(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_force_new" "foo" { - triggers = { - "a" = "bar" - } -}`), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_force_new" "foo" { -} `), - }, - }, - }) -} diff --git a/builtin/providers/test/resource_gh12183_test.go b/builtin/providers/test/resource_gh12183_test.go deleted file mode 100644 index 9cf100587..000000000 --- a/builtin/providers/test/resource_gh12183_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package test - -import ( - "strings" - "testing" - - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" -) - -// Tests GH-12183. This would previously cause a crash. More granular -// unit tests are scattered through helper/schema and terraform core for -// this. -func TestResourceGH12183_basic(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_gh12183" "a" { - config { - name = "hello" - } -} - -resource "test_resource_gh12183" "b" { - key = "${lookup(test_resource_gh12183.a.config[0], "name")}" - config { - name = "required" - } -} - `), - Check: func(s *terraform.State) error { - return nil - }, - }, - }, - }) -} diff --git a/builtin/providers/test/resource_list_set_test.go b/builtin/providers/test/resource_list_set_test.go deleted file mode 100644 index f1e8353fb..000000000 --- a/builtin/providers/test/resource_list_set_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package test - -import ( - "strings" - "testing" - - "github.com/hashicorp/terraform/helper/resource" -) - -func TestResourceListSet_basic(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list_set" "foo" { - list { - set { - elem = "A" - } - set { - elem = "B" - } - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource_list_set.foo", "list.0.set.1255198513.elem", "B"), - resource.TestCheckResourceAttr("test_resource_list_set.foo", "list.0.set.3554254475.elem", "A"), - resource.TestCheckResourceAttr("test_resource_list_set.foo", "list.0.set.#", "2"), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list_set" "foo" { - list { - set { - elem = "B" - } - set { - elem = "C" - } - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource_list_set.foo", "list.0.set.1255198513.elem", "B"), - resource.TestCheckResourceAttr("test_resource_list_set.foo", "list.0.set.1037565863.elem", "C"), - resource.TestCheckResourceAttr("test_resource_list_set.foo", "list.0.set.#", "2"), - ), - }, - }, - }) -} - -func TestResourceListSet_updateNested(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list_set" "foo" { - replication_configuration { - role = "role_id" - rules { - id = "foobar" - status = "Enabled" - priority = 42 - filter { - tags = { - ReplicateMe = "Yes" - } - } - destination { - bucket = "bucket_id" - storage_class = "STANDARD" - } - } - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource_list_set.foo", "replication_configuration.0.rules.#", "1"), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list_set" "foo" { - replication_configuration { - role = "role_id" - rules { - id = "foobar" - status = "Enabled" - priority = 42 - filter { - prefix = "foo" - tags = { - ReplicateMe = "Yes" - AnotherTag = "OK" - } - } - destination { - bucket = "bucket_id" - storage_class = "STANDARD" - } - } - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource_list_set.foo", "replication_configuration.0.rules.#", "1"), - ), - }, - }, - }) -} diff --git a/builtin/providers/test/resource_list_test.go b/builtin/providers/test/resource_list_test.go deleted file mode 100644 index 876a81fd5..000000000 --- a/builtin/providers/test/resource_list_test.go +++ /dev/null @@ -1,566 +0,0 @@ -package test - -import ( - "regexp" - "strings" - "testing" - - "github.com/hashicorp/terraform/helper/resource" -) - -// an empty config should be ok, because no deprecated/removed fields are set. -func TestResourceList_changed(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list" "foo" { - list_block { - string = "a" - int = 1 - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_list.foo", "list_block.#", "1", - ), - resource.TestCheckResourceAttr( - "test_resource_list.foo", "list_block.0.string", "a", - ), - resource.TestCheckResourceAttr( - "test_resource_list.foo", "list_block.0.int", "1", - ), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list" "foo" { - list_block { - string = "a" - int = 1 - } - - list_block { - string = "b" - int = 2 - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_list.foo", "list_block.#", "2", - ), - resource.TestCheckResourceAttr( - "test_resource_list.foo", "list_block.0.string", "a", - ), - resource.TestCheckResourceAttr( - "test_resource_list.foo", "list_block.0.int", "1", - ), - resource.TestCheckResourceAttr( - "test_resource_list.foo", "list_block.1.string", "b", - ), - resource.TestCheckResourceAttr( - "test_resource_list.foo", "list_block.1.int", "2", - ), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list" "foo" { - list_block { - string = "a" - int = 1 - } - - list_block { - string = "c" - int = 2 - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_list.foo", "list_block.#", "2", - ), - resource.TestCheckResourceAttr( - "test_resource_list.foo", "list_block.0.string", "a", - ), - resource.TestCheckResourceAttr( - "test_resource_list.foo", "list_block.0.int", "1", - ), - resource.TestCheckResourceAttr( - "test_resource_list.foo", "list_block.1.string", "c", - ), - resource.TestCheckResourceAttr( - "test_resource_list.foo", "list_block.1.int", "2", - ), - ), - }, - }, - }) -} - -func TestResourceList_mapList(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -variable "map" { - type = map(string) - default = {} -} - -resource "test_resource_list" "foo" { - map_list = [ - { - a = "1" - }, - var.map - ] -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_list.foo", "map_list.1", "", - ), - ), - }, - }, - }) -} - -func TestResourceList_sublist(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list" "foo" { - list_block { - sublist_block { - string = "a" - int = 1 - } - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_list.foo", "list_block.0.sublist_block.#", "1", - ), - resource.TestCheckResourceAttr( - "test_resource_list.foo", "list_block.0.sublist_block.0.string", "a", - ), - resource.TestCheckResourceAttr( - "test_resource_list.foo", "list_block.0.sublist_block.0.int", "1", - ), - ), - }, - }, - }) -} - -func TestResourceList_interpolationChanges(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list" "foo" { - list_block { - string = "x" - } -} -resource "test_resource_list" "bar" { - list_block { - string = test_resource_list.foo.id - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_list.foo", "list_block.0.string", "x", - ), - resource.TestCheckResourceAttr( - "test_resource_list.bar", "list_block.0.string", "testId", - ), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list" "baz" { - list_block { - string = "x" - int = 1 - } -} -resource "test_resource_list" "bar" { - list_block { - string = test_resource_list.baz.id - int = 3 - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_list.baz", "list_block.0.string", "x", - ), - resource.TestCheckResourceAttr( - "test_resource_list.bar", "list_block.0.string", "testId", - ), - ), - }, - }, - }) -} - -func TestResourceList_removedForcesNew(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list" "foo" { - list_block { - force_new = "ok" - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_list.foo", "list_block.0.force_new", "ok", - ), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list" "foo" { -} - `), - Check: resource.ComposeTestCheckFunc(), - }, - }, - }) -} - -func TestResourceList_emptyStrings(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list" "foo" { - list_block { - sublist = ["a", ""] - } - - list_block { - sublist = [""] - } - - list_block { - sublist = ["", "c", ""] - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource_list.foo", "list_block.0.sublist.0", "a"), - resource.TestCheckResourceAttr("test_resource_list.foo", "list_block.0.sublist.1", ""), - resource.TestCheckResourceAttr("test_resource_list.foo", "list_block.1.sublist.0", ""), - resource.TestCheckResourceAttr("test_resource_list.foo", "list_block.2.sublist.0", ""), - resource.TestCheckResourceAttr("test_resource_list.foo", "list_block.2.sublist.1", "c"), - resource.TestCheckResourceAttr("test_resource_list.foo", "list_block.2.sublist.2", ""), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list" "foo" { - list_block { - sublist = [""] - } - - list_block { - sublist = [] - } - - list_block { - sublist = ["", "c"] - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource_list.foo", "list_block.0.sublist.#", "1"), - resource.TestCheckResourceAttr("test_resource_list.foo", "list_block.0.sublist.0", ""), - resource.TestCheckResourceAttr("test_resource_list.foo", "list_block.1.sublist.#", "0"), - resource.TestCheckResourceAttr("test_resource_list.foo", "list_block.2.sublist.1", "c"), - resource.TestCheckResourceAttr("test_resource_list.foo", "list_block.2.sublist.#", "2"), - ), - }, - }, - }) -} - -func TestResourceList_addRemove(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list" "foo" { -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource_list.foo", "computed_list.#", "0"), - resource.TestCheckResourceAttr("test_resource_list.foo", "dependent_list.#", "0"), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list" "foo" { - dependent_list { - val = "a" - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource_list.foo", "computed_list.#", "1"), - resource.TestCheckResourceAttr("test_resource_list.foo", "dependent_list.#", "1"), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list" "foo" { -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource_list.foo", "computed_list.#", "0"), - resource.TestCheckResourceAttr("test_resource_list.foo", "dependent_list.#", "0"), - ), - }, - }, - }) -} - -func TestResourceList_planUnknownInterpolation(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list" "foo" { - list_block { - string = "x" - } -} -resource "test_resource_list" "bar" { - list_block { - sublist = [ - test_resource_list.foo.list_block[0].string, - ] - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_list.bar", "list_block.0.sublist.0", "x", - ), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list" "foo" { - list_block { - string = "x" - } - dependent_list { - val = "y" - } -} -resource "test_resource_list" "bar" { - list_block { - sublist = [ - test_resource_list.foo.computed_list[0], - ] - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_list.bar", "list_block.0.sublist.0", "y", - ), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list" "foo" { - list_block { - string = "x" - } - dependent_list { - val = "z" - } -} -resource "test_resource_list" "bar" { - list_block { - sublist = [ - test_resource_list.foo.computed_list[0], - ] - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_list.bar", "list_block.0.sublist.0", "z", - ), - ), - }, - }, - }) -} - -func TestResourceList_planUnknownInterpolationList(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list" "foo" { - dependent_list { - val = "y" - } -} -resource "test_resource_list" "bar" { - list_block { - sublist_block_optional { - list = test_resource_list.foo.computed_list - } - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_list.bar", "list_block.0.sublist_block_optional.0.list.0", "y", - ), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list" "foo" { - dependent_list { - val = "z" - } -} -resource "test_resource_list" "bar" { - list_block { - sublist_block_optional { - list = test_resource_list.foo.computed_list - } - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_list.bar", "list_block.0.sublist_block_optional.0.list.0", "z", - ), - ), - }, - }, - }) -} - -func TestResourceList_dynamicList(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list" "a" { - dependent_list { - val = "a" - } - - dependent_list { - val = "b" - } -} -resource "test_resource_list" "b" { - list_block { - string = "constant" - } - dynamic "list_block" { - for_each = test_resource_list.a.computed_list - content { - string = list_block.value - } - } -} - `), - Check: resource.ComposeTestCheckFunc(), - }, - }, - }) -} - -func TestResourceList_dynamicMinItems(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -variable "a" { - type = list(number) - default = [1] -} - -resource "test_resource_list" "b" { - dynamic "min_items" { - for_each = var.a - content { - val = "foo" - } - } -} - `), - ExpectError: regexp.MustCompile(`attribute supports 2`), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list" "a" { - dependent_list { - val = "a" - } - - dependent_list { - val = "b" - } -} -resource "test_resource_list" "b" { - list_block { - string = "constant" - } - dynamic "min_items" { - for_each = test_resource_list.a.computed_list - content { - val = min_items.value - } - } -} - `), - }, - }, - }) -} diff --git a/builtin/providers/test/resource_map_test.go b/builtin/providers/test/resource_map_test.go deleted file mode 100644 index 0d82d5f4f..000000000 --- a/builtin/providers/test/resource_map_test.go +++ /dev/null @@ -1,138 +0,0 @@ -package test - -import ( - "testing" - - "github.com/hashicorp/terraform/helper/resource" -) - -func TestResourceMap_basic(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - { - Config: ` -resource "test_resource_map" "foobar" { - name = "test" - map_of_three = { - one = "one" - two = "two" - empty = "" - } -}`, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_map.foobar", "map_of_three.empty", "", - ), - ), - }, - }, - }) -} - -func TestResourceMap_basicWithVars(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - { - Config: ` -variable "a" { - default = "a" -} - -variable "b" { - default = "b" -} - -resource "test_resource_map" "foobar" { - name = "test" - map_of_three = { - one = var.a - two = var.b - empty = "" - } -}`, - Check: resource.ComposeTestCheckFunc(), - }, - }, - }) -} - -func TestResourceMap_computedMap(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - { - Config: ` -resource "test_resource_map" "foobar" { - name = "test" - map_of_three = { - one = "one" - two = "two" - empty = "" - } - map_values = { - a = "1" - b = "2" - } -}`, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_map.foobar", "computed_map.a", "1", - ), - resource.TestCheckResourceAttr( - "test_resource_map.foobar", "computed_map.b", "2", - ), - ), - }, - { - Config: ` -resource "test_resource_map" "foobar" { - name = "test" - map_of_three = { - one = "one" - two = "two" - empty = "" - } - map_values = { - a = "3" - b = "4" - } -}`, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_map.foobar", "computed_map.a", "3", - ), - resource.TestCheckResourceAttr( - "test_resource_map.foobar", "computed_map.b", "4", - ), - ), - }, - { - Config: ` -resource "test_resource_map" "foobar" { - name = "test" - map_of_three = { - one = "one" - two = "two" - empty = "" - } - map_values = { - a = "3" - } -}`, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_map.foobar", "computed_map.a", "3", - ), - resource.TestCheckNoResourceAttr( - "test_resource_map.foobar", "computed_map.b", - ), - ), - }, - }, - }) -} diff --git a/builtin/providers/test/resource_nested_id_test.go b/builtin/providers/test/resource_nested_id_test.go deleted file mode 100644 index 9ca7a2468..000000000 --- a/builtin/providers/test/resource_nested_id_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package test - -import ( - "strings" - "testing" - - "github.com/hashicorp/terraform/helper/resource" -) - -func TestResourceNestedId_unknownId(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_id" "foo" { -} -resource "test_resource_nested_id" "bar" { - list_block { - id = test_resource_nested_id.foo.id - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource_nested_id.bar", "list_block.0.id", "testId"), - ), - }, - }, - }) -} diff --git a/builtin/providers/test/resource_nested_set_test.go b/builtin/providers/test/resource_nested_set_test.go deleted file mode 100644 index dddce0e81..000000000 --- a/builtin/providers/test/resource_nested_set_test.go +++ /dev/null @@ -1,653 +0,0 @@ -package test - -import ( - "errors" - "fmt" - "regexp" - "strings" - "testing" - - "github.com/hashicorp/terraform/addrs" - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" -) - -func TestResourceNestedSet_basic(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - single { - value = "bar" - } -} - `), - }, - }, - }) -} - -func TestResourceNestedSet_basicImport(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - single { - value = "bar" - } -} - `), - }, - resource.TestStep{ - ImportState: true, - ResourceName: "test_resource_nested_set.foo", - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - single { - value = "bar" - } -} - `), - ImportStateCheck: func(ss []*terraform.InstanceState) error { - for _, s := range ss { - if s.Attributes["multi.#"] != "0" || - s.Attributes["single.#"] != "0" || - s.Attributes["type_list.#"] != "0" || - s.Attributes["with_list.#"] != "0" { - return fmt.Errorf("missing blocks in imported state:\n%s", s) - } - } - return nil - }, - }, - }, - }) -} - -// The set should not be generated because of it's computed value -func TestResourceNestedSet_noSet(t *testing.T) { - checkFunc := func(s *terraform.State) error { - root := s.ModuleByPath(addrs.RootModuleInstance) - res := root.Resources["test_resource_nested_set.foo"] - for k, v := range res.Primary.Attributes { - if strings.HasPrefix(k, "single") && k != "single.#" { - return fmt.Errorf("unexpected set value: %s:%s", k, v) - } - } - return nil - } - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { -} - `), - Check: checkFunc, - }, - }, - }) -} - -// the empty type_list must be passed to the provider with 1 nil element -func TestResourceNestedSet_emptyBlock(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - type_list { - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource_nested_set.foo", "type_list.#", "1"), - ), - }, - }, - }) -} - -func TestResourceNestedSet_emptyNestedListBlock(t *testing.T) { - checkFunc := func(s *terraform.State) error { - root := s.ModuleByPath(addrs.RootModuleInstance) - res := root.Resources["test_resource_nested_set.foo"] - found := false - for k := range res.Primary.Attributes { - if !regexp.MustCompile(`^with_list\.\d+\.list_block\.`).MatchString(k) { - continue - } - found = true - } - if !found { - return fmt.Errorf("with_list.X.list_block not found") - } - return nil - } - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - with_list { - required = "ok" - list_block { - } - } -} - `), - Check: checkFunc, - }, - }, - }) -} -func TestResourceNestedSet_emptyNestedList(t *testing.T) { - checkFunc := func(s *terraform.State) error { - root := s.ModuleByPath(addrs.RootModuleInstance) - res := root.Resources["test_resource_nested_set.foo"] - found := false - for k, v := range res.Primary.Attributes { - if regexp.MustCompile(`^with_list\.\d+\.list\.#$`).MatchString(k) { - found = true - if v != "0" { - return fmt.Errorf("expected empty list: %s, got %s", k, v) - } - break - } - } - if !found { - return fmt.Errorf("with_list.X.nested_list not found") - } - return nil - } - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - with_list { - required = "ok" - list = [] - } -} - `), - Check: checkFunc, - }, - }, - }) -} - -func TestResourceNestedSet_addRemove(t *testing.T) { - var id string - checkFunc := func(s *terraform.State) error { - root := s.ModuleByPath(addrs.RootModuleInstance) - res := root.Resources["test_resource_nested_set.foo"] - if res.Primary.ID == id { - return errors.New("expected new resource") - } - id = res.Primary.ID - return nil - } - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { -} - `), - Check: checkFunc, - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - single { - value = "bar" - } -} - `), - Check: resource.ComposeTestCheckFunc( - checkFunc, - resource.TestCheckResourceAttr( - "test_resource_nested_set.foo", "single.#", "1", - ), - // the hash of single seems to change here, so we're not - // going to test for "value" directly - // FIXME: figure out why the set hash changes - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_nested_set.foo", "single.#", "0", - ), - checkFunc, - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - single { - value = "bar" - } -} - `), - Check: checkFunc, - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - single { - value = "bar" - optional = "baz" - } -} - `), - Check: checkFunc, - }, - - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { -} - `), - Check: checkFunc, - }, - }, - }) -} -func TestResourceNestedSet_multiAddRemove(t *testing.T) { - checkFunc := func(s *terraform.State) error { - return nil - } - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { -} - `), - Check: checkFunc, - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - multi { - optional = "bar" - } -} - `), - Check: checkFunc, - }, - - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { -} - `), - Check: checkFunc, - }, - - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - multi { - set { - required = "val" - } - } -} - `), - Check: checkFunc, - }, - - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - multi { - set { - required = "new" - } - } -} - `), - Check: checkFunc, - }, - - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - multi { - set { - required = "new" - optional_int = 3 - } - } -} - `), - Check: checkFunc, - }, - - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - single { - value = "bar" - optional = "baz" - } - multi { - set { - required = "new" - optional_int = 3 - } - } -} - `), - Check: checkFunc, - }, - - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - optional = true - single { - value = "bar" - optional = "baz" - } - multi { - set { - required = "new" - optional_int = 3 - } - } -} - `), - Check: checkFunc, - }, - }, - }) -} - -func TestResourceNestedSet_forceNewEmptyString(t *testing.T) { - var id string - step := 0 - checkFunc := func(s *terraform.State) error { - root := s.ModuleByPath(addrs.RootModuleInstance) - res := root.Resources["test_resource_nested_set.foo"] - defer func() { - step++ - id = res.Primary.ID - }() - - if step == 2 && res.Primary.ID == id { - // setting an empty string currently does not trigger ForceNew, but - // it should in the future. - return nil - } - - if res.Primary.ID == id { - return errors.New("expected new resource") - } - - return nil - } - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - multi { - set { - required = "val" - } - } -} - `), - Check: checkFunc, - }, - - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - multi { - set { - required = "" - } - } -} - `), - Check: checkFunc, - }, - - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - force_new = "" -} - `), - Check: checkFunc, - }, - }, - }) -} - -func TestResourceNestedSet_setWithList(t *testing.T) { - checkFunc := func(s *terraform.State) error { - return nil - } - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - with_list { - required = "bar" - list = ["initial value"] - } -} - `), - Check: checkFunc, - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - with_list { - required = "bar" - list = ["second value"] - } -} - `), - Check: checkFunc, - }, - }, - }) -} - -// This is the same as forceNewEmptyString, but we start with the empty value, -// instead of changing it. -func TestResourceNestedSet_nestedSetEmptyString(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - multi { - set { - required = "" - } - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_nested_set.foo", "multi.529860700.set.4196279896.required", "", - ), - ), - }, - }, - }) -} - -func TestResourceNestedSet_emptySet(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - multi { - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_nested_set.foo", "multi.#", "1", - ), - ), - }, - }, - }) -} - -func TestResourceNestedSet_multipleUnknownSetElements(t *testing.T) { - checkFunc := func(s *terraform.State) error { - return nil - } - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "a" { -} - -resource "test_resource_nested_set" "b" { -} - -resource "test_resource_nested_set" "c" { - multi { - optional = test_resource_nested_set.a.id - } - multi { - optional = test_resource_nested_set.b.id - } -} - `), - Check: checkFunc, - }, - }, - }) -} - -func TestResourceNestedSet_interpolationChanges(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "foo" { - single { - value = "x" - } -} -resource "test_resource_nested_set" "bar" { - single { - value = test_resource_nested_set.foo.id - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_nested_set.foo", "single.#", "1", - ), - resource.TestCheckResourceAttr( - "test_resource_nested_set.bar", "single.#", "1", - ), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested_set" "baz" { - single { - value = "x" - } -} -resource "test_resource_nested_set" "bar" { - single { - value = test_resource_nested_set.baz.id - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_nested_set.baz", "single.#", "1", - ), - resource.TestCheckResourceAttr( - "test_resource_nested_set.bar", "single.#", "1", - ), - ), - }, - }, - }) -} - -func TestResourceNestedSet_dynamicSetBlock(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "a" { - required = "ok" - required_map = { - a = "b" - } -} - -resource "test_resource_nested_set" "foo" { - dynamic "with_list" { - iterator = thing - for_each = test_resource.a.computed_list - content { - required = thing.value - list = [thing.key] - } - } -} - `), - Check: resource.ComposeTestCheckFunc(), - }, - }, - }) -} diff --git a/builtin/providers/test/resource_nested_test.go b/builtin/providers/test/resource_nested_test.go deleted file mode 100644 index c525f625c..000000000 --- a/builtin/providers/test/resource_nested_test.go +++ /dev/null @@ -1,217 +0,0 @@ -package test - -import ( - "errors" - "strings" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform/addrs" - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" -) - -func TestResourceNested_basic(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested" "foo" { - nested { - string = "val" - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource_nested.foo", "nested.#", "1", - ), - resource.TestCheckResourceAttr( - "test_resource_nested.foo", "nested.1877647874.string", "val", - ), - resource.TestCheckResourceAttr( - "test_resource_nested.foo", "list_block.0.sub_list_block.0.bool", "false", - ), - ), - }, - }, - }) -} - -func TestResourceNested_addRemove(t *testing.T) { - var id string - idCheck := func(s *terraform.State) error { - root := s.ModuleByPath(addrs.RootModuleInstance) - res := root.Resources["test_resource_nested.foo"] - if res.Primary.ID == id { - return errors.New("expected new resource") - } - id = res.Primary.ID - return nil - } - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested" "foo" { -} - `), - Check: resource.ComposeTestCheckFunc( - idCheck, - resource.TestCheckResourceAttr( - "test_resource_nested.foo", "nested.#", "0", - ), - // Checking for a count of 0 and a nonexistent count should - // now be the same operation. - resource.TestCheckNoResourceAttr( - "test_resource_nested.foo", "nested.#", - ), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested" "foo" { - nested { - string = "val" - } -} - `), - Check: resource.ComposeTestCheckFunc( - idCheck, - resource.TestCheckResourceAttr( - "test_resource_nested.foo", "nested.1877647874.string", "val", - ), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested" "foo" { - optional = true - nested { - string = "val" - } -} - `), - Check: resource.ComposeTestCheckFunc( - idCheck, - resource.TestCheckResourceAttr( - "test_resource_nested.foo", "nested.1877647874.string", "val", - ), - resource.TestCheckResourceAttr( - "test_resource_nested.foo", "optional", "true", - ), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested" "foo" { - nested { - string = "val" - } -} - `), - Check: resource.ComposeTestCheckFunc( - idCheck, - resource.TestCheckResourceAttr( - "test_resource_nested.foo", "nested.1877647874.string", "val", - ), - resource.TestCheckNoResourceAttr( - "test_resource_nested.foo", "optional", - ), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested" "foo" { - nested { - string = "val" - optional = true - } -} - `), - Check: resource.ComposeTestCheckFunc( - idCheck, - resource.TestCheckResourceAttr( - "test_resource_nested.foo", "nested.2994502535.string", "val", - ), - resource.TestCheckResourceAttr( - "test_resource_nested.foo", "nested.2994502535.optional", "true", - ), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested" "foo" { -} - `), - Check: resource.ComposeTestCheckFunc( - idCheck, - resource.TestCheckNoResourceAttr( - "test_resource_nested.foo", "nested.#", - ), - ), - }, - }, - }) -} - -func TestResourceNested_dynamic(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested" "foo" { - dynamic "nested" { - for_each = [["a"], []] - content { - string = join(",", nested.value) - optional = false - dynamic "nested_again" { - for_each = nested.value - content { - string = nested_again.value - } - } - } - } -} - `), - Check: func(s *terraform.State) error { - rs, ok := s.RootModule().Resources["test_resource_nested.foo"] - if !ok { - return errors.New("missing resource in state") - } - - got := rs.Primary.Attributes - want := map[string]string{ - "nested.#": "2", - "nested.33842314.string": "a", - "nested.33842314.optional": "false", - "nested.33842314.nested_again.#": "1", - "nested.33842314.nested_again.936590934.string": "a", - "nested.140280279.string": "", - "nested.140280279.optional": "false", - "nested.140280279.nested_again.#": "0", - "list_block.#": "1", - "list_block.0.sub_list_block.#": "1", - "list_block.0.sub_list_block.0.bool": "false", - "list_block.0.sub_list_block.0.set.#": "0", - } - delete(got, "id") // it's random, so not useful for testing - - if !cmp.Equal(got, want) { - return errors.New("wrong result\n" + cmp.Diff(want, got)) - } - - return nil - }, - }, - }, - }) -} diff --git a/builtin/providers/test/resource_provider_meta_test.go b/builtin/providers/test/resource_provider_meta_test.go deleted file mode 100644 index 3b92d0a40..000000000 --- a/builtin/providers/test/resource_provider_meta_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package test - -import ( - "strings" - "testing" - - "github.com/hashicorp/terraform/helper/resource" -) - -func TestResourceProviderMeta_basic(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -terraform { - provider_meta "test" { - foo = "bar" - } -} - -resource "test_resource_provider_meta" "foo" { -} - `), - }, - }, - }) -} diff --git a/builtin/providers/test/resource_required_min_test.go b/builtin/providers/test/resource_required_min_test.go deleted file mode 100644 index 180c28ccf..000000000 --- a/builtin/providers/test/resource_required_min_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package test - -import ( - "regexp" - "strings" - "testing" - - "github.com/hashicorp/terraform/helper/resource" -) - -func TestResource_dynamicRequiredMinItems(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: ` -resource "test_resource_required_min" "a" { -} -`, - ExpectError: regexp.MustCompile(`"required_min_items" blocks are required`), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list" "a" { - dependent_list { - val = "a" - } -} - -resource "test_resource_required_min" "b" { - dynamic "required_min_items" { - for_each = test_resource_list.a.computed_list - content { - val = required_min_items.value - } - } -} - `), - ExpectError: regexp.MustCompile(`required_min_items: attribute supports 2 item as a minimum`), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_list" "c" { - dependent_list { - val = "a" - } - - dependent_list { - val = "b" - } -} - -resource "test_resource_required_min" "b" { - dynamic "required_min_items" { - for_each = test_resource_list.c.computed_list - content { - val = required_min_items.value - } - } -} - `), - }, - }, - }) -} diff --git a/builtin/providers/test/resource_state_func_test.go b/builtin/providers/test/resource_state_func_test.go deleted file mode 100644 index cf5726eea..000000000 --- a/builtin/providers/test/resource_state_func_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package test - -import ( - "strings" - "testing" - - "github.com/hashicorp/terraform/helper/resource" -) - -func TestResourceStateFunc_basic(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_state_func" "foo" { -} - `), - Check: resource.TestCheckNoResourceAttr("test_resource_state_func.foo", "state_func"), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_state_func" "foo" { - state_func = "data" - state_func_value = "data" -} - `), - Check: resource.TestCheckResourceAttr("test_resource_state_func.foo", "state_func", stateFuncHash("data")), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_state_func" "foo" { -} - `), - Check: resource.TestCheckNoResourceAttr("test_resource_state_func.foo", "state_func"), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_state_func" "foo" { - optional = "added" - state_func = "data" - state_func_value = "data" -} - `), - Check: resource.TestCheckResourceAttr("test_resource_state_func.foo", "state_func", stateFuncHash("data")), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_state_func" "foo" { - optional = "added" - state_func = "changed" - state_func_value = "changed" -} - `), - Check: resource.TestCheckResourceAttr("test_resource_state_func.foo", "state_func", stateFuncHash("changed")), - }, - }, - }) -} - -func TestResourceStateFunc_getOkSetElem(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_state_func" "foo" { -} - -resource "test_resource_state_func" "bar" { - set_block { - required = "foo" - optional = test_resource_state_func.foo.id - } - set_block { - required = test_resource_state_func.foo.id - } -} - `), - }, - }, - }) -} diff --git a/builtin/providers/test/resource_test.go b/builtin/providers/test/resource_test.go deleted file mode 100644 index 510b7816c..000000000 --- a/builtin/providers/test/resource_test.go +++ /dev/null @@ -1,1220 +0,0 @@ -package test - -import ( - "reflect" - "regexp" - "strings" - "testing" - - "github.com/hashicorp/terraform/addrs" - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/terraform" -) - -func TestResource_basic(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckNoResourceAttr( - "test_resource.foo", "list.#", - ), - ), - }, - }, - }) -} - -func TestResource_changedList(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - { - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckNoResourceAttr( - "test_resource.foo", "list.#", - ), - ), - }, - { - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - list = ["a"] -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource.foo", "list.#", "1", - ), - resource.TestCheckResourceAttr( - "test_resource.foo", "list.0", "a", - ), - ), - }, - { - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - list = ["a", "b"] -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource.foo", "list.#", "2", - ), - resource.TestCheckResourceAttr( - "test_resource.foo", "list.0", "a", - ), - resource.TestCheckResourceAttr( - "test_resource.foo", "list.1", "b", - ), - ), - }, - { - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - list = ["b"] -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource.foo", "list.#", "1", - ), - resource.TestCheckResourceAttr( - "test_resource.foo", "list.0", "b", - ), - ), - }, - }, - }) -} - -// Targeted test in TestContext2Apply_ignoreChangesCreate -func TestResource_ignoreChangesRequired(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - lifecycle { - ignore_changes = ["required"] - } -} - `), - Check: func(s *terraform.State) error { - return nil - }, - }, - }, - }) -} - -func TestResource_ignoreChangesEmpty(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - optional_force_new = "one" - lifecycle { - ignore_changes = [] - } -} - `), - Check: func(s *terraform.State) error { - return nil - }, - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - optional_force_new = "two" - lifecycle { - ignore_changes = [] - } -} - `), - Check: func(s *terraform.State) error { - return nil - }, - }, - }, - }) -} - -func TestResource_ignoreChangesForceNew(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - optional_force_new = "one" - lifecycle { - ignore_changes = ["optional_force_new"] - } -} - `), - Check: func(s *terraform.State) error { - return nil - }, - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - optional_force_new = "two" - lifecycle { - ignore_changes = ["optional_force_new"] - } -} - `), - Check: func(s *terraform.State) error { - return nil - }, - }, - }, - }) -} - -// Covers specific scenario in #6005, handled by normalizing boolean strings in -// helper/schema -func TestResource_ignoreChangesForceNewBoolean(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - optional_force_new = "one" - optional_bool = true - lifecycle { - ignore_changes = ["optional_force_new"] - } -} - `), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - optional_force_new = "two" - optional_bool = true - lifecycle { - ignore_changes = ["optional_force_new"] - } -} - `), - Check: func(s *terraform.State) error { - return nil - }, - }, - }, - }) -} - -func TestResource_ignoreChangesMap(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - optional_computed_map = { - foo = "bar" - } - lifecycle { - ignore_changes = ["optional_computed_map"] - } -} - `), - Check: func(s *terraform.State) error { - return nil - }, - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - optional_computed_map = { - foo = "bar" - no = "update" - } - lifecycle { - ignore_changes = ["optional_computed_map"] - } -} - `), - Check: func(s *terraform.State) error { - return nil - }, - }, - }, - }) -} - -func TestResource_ignoreChangesDependent(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - count = 2 - required = "yep" - required_map = { - key = "value" - } - - optional_force_new = "one" - lifecycle { - ignore_changes = ["optional_force_new"] - } -} -resource "test_resource" "bar" { - count = 2 - required = "yep" - required_map = { - key = "value" - } - optional = "${element(test_resource.foo.*.id, count.index)}" -} - `), - Check: func(s *terraform.State) error { - return nil - }, - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - count = 2 - required = "yep" - required_map = { - key = "value" - } - - optional_force_new = "two" - lifecycle { - ignore_changes = ["optional_force_new"] - } -} -resource "test_resource" "bar" { - count = 2 - required = "yep" - required_map = { - key = "value" - } - optional = "${element(test_resource.foo.*.id, count.index)}" -} - `), - Check: func(s *terraform.State) error { - return nil - }, - }, - }, - }) -} - -func TestResource_ignoreChangesStillReplaced(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - optional_force_new = "one" - optional_bool = true - lifecycle { - ignore_changes = ["optional_bool"] - } -} - `), - Check: func(s *terraform.State) error { - return nil - }, - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - optional_force_new = "two" - optional_bool = false - lifecycle { - ignore_changes = ["optional_bool"] - } -} - `), - Check: func(s *terraform.State) error { - return nil - }, - }, - }, - }) -} - -func TestResource_ignoreChangesCustomizeDiff(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - optional = "a" - lifecycle { - ignore_changes = [optional] - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource.foo", "planned_computed", "a", - ), - ), - }, - // On this step, `optional` changes, but `planned_computed` - // should remain as "a" because we have set `ignore_changes` - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - optional = "b" - lifecycle { - ignore_changes = [optional] - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource.foo", "planned_computed", "a", - ), - ), - }, - }, - }) -} - -// Reproduces plan-time panic when the wrong type is interpolated in a list of -// maps. -// TODO: this should return a type error, rather than silently setting an empty -// list -func TestResource_dataSourceListMapPanic(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "val" - required_map = {x = "y"} - list_of_map = "${var.maplist}" -} - -variable "maplist" { - type = "list" - - default = [ - {a = "b"} - ] -} - `), - ExpectError: nil, - Check: func(s *terraform.State) error { - return nil - }, - }, - }, - }) -} - -func TestResource_dataSourceIndexMapList(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "val" - - required_map = { - x = "y" - } - - list_of_map = [ - { - a = "1" - b = "2" - }, - { - c = "3" - d = "4" - }, - ] -} - -output "map_from_list" { - value = "${test_resource.foo.list_of_map[0]}" -} - -output "value_from_map_from_list" { - value = "${lookup(test_resource.foo.list_of_map[1], "d")}" -} - `), - ExpectError: nil, - Check: func(s *terraform.State) error { - root := s.ModuleByPath(addrs.RootModuleInstance) - mapOut := root.Outputs["map_from_list"].Value - expectedMapOut := map[string]interface{}{ - "a": "1", - "b": "2", - } - - valueOut := root.Outputs["value_from_map_from_list"].Value - expectedValueOut := "4" - - if !reflect.DeepEqual(mapOut, expectedMapOut) { - t.Fatalf("Expected: %#v\nGot: %#v", expectedMapOut, mapOut) - } - if !reflect.DeepEqual(valueOut, expectedValueOut) { - t.Fatalf("Expected: %#v\nGot: %#v", valueOut, expectedValueOut) - } - return nil - }, - }, - }, - }) -} - -func testAccCheckResourceDestroy(s *terraform.State) error { - return nil -} - -func TestResource_removeForceNew(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - optional_force_new = "here" -} - `), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } -} - `), - }, - }, - }) -} - -func TestResource_unknownFuncInMap(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "ok" - required_map = { - key = "${uuid()}" - } -} - `), - ExpectNonEmptyPlan: true, - }, - }, - }) -} - -// Verify that we can destroy when a managed resource references something with -// a count of 1. -func TestResource_countRefDestroyError(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: strings.TrimSpace(` -resource "test_resource" "one" { - count = 1 - required = "ok" - required_map = { - key = "val" - } -} - -resource "test_resource" "two" { - required = test_resource.one[0].id - required_map = { - key = "val" - } -} - `), - }, - }, - }) -} - -func TestResource_emptyMapValue(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "ok" - required_map = { - a = "a" - b = "" - } -} - `), - }, - }, - }) -} - -func TestResource_updateError(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "first" - required_map = { - a = "a" - } -} -`), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "second" - required_map = { - a = "a" - } - apply_error = "update_error" -} -`), - ExpectError: regexp.MustCompile("update_error"), - }, - }, - }) -} - -func TestResource_applyError(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "second" - required_map = { - a = "a" - } - apply_error = "apply_error" -} -`), - ExpectError: regexp.MustCompile("apply_error"), - }, - }, - }) -} - -func TestResource_emptyStrings(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "second" - required_map = { - a = "a" - } - - list = [""] -} -`), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource.foo", "list.0", ""), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "second" - required_map = { - a = "a" - } - - list = ["", "b"] -} -`), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource.foo", "list.0", ""), - resource.TestCheckResourceAttr("test_resource.foo", "list.1", "b"), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "second" - required_map = { - a = "a" - } - - list = [""] -} -`), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource.foo", "list.0", ""), - ), - }, - }, - }) -} - -func TestResource_setDrift(t *testing.T) { - testProvider := testAccProviders["test"] - res := testProvider.(*schema.Provider).ResourcesMap["test_resource"] - - // reset the Read function after the test - defer func() { - res.Read = testResourceRead - }() - - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "first" - required_map = { - a = "a" - } - set = ["a", "b"] -} -`), - Check: func(s *terraform.State) error { - return nil - }, - }, - resource.TestStep{ - PreConfig: func() { - // update the Read function to return the wrong "set" attribute values. - res.Read = func(d *schema.ResourceData, meta interface{}) error { - // update as expected first - if err := testResourceRead(d, meta); err != nil { - return err - } - d.Set("set", []interface{}{"a", "x"}) - return nil - } - }, - // Leave the config, so we can detect the mismatched set values. - // Updating the config would force the test to pass even if the Read - // function values were ignored. - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "second" - required_map = { - a = "a" - } - set = ["a", "b"] -} -`), - ExpectNonEmptyPlan: true, - }, - }, - }) -} - -func TestResource_optionalComputedMap(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - optional_computed_map = { - foo = "bar" - baz = "" - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource.foo", "optional_computed_map.foo", "bar", - ), - resource.TestCheckResourceAttr( - "test_resource.foo", "optional_computed_map.baz", "", - ), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - optional_computed_map = {} -} - `), - // removing the map from the config should still leave an empty computed map - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource.foo", "optional_computed_map.%", "0", - ), - ), - }, - }, - }) -} - -func TestResource_plannedComputed(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "ok" - required_map = { - key = "value" - } - optional = "hi" -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource.foo", "planned_computed", "hi", - ), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "ok" - required_map = { - key = "value" - } - optional = "changed" -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource.foo", "planned_computed", "changed", - ), - ), - }, - }, - }) -} - -func TestDiffApply_map(t *testing.T) { - resSchema := map[string]*schema.Schema{ - "map": { - Type: schema.TypeMap, - Optional: true, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - } - - priorAttrs := map[string]string{ - "id": "ok", - "map.%": "2", - "map.foo": "bar", - "map.bar": "", - } - - diff := &terraform.InstanceDiff{ - Attributes: map[string]*terraform.ResourceAttrDiff{ - "map.foo": &terraform.ResourceAttrDiff{Old: "bar", New: "", NewRemoved: true}, - "map.bar": &terraform.ResourceAttrDiff{Old: "", New: "", NewRemoved: true}, - }, - } - - newAttrs, err := diff.Apply(priorAttrs, (&schema.Resource{Schema: resSchema}).CoreConfigSchema()) - if err != nil { - t.Fatal(err) - } - - expect := map[string]string{ - "id": "ok", - "map.%": "0", - } - - if !reflect.DeepEqual(newAttrs, expect) { - t.Fatalf("expected:%#v got:%#v", expect, newAttrs) - } -} - -func TestResource_dependsComputed(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -variable "change" { - default = false -} - -resource "test_resource" "foo" { - required = "ok" - required_map = { - key = "value" - } - optional = var.change ? "after" : "" -} - -resource "test_resource" "bar" { - count = var.change ? 1 : 0 - required = test_resource.foo.planned_computed - required_map = { - key = "value" - } -} - `), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -variable "change" { - default = true -} - -resource "test_resource" "foo" { - required = "ok" - required_map = { - key = "value" - } - optional = var.change ? "after" : "" -} - -resource "test_resource" "bar" { - count = var.change ? 1 : 0 - required = test_resource.foo.planned_computed - required_map = { - key = "value" - } -} - `), - }, - }, - }) -} - -func TestResource_optionalComputedBool(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } -} - `), - }, - }, - }) -} - -func TestResource_replacedOptionalComputed(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested" "a" { -} - -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - optional_computed = test_resource_nested.a.id -} - `), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_nested" "b" { -} - -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - optional_computed = test_resource_nested.b.id -} - `), - }, - }, - }) -} - -func TestResource_floatInIntAttr(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - int = 40.2 -} - `), - ExpectError: regexp.MustCompile(`must be a whole number, got 40.2`), - }, - }, - }) -} - -func TestResource_unsetNil(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - optional = "a" -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource.foo", "optional", "a"), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource.foo", "optional", ""), - ), - }, - }, - }) -} - -// Verify we can use use numeric indices in `ignore_changes` paths. -func TestResource_ignoreChangesIndex(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - list_of_map = [ - { - a = "b" - } - ] - - lifecycle { - ignore_changes = [list_of_map[0]["a"]] - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource.foo", "list_of_map.0.a", "b", - ), - ), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - list_of_map = [ - { - a = "c" - } - ] - - lifecycle { - ignore_changes = [list_of_map[0]["a"]] - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource.foo", "list_of_map.0.a", "b", - ), - ), - }, - // set ignore_changes to a prefix of the changed value - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource" "foo" { - required = "yep" - required_map = { - key = "value" - } - list_of_map = [ - { - a = "d" - } - ] - - lifecycle { - ignore_changes = [list_of_map[0]] - } -} - `), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "test_resource.foo", "list_of_map.0.a", "b", - ), - ), - }, - }, - }) -} diff --git a/builtin/providers/test/resource_timeout_test.go b/builtin/providers/test/resource_timeout_test.go deleted file mode 100644 index 312a37a78..000000000 --- a/builtin/providers/test/resource_timeout_test.go +++ /dev/null @@ -1,158 +0,0 @@ -package test - -import ( - "regexp" - "strings" - "testing" - - "github.com/hashicorp/terraform/helper/resource" -) - -func TestResourceTimeout_create(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_timeout" "foo" { - create_delay = "2s" - timeouts { - create = "1s" - } -} - `), - ExpectError: regexp.MustCompile("timeout while creating resource"), - }, - }, - }) -} - -// start with the default, then modify it -func TestResourceTimeout_defaults(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_timeout" "foo" { - update_delay = "1ms" -} - `), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_timeout" "foo" { - update_delay = "2ms" - timeouts { - update = "3s" - } -} - `), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_timeout" "foo" { - update_delay = "2s" - delete_delay = "2s" - timeouts { - delete = "3s" - update = "3s" - } -} - `), - }, - // delete "foo" - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_timeout" "bar" { -} - `), - }, - }, - }) -} - -func TestResourceTimeout_delete(t *testing.T) { - // If the delete timeout isn't saved until destroy, the cleanup here will - // fail because the default is only 20m. - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_timeout" "foo" { - delete_delay = "25m" - timeouts { - delete = "30m" - } -} - `), - }, - }, - }) -} -func TestResourceTimeout_update(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_timeout" "foo" { - update_delay = "1s" - timeouts { - update = "1s" - } -} - `), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_timeout" "foo" { - update_delay = "2s" - timeouts { - update = "1s" - } -} - `), - ExpectError: regexp.MustCompile("timeout while updating resource"), - }, - }, - }) -} - -func TestResourceTimeout_read(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - CheckDestroy: testAccCheckResourceDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_timeout" "foo" { -} - `), - }, - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_timeout" "foo" { - read_delay = "30m" -} - `), - ExpectError: regexp.MustCompile("timeout while reading resource"), - }, - // we need to remove the read_delay so that the resource can be - // destroyed in the final step, but expect an error here from the - // pre-existing delay. - resource.TestStep{ - Config: strings.TrimSpace(` -resource "test_resource_timeout" "foo" { -} - `), - ExpectError: regexp.MustCompile("timeout while reading resource"), - }, - }, - }) -} diff --git a/builtin/providers/test/resource_with_custom_diff_test.go b/builtin/providers/test/resource_with_custom_diff_test.go deleted file mode 100644 index 05982bec9..000000000 --- a/builtin/providers/test/resource_with_custom_diff_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package test - -import ( - "fmt" - "regexp" - "testing" - - "github.com/hashicorp/terraform/helper/resource" -) - -// TestResourceWithCustomDiff test custom diff behaviour. -func TestResourceWithCustomDiff(t *testing.T) { - resource.UnitTest(t, resource.TestCase{ - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: resourceWithCustomDiffConfig(false), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "computed", "1"), - resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "index", "1"), - resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "list.#", "1"), - resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "list.0", "dc1"), - ), - ExpectNonEmptyPlan: true, - }, - { - Config: resourceWithCustomDiffConfig(false), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "computed", "2"), - resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "index", "2"), - resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "list.#", "2"), - resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "list.0", "dc2"), - resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "list.1", "dc3"), - resource.TestCheckNoResourceAttr("test_resource_with_custom_diff.foo", "list.2"), - ), - ExpectNonEmptyPlan: true, - }, - { - Config: resourceWithCustomDiffConfig(true), - ExpectError: regexp.MustCompile("veto is true, diff vetoed"), - }, - }, - }) -} - -func resourceWithCustomDiffConfig(veto bool) string { - return fmt.Sprintf(` -resource "test_resource_with_custom_diff" "foo" { - required = "yep" - veto = %t -} -`, veto) -} diff --git a/builtin/provisioners/file/resource_provisioner.go b/builtin/provisioners/file/resource_provisioner.go index 26f2f4daf..61941e978 100644 --- a/builtin/provisioners/file/resource_provisioner.go +++ b/builtin/provisioners/file/resource_provisioner.go @@ -7,8 +7,8 @@ import ( "os" "github.com/hashicorp/terraform/communicator" - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/terraform" "github.com/mitchellh/go-homedir" ) diff --git a/builtin/provisioners/file/resource_provisioner_test.go b/builtin/provisioners/file/resource_provisioner_test.go index c7e34c0ab..f56f7641c 100644 --- a/builtin/provisioners/file/resource_provisioner_test.go +++ b/builtin/provisioners/file/resource_provisioner_test.go @@ -4,8 +4,8 @@ import ( "testing" "github.com/hashicorp/terraform/configs/hcl2shim" - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) func TestResourceProvisioner_impl(t *testing.T) { diff --git a/builtin/provisioners/local-exec/resource_provisioner.go b/builtin/provisioners/local-exec/resource_provisioner.go index f0ba28f34..12c69161f 100644 --- a/builtin/provisioners/local-exec/resource_provisioner.go +++ b/builtin/provisioners/local-exec/resource_provisioner.go @@ -9,8 +9,8 @@ import ( "runtime" "github.com/armon/circbuf" - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/terraform" "github.com/mitchellh/go-linereader" ) diff --git a/builtin/provisioners/local-exec/resource_provisioner_test.go b/builtin/provisioners/local-exec/resource_provisioner_test.go index 8718d4dab..32ccfa7e6 100644 --- a/builtin/provisioners/local-exec/resource_provisioner_test.go +++ b/builtin/provisioners/local-exec/resource_provisioner_test.go @@ -7,8 +7,8 @@ import ( "testing" "time" - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) func TestResourceProvisioner_impl(t *testing.T) { diff --git a/builtin/provisioners/remote-exec/resource_provisioner.go b/builtin/provisioners/remote-exec/resource_provisioner.go index 50042977e..7e74d1ce9 100644 --- a/builtin/provisioners/remote-exec/resource_provisioner.go +++ b/builtin/provisioners/remote-exec/resource_provisioner.go @@ -13,8 +13,8 @@ import ( "github.com/hashicorp/terraform/communicator" "github.com/hashicorp/terraform/communicator/remote" - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/terraform" "github.com/mitchellh/go-linereader" ) diff --git a/builtin/provisioners/remote-exec/resource_provisioner_test.go b/builtin/provisioners/remote-exec/resource_provisioner_test.go index cb865a8e3..014bb6410 100644 --- a/builtin/provisioners/remote-exec/resource_provisioner_test.go +++ b/builtin/provisioners/remote-exec/resource_provisioner_test.go @@ -11,8 +11,8 @@ import ( "github.com/hashicorp/terraform/communicator" "github.com/hashicorp/terraform/communicator/remote" - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) func TestResourceProvisioner_impl(t *testing.T) { diff --git a/command/e2etest/provider_dev_test.go b/command/e2etest/provider_dev_test.go index a31971535..ba8f9c61c 100644 --- a/command/e2etest/provider_dev_test.go +++ b/command/e2etest/provider_dev_test.go @@ -33,7 +33,7 @@ func TestProviderDevOverrides(t *testing.T) { // such as if it stops being buildable into an independent executable. providerExeDir := filepath.Join(tf.WorkDir(), "pkgdir") providerExePrefix := filepath.Join(providerExeDir, "terraform-provider-test_") - providerExe := e2e.GoBuild("github.com/hashicorp/terraform/builtin/bins/provider-test", providerExePrefix) + providerExe := e2e.GoBuild("github.com/hashicorp/terraform/internal/legacy/builtin/bins/provider-test", providerExePrefix) t.Logf("temporary provider executable is %s", providerExe) err := ioutil.WriteFile(filepath.Join(tf.WorkDir(), "dev.tfrc"), []byte(fmt.Sprintf(` diff --git a/command/e2etest/unmanaged_test.go b/command/e2etest/unmanaged_test.go index ab8e19aa1..0dd262b3b 100644 --- a/command/e2etest/unmanaged_test.go +++ b/command/e2etest/unmanaged_test.go @@ -11,9 +11,9 @@ import ( "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin" - "github.com/hashicorp/terraform/builtin/providers/test" "github.com/hashicorp/terraform/e2e" - grpcplugin "github.com/hashicorp/terraform/helper/plugin" + "github.com/hashicorp/terraform/internal/legacy/builtin/providers/test" + grpcplugin "github.com/hashicorp/terraform/internal/legacy/helper/plugin" proto "github.com/hashicorp/terraform/internal/tfplugin5" tfplugin "github.com/hashicorp/terraform/plugin" ) diff --git a/communicator/communicator.go b/communicator/communicator.go index 12b725b32..f3ca660f5 100644 --- a/communicator/communicator.go +++ b/communicator/communicator.go @@ -11,7 +11,7 @@ import ( "github.com/hashicorp/terraform/communicator/remote" "github.com/hashicorp/terraform/communicator/ssh" "github.com/hashicorp/terraform/communicator/winrm" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) // Communicator is an interface that must be implemented by all communicators diff --git a/communicator/communicator_mock.go b/communicator/communicator_mock.go index 49304a070..9207b9438 100644 --- a/communicator/communicator_mock.go +++ b/communicator/communicator_mock.go @@ -8,7 +8,7 @@ import ( "time" "github.com/hashicorp/terraform/communicator/remote" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) // MockCommunicator is an implementation of Communicator that can be used for tests. diff --git a/communicator/communicator_test.go b/communicator/communicator_test.go index e20d0368b..ba2383b8b 100644 --- a/communicator/communicator_test.go +++ b/communicator/communicator_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) func TestCommunicator_new(t *testing.T) { diff --git a/communicator/ssh/communicator.go b/communicator/ssh/communicator.go index 2427194bd..3e9c931e2 100644 --- a/communicator/ssh/communicator.go +++ b/communicator/ssh/communicator.go @@ -20,7 +20,7 @@ import ( "github.com/hashicorp/errwrap" "github.com/hashicorp/terraform/communicator/remote" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" ) diff --git a/communicator/ssh/communicator_test.go b/communicator/ssh/communicator_test.go index 445fbebb9..99bee1439 100644 --- a/communicator/ssh/communicator_test.go +++ b/communicator/ssh/communicator_test.go @@ -20,7 +20,7 @@ import ( "time" "github.com/hashicorp/terraform/communicator/remote" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" "golang.org/x/crypto/ssh" ) diff --git a/communicator/ssh/provisioner.go b/communicator/ssh/provisioner.go index 63a753c1a..ad16053c1 100644 --- a/communicator/ssh/provisioner.go +++ b/communicator/ssh/provisioner.go @@ -14,7 +14,7 @@ import ( "time" "github.com/hashicorp/terraform/communicator/shared" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" "github.com/mitchellh/mapstructure" sshagent "github.com/xanzy/ssh-agent" "golang.org/x/crypto/ssh" diff --git a/communicator/ssh/provisioner_test.go b/communicator/ssh/provisioner_test.go index 959831544..99f06d920 100644 --- a/communicator/ssh/provisioner_test.go +++ b/communicator/ssh/provisioner_test.go @@ -3,7 +3,7 @@ package ssh import ( "testing" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) func TestProvisioner_connInfo(t *testing.T) { diff --git a/communicator/winrm/communicator.go b/communicator/winrm/communicator.go index 827344917..1489d514c 100644 --- a/communicator/winrm/communicator.go +++ b/communicator/winrm/communicator.go @@ -10,7 +10,7 @@ import ( "time" "github.com/hashicorp/terraform/communicator/remote" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" "github.com/masterzen/winrm" "github.com/packer-community/winrmcp/winrmcp" ) diff --git a/communicator/winrm/communicator_test.go b/communicator/winrm/communicator_test.go index f6a049932..62d4fab55 100644 --- a/communicator/winrm/communicator_test.go +++ b/communicator/winrm/communicator_test.go @@ -9,7 +9,7 @@ import ( "github.com/dylanmei/winrmtest" "github.com/hashicorp/terraform/communicator/remote" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) func newMockWinRMServer(t *testing.T) *winrmtest.Remote { diff --git a/communicator/winrm/provisioner.go b/communicator/winrm/provisioner.go index 5cef1309d..df3ed89a4 100644 --- a/communicator/winrm/provisioner.go +++ b/communicator/winrm/provisioner.go @@ -8,7 +8,7 @@ import ( "time" "github.com/hashicorp/terraform/communicator/shared" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" "github.com/mitchellh/mapstructure" ) diff --git a/communicator/winrm/provisioner_test.go b/communicator/winrm/provisioner_test.go index fbc45c34b..40f59c55f 100644 --- a/communicator/winrm/provisioner_test.go +++ b/communicator/winrm/provisioner_test.go @@ -3,7 +3,7 @@ package winrm import ( "testing" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) func TestProvisioner_defaultHTTPSPort(t *testing.T) { diff --git a/helper/resource/error.go b/helper/resource/error.go deleted file mode 100644 index 7ee21614b..000000000 --- a/helper/resource/error.go +++ /dev/null @@ -1,79 +0,0 @@ -package resource - -import ( - "fmt" - "strings" - "time" -) - -type NotFoundError struct { - LastError error - LastRequest interface{} - LastResponse interface{} - Message string - Retries int -} - -func (e *NotFoundError) Error() string { - if e.Message != "" { - return e.Message - } - - if e.Retries > 0 { - return fmt.Sprintf("couldn't find resource (%d retries)", e.Retries) - } - - return "couldn't find resource" -} - -// UnexpectedStateError is returned when Refresh returns a state that's neither in Target nor Pending -type UnexpectedStateError struct { - LastError error - State string - ExpectedState []string -} - -func (e *UnexpectedStateError) Error() string { - return fmt.Sprintf( - "unexpected state '%s', wanted target '%s'. last error: %s", - e.State, - strings.Join(e.ExpectedState, ", "), - e.LastError, - ) -} - -// TimeoutError is returned when WaitForState times out -type TimeoutError struct { - LastError error - LastState string - Timeout time.Duration - ExpectedState []string -} - -func (e *TimeoutError) Error() string { - expectedState := "resource to be gone" - if len(e.ExpectedState) > 0 { - expectedState = fmt.Sprintf("state to become '%s'", strings.Join(e.ExpectedState, ", ")) - } - - extraInfo := make([]string, 0) - if e.LastState != "" { - extraInfo = append(extraInfo, fmt.Sprintf("last state: '%s'", e.LastState)) - } - if e.Timeout > 0 { - extraInfo = append(extraInfo, fmt.Sprintf("timeout: %s", e.Timeout.String())) - } - - suffix := "" - if len(extraInfo) > 0 { - suffix = fmt.Sprintf(" (%s)", strings.Join(extraInfo, ", ")) - } - - if e.LastError != nil { - return fmt.Sprintf("timeout while waiting for %s%s: %s", - expectedState, suffix, e.LastError) - } - - return fmt.Sprintf("timeout while waiting for %s%s", - expectedState, suffix) -} diff --git a/helper/resource/grpc_test_provider.go b/helper/resource/grpc_test_provider.go deleted file mode 100644 index 0742e993b..000000000 --- a/helper/resource/grpc_test_provider.go +++ /dev/null @@ -1,43 +0,0 @@ -package resource - -import ( - "context" - "net" - "time" - - "github.com/hashicorp/terraform/helper/plugin" - proto "github.com/hashicorp/terraform/internal/tfplugin5" - tfplugin "github.com/hashicorp/terraform/plugin" - "github.com/hashicorp/terraform/providers" - "github.com/hashicorp/terraform/terraform" - "google.golang.org/grpc" - "google.golang.org/grpc/test/bufconn" -) - -// GRPCTestProvider takes a legacy ResourceProvider, wraps it in the new GRPC -// shim and starts it in a grpc server using an inmem connection. It returns a -// GRPCClient for this new server to test the shimmed resource provider. -func GRPCTestProvider(rp terraform.ResourceProvider) providers.Interface { - listener := bufconn.Listen(256 * 1024) - grpcServer := grpc.NewServer() - - p := plugin.NewGRPCProviderServerShim(rp) - proto.RegisterProviderServer(grpcServer, p) - - go grpcServer.Serve(listener) - - conn, err := grpc.Dial("", grpc.WithDialer(func(string, time.Duration) (net.Conn, error) { - return listener.Dial() - }), grpc.WithInsecure()) - if err != nil { - panic(err) - } - - var pp tfplugin.GRPCProviderPlugin - client, _ := pp.GRPCClient(context.Background(), nil, conn) - - grpcClient := client.(*tfplugin.GRPCProvider) - grpcClient.TestServer = grpcServer - - return grpcClient -} diff --git a/helper/resource/id.go b/helper/resource/id.go deleted file mode 100644 index 44949550e..000000000 --- a/helper/resource/id.go +++ /dev/null @@ -1,45 +0,0 @@ -package resource - -import ( - "fmt" - "strings" - "sync" - "time" -) - -const UniqueIdPrefix = `terraform-` - -// idCounter is a monotonic counter for generating ordered unique ids. -var idMutex sync.Mutex -var idCounter uint32 - -// Helper for a resource to generate a unique identifier w/ default prefix -func UniqueId() string { - return PrefixedUniqueId(UniqueIdPrefix) -} - -// UniqueIDSuffixLength is the string length of the suffix generated by -// PrefixedUniqueId. This can be used by length validation functions to -// ensure prefixes are the correct length for the target field. -const UniqueIDSuffixLength = 26 - -// Helper for a resource to generate a unique identifier w/ given prefix -// -// After the prefix, the ID consists of an incrementing 26 digit value (to match -// previous timestamp output). After the prefix, the ID consists of a timestamp -// and an incrementing 8 hex digit value The timestamp means that multiple IDs -// created with the same prefix will sort in the order of their creation, even -// across multiple terraform executions, as long as the clock is not turned back -// between calls, and as long as any given terraform execution generates fewer -// than 4 billion IDs. -func PrefixedUniqueId(prefix string) string { - // Be precise to 4 digits of fractional seconds, but remove the dot before the - // fractional seconds. - timestamp := strings.Replace( - time.Now().UTC().Format("20060102150405.0000"), ".", "", 1) - - idMutex.Lock() - defer idMutex.Unlock() - idCounter++ - return fmt.Sprintf("%s%s%08x", prefix, timestamp, idCounter) -} diff --git a/helper/resource/id_test.go b/helper/resource/id_test.go deleted file mode 100644 index f1560dab1..000000000 --- a/helper/resource/id_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package resource - -import ( - "regexp" - "strings" - "testing" - "time" -) - -var allDigits = regexp.MustCompile(`^\d+$`) -var allHex = regexp.MustCompile(`^[a-f0-9]+$`) - -func TestUniqueId(t *testing.T) { - split := func(rest string) (timestamp, increment string) { - return rest[:18], rest[18:] - } - - iterations := 10000 - ids := make(map[string]struct{}) - var id, lastId string - for i := 0; i < iterations; i++ { - id = UniqueId() - - if _, ok := ids[id]; ok { - t.Fatalf("Got duplicated id! %s", id) - } - - if !strings.HasPrefix(id, UniqueIdPrefix) { - t.Fatalf("Unique ID didn't have terraform- prefix! %s", id) - } - - rest := strings.TrimPrefix(id, UniqueIdPrefix) - - if len(rest) != UniqueIDSuffixLength { - t.Fatalf("PrefixedUniqueId is out of sync with UniqueIDSuffixLength, post-prefix part has wrong length! %s", rest) - } - - timestamp, increment := split(rest) - - if !allDigits.MatchString(timestamp) { - t.Fatalf("Timestamp not all digits! %s", timestamp) - } - - if !allHex.MatchString(increment) { - t.Fatalf("Increment part not all hex! %s", increment) - } - - if lastId != "" && lastId >= id { - t.Fatalf("IDs not ordered! %s vs %s", lastId, id) - } - - ids[id] = struct{}{} - lastId = id - } - - id1 := UniqueId() - time.Sleep(time.Millisecond) - id2 := UniqueId() - timestamp1, _ := split(strings.TrimPrefix(id1, UniqueIdPrefix)) - timestamp2, _ := split(strings.TrimPrefix(id2, UniqueIdPrefix)) - - if timestamp1 == timestamp2 { - t.Fatalf("Timestamp part should update at least once a millisecond %s %s", - id1, id2) - } -} diff --git a/helper/resource/state.go b/helper/resource/state.go deleted file mode 100644 index 88a839664..000000000 --- a/helper/resource/state.go +++ /dev/null @@ -1,259 +0,0 @@ -package resource - -import ( - "log" - "time" -) - -var refreshGracePeriod = 30 * time.Second - -// StateRefreshFunc is a function type used for StateChangeConf that is -// responsible for refreshing the item being watched for a state change. -// -// It returns three results. `result` is any object that will be returned -// as the final object after waiting for state change. This allows you to -// return the final updated object, for example an EC2 instance after refreshing -// it. -// -// `state` is the latest state of that object. And `err` is any error that -// may have happened while refreshing the state. -type StateRefreshFunc func() (result interface{}, state string, err error) - -// StateChangeConf is the configuration struct used for `WaitForState`. -type StateChangeConf struct { - Delay time.Duration // Wait this time before starting checks - Pending []string // States that are "allowed" and will continue trying - Refresh StateRefreshFunc // Refreshes the current state - Target []string // Target state - Timeout time.Duration // The amount of time to wait before timeout - MinTimeout time.Duration // Smallest time to wait before refreshes - PollInterval time.Duration // Override MinTimeout/backoff and only poll this often - NotFoundChecks int // Number of times to allow not found - - // This is to work around inconsistent APIs - ContinuousTargetOccurence int // Number of times the Target state has to occur continuously -} - -// WaitForState watches an object and waits for it to achieve the state -// specified in the configuration using the specified Refresh() func, -// waiting the number of seconds specified in the timeout configuration. -// -// If the Refresh function returns an error, exit immediately with that error. -// -// If the Refresh function returns a state other than the Target state or one -// listed in Pending, return immediately with an error. -// -// If the Timeout is exceeded before reaching the Target state, return an -// error. -// -// Otherwise, the result is the result of the first call to the Refresh function to -// reach the target state. -func (conf *StateChangeConf) WaitForState() (interface{}, error) { - log.Printf("[DEBUG] Waiting for state to become: %s", conf.Target) - - notfoundTick := 0 - targetOccurence := 0 - - // Set a default for times to check for not found - if conf.NotFoundChecks == 0 { - conf.NotFoundChecks = 20 - } - - if conf.ContinuousTargetOccurence == 0 { - conf.ContinuousTargetOccurence = 1 - } - - type Result struct { - Result interface{} - State string - Error error - Done bool - } - - // Read every result from the refresh loop, waiting for a positive result.Done. - resCh := make(chan Result, 1) - // cancellation channel for the refresh loop - cancelCh := make(chan struct{}) - - result := Result{} - - go func() { - defer close(resCh) - - time.Sleep(conf.Delay) - - // start with 0 delay for the first loop - var wait time.Duration - - for { - // store the last result - resCh <- result - - // wait and watch for cancellation - select { - case <-cancelCh: - return - case <-time.After(wait): - // first round had no wait - if wait == 0 { - wait = 100 * time.Millisecond - } - } - - res, currentState, err := conf.Refresh() - result = Result{ - Result: res, - State: currentState, - Error: err, - } - - if err != nil { - resCh <- result - return - } - - // If we're waiting for the absence of a thing, then return - if res == nil && len(conf.Target) == 0 { - targetOccurence++ - if conf.ContinuousTargetOccurence == targetOccurence { - result.Done = true - resCh <- result - return - } - continue - } - - if res == nil { - // If we didn't find the resource, check if we have been - // not finding it for awhile, and if so, report an error. - notfoundTick++ - if notfoundTick > conf.NotFoundChecks { - result.Error = &NotFoundError{ - LastError: err, - Retries: notfoundTick, - } - resCh <- result - return - } - } else { - // Reset the counter for when a resource isn't found - notfoundTick = 0 - found := false - - for _, allowed := range conf.Target { - if currentState == allowed { - found = true - targetOccurence++ - if conf.ContinuousTargetOccurence == targetOccurence { - result.Done = true - resCh <- result - return - } - continue - } - } - - for _, allowed := range conf.Pending { - if currentState == allowed { - found = true - targetOccurence = 0 - break - } - } - - if !found && len(conf.Pending) > 0 { - result.Error = &UnexpectedStateError{ - LastError: err, - State: result.State, - ExpectedState: conf.Target, - } - resCh <- result - return - } - } - - // Wait between refreshes using exponential backoff, except when - // waiting for the target state to reoccur. - if targetOccurence == 0 { - wait *= 2 - } - - // If a poll interval has been specified, choose that interval. - // Otherwise bound the default value. - if conf.PollInterval > 0 && conf.PollInterval < 180*time.Second { - wait = conf.PollInterval - } else { - if wait < conf.MinTimeout { - wait = conf.MinTimeout - } else if wait > 10*time.Second { - wait = 10 * time.Second - } - } - - log.Printf("[TRACE] Waiting %s before next try", wait) - } - }() - - // store the last value result from the refresh loop - lastResult := Result{} - - timeout := time.After(conf.Timeout) - for { - select { - case r, ok := <-resCh: - // channel closed, so return the last result - if !ok { - return lastResult.Result, lastResult.Error - } - - // we reached the intended state - if r.Done { - return r.Result, r.Error - } - - // still waiting, store the last result - lastResult = r - - case <-timeout: - log.Printf("[WARN] WaitForState timeout after %s", conf.Timeout) - log.Printf("[WARN] WaitForState starting %s refresh grace period", refreshGracePeriod) - - // cancel the goroutine and start our grace period timer - close(cancelCh) - timeout := time.After(refreshGracePeriod) - - // we need a for loop and a label to break on, because we may have - // an extra response value to read, but still want to wait for the - // channel to close. - forSelect: - for { - select { - case r, ok := <-resCh: - if r.Done { - // the last refresh loop reached the desired state - return r.Result, r.Error - } - - if !ok { - // the goroutine returned - break forSelect - } - - // target state not reached, save the result for the - // TimeoutError and wait for the channel to close - lastResult = r - case <-timeout: - log.Println("[ERROR] WaitForState exceeded refresh grace period") - break forSelect - } - } - - return nil, &TimeoutError{ - LastError: lastResult.Error, - LastState: lastResult.State, - Timeout: conf.Timeout, - ExpectedState: conf.Target, - } - } - } -} diff --git a/helper/resource/state_shim.go b/helper/resource/state_shim.go deleted file mode 100644 index aa2231b28..000000000 --- a/helper/resource/state_shim.go +++ /dev/null @@ -1,218 +0,0 @@ -package resource - -import ( - "encoding/json" - "fmt" - - "github.com/hashicorp/terraform/addrs" - "github.com/zclconf/go-cty/cty" - - "github.com/hashicorp/terraform/configs/hcl2shim" - "github.com/hashicorp/terraform/helper/schema" - - "github.com/hashicorp/terraform/states" - "github.com/hashicorp/terraform/terraform" -) - -// shimState takes a new *states.State and reverts it to a legacy state for the provider ACC tests -func shimNewState(newState *states.State, providers map[string]terraform.ResourceProvider) (*terraform.State, error) { - state := terraform.NewState() - - // in the odd case of a nil state, let the helper packages handle it - if newState == nil { - return nil, nil - } - - for _, newMod := range newState.Modules { - mod := state.AddModule(newMod.Addr) - - for name, out := range newMod.OutputValues { - outputType := "" - val := hcl2shim.ConfigValueFromHCL2(out.Value) - ty := out.Value.Type() - switch { - case ty == cty.String: - outputType = "string" - case ty.IsTupleType() || ty.IsListType(): - outputType = "list" - case ty.IsMapType(): - outputType = "map" - } - - mod.Outputs[name] = &terraform.OutputState{ - Type: outputType, - Value: val, - Sensitive: out.Sensitive, - } - } - - for _, res := range newMod.Resources { - resType := res.Addr.Resource.Type - providerType := res.ProviderConfig.Provider.Type - - resource := getResource(providers, providerType, res.Addr.Resource) - - for key, i := range res.Instances { - resState := &terraform.ResourceState{ - Type: resType, - Provider: legacyProviderConfigString(res.ProviderConfig), - } - - // We should always have a Current instance here, but be safe about checking. - if i.Current != nil { - flatmap, err := shimmedAttributes(i.Current, resource) - if err != nil { - return nil, fmt.Errorf("error decoding state for %q: %s", resType, err) - } - - var meta map[string]interface{} - if i.Current.Private != nil { - err := json.Unmarshal(i.Current.Private, &meta) - if err != nil { - return nil, err - } - } - - resState.Primary = &terraform.InstanceState{ - ID: flatmap["id"], - Attributes: flatmap, - Tainted: i.Current.Status == states.ObjectTainted, - Meta: meta, - } - - if i.Current.SchemaVersion != 0 { - if resState.Primary.Meta == nil { - resState.Primary.Meta = map[string]interface{}{} - } - resState.Primary.Meta["schema_version"] = i.Current.SchemaVersion - } - - // convert the indexes to the old style flapmap indexes - idx := "" - switch key.(type) { - case addrs.IntKey: - // don't add numeric index values to resources with a count of 0 - if len(res.Instances) > 1 { - idx = fmt.Sprintf(".%d", key) - } - case addrs.StringKey: - idx = "." + key.String() - } - - mod.Resources[res.Addr.Resource.String()+idx] = resState - } - - // add any deposed instances - for _, dep := range i.Deposed { - flatmap, err := shimmedAttributes(dep, resource) - if err != nil { - return nil, fmt.Errorf("error decoding deposed state for %q: %s", resType, err) - } - - var meta map[string]interface{} - if dep.Private != nil { - err := json.Unmarshal(dep.Private, &meta) - if err != nil { - return nil, err - } - } - - deposed := &terraform.InstanceState{ - ID: flatmap["id"], - Attributes: flatmap, - Tainted: dep.Status == states.ObjectTainted, - Meta: meta, - } - if dep.SchemaVersion != 0 { - deposed.Meta = map[string]interface{}{ - "schema_version": dep.SchemaVersion, - } - } - - resState.Deposed = append(resState.Deposed, deposed) - } - } - } - } - - return state, nil -} - -func getResource(providers map[string]terraform.ResourceProvider, providerName string, addr addrs.Resource) *schema.Resource { - p := providers[providerName] - if p == nil { - panic(fmt.Sprintf("provider %q not found in test step", providerName)) - } - - // this is only for tests, so should only see schema.Providers - provider := p.(*schema.Provider) - - switch addr.Mode { - case addrs.ManagedResourceMode: - resource := provider.ResourcesMap[addr.Type] - if resource != nil { - return resource - } - case addrs.DataResourceMode: - resource := provider.DataSourcesMap[addr.Type] - if resource != nil { - return resource - } - } - - panic(fmt.Sprintf("resource %s not found in test step", addr.Type)) -} - -func shimmedAttributes(instance *states.ResourceInstanceObjectSrc, res *schema.Resource) (map[string]string, error) { - flatmap := instance.AttrsFlat - if flatmap != nil { - return flatmap, nil - } - - // if we have json attrs, they need to be decoded - rio, err := instance.Decode(res.CoreConfigSchema().ImpliedType()) - if err != nil { - return nil, err - } - - instanceState, err := res.ShimInstanceStateFromValue(rio.Value) - if err != nil { - return nil, err - } - - return instanceState.Attributes, nil -} - -func shimLegacyState(legacy *terraform.State) (*states.State, error) { - state, err := terraform.ShimLegacyState(legacy) - if err != nil { - return nil, err - } - - if state.HasResources() { - for _, module := range state.Modules { - for name, resource := range module.Resources { - module.Resources[name].ProviderConfig.Provider = addrs.ImpliedProviderForUnqualifiedType(resource.Addr.Resource.ImpliedProvider()) - } - } - } - return state, err -} - -// legacyProviderConfigString was copied from addrs.Provider.LegacyString() to -// create a legacy-style string from a non-legacy provider. This is only -// necessary as this package shims back and forth between legacy and modern -// state, neither of which encode the addrs.Provider for a resource. -func legacyProviderConfigString(pc addrs.AbsProviderConfig) string { - if pc.Alias != "" { - if len(pc.Module) == 0 { - return fmt.Sprintf("%s.%s.%s", "provider", pc.Provider.Type, pc.Alias) - } else { - return fmt.Sprintf("%s.%s.%s.%s", pc.Module.String(), "provider", pc.Provider.LegacyString(), pc.Alias) - } - } - if len(pc.Module) == 0 { - return fmt.Sprintf("%s.%s", "provider", pc.Provider.Type) - } - return fmt.Sprintf("%s.%s.%s", pc.Module.String(), "provider", pc.Provider.Type) -} diff --git a/helper/resource/state_shim_test.go b/helper/resource/state_shim_test.go deleted file mode 100644 index 789e1295f..000000000 --- a/helper/resource/state_shim_test.go +++ /dev/null @@ -1,387 +0,0 @@ -package resource - -import ( - "testing" - - "github.com/hashicorp/terraform/addrs" - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/states" - "github.com/hashicorp/terraform/terraform" - "github.com/zclconf/go-cty/cty" -) - -// TestStateShim is meant to be a fairly comprehensive test, checking for dependencies, root outputs, -func TestStateShim(t *testing.T) { - state := states.NewState() - - rootModule := state.RootModule() - if rootModule == nil { - t.Errorf("root module is nil; want valid object") - } - - rootModule.SetOutputValue("bar", cty.ListVal([]cty.Value{cty.StringVal("bar"), cty.StringVal("value")}), false) - rootModule.SetOutputValue("secret", cty.StringVal("secret value"), true) - rootModule.SetResourceInstanceCurrent( - addrs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "test_thing", - Name: "foo", - }.Instance(addrs.NoKey), - &states.ResourceInstanceObjectSrc{ - Status: states.ObjectReady, - AttrsFlat: map[string]string{"id": "foo", "bazzle": "dazzle"}, - SchemaVersion: 7, - }, - addrs.AbsProviderConfig{ - Provider: addrs.NewDefaultProvider("test"), - Module: addrs.RootModule, - }, - ) - rootModule.SetResourceInstanceCurrent( - addrs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "test_thing", - Name: "baz", - }.Instance(addrs.NoKey), - &states.ResourceInstanceObjectSrc{ - Status: states.ObjectReady, - AttrsFlat: map[string]string{"id": "baz", "bazzle": "dazzle"}, - }, - addrs.AbsProviderConfig{ - Provider: addrs.NewDefaultProvider("test"), - Module: addrs.RootModule, - }, - ) - - childInstance := addrs.RootModuleInstance.Child("child", addrs.NoKey) - childModule := state.EnsureModule(childInstance) - childModule.SetResourceInstanceCurrent( - addrs.Resource{ - Mode: addrs.DataResourceMode, - Type: "test_data_thing", - Name: "foo", - }.Instance(addrs.NoKey), - &states.ResourceInstanceObjectSrc{ - Status: states.ObjectReady, - AttrsJSON: []byte(`{"id": "bar", "fuzzle":"wuzzle"}`), - }, - addrs.AbsProviderConfig{ - Provider: addrs.NewDefaultProvider("test"), - Module: childInstance.Module(), - }, - ) - childModule.SetResourceInstanceCurrent( - addrs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "test_thing", - Name: "baz", - }.Instance(addrs.NoKey), - &states.ResourceInstanceObjectSrc{ - Status: states.ObjectReady, - AttrsJSON: []byte(`{"id": "bar", "fizzle":"wizzle"}`), - }, - addrs.AbsProviderConfig{ - Provider: addrs.NewDefaultProvider("test"), - Module: childInstance.Module(), - }, - ) - - childModule.SetResourceInstanceDeposed( - addrs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "test_thing", - Name: "baz", - }.Instance(addrs.NoKey), - "00000001", - &states.ResourceInstanceObjectSrc{ - Status: states.ObjectReady, - AttrsFlat: map[string]string{"id": "old", "fizzle": "wizzle"}, - }, - addrs.AbsProviderConfig{ - Provider: addrs.NewDefaultProvider("test"), - Module: childInstance.Module(), - }, - ) - - childModule.SetResourceInstanceCurrent( - addrs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "test_thing", - Name: "lots", - }.Instance(addrs.IntKey(0)), - &states.ResourceInstanceObjectSrc{ - Status: states.ObjectReady, - AttrsFlat: map[string]string{"id": "0", "bazzle": "dazzle"}, - }, - addrs.AbsProviderConfig{ - Provider: addrs.NewDefaultProvider("test"), - Module: childInstance.Module(), - }, - ) - childModule.SetResourceInstanceCurrent( - addrs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "test_thing", - Name: "lots", - }.Instance(addrs.IntKey(1)), - &states.ResourceInstanceObjectSrc{ - Status: states.ObjectTainted, - AttrsFlat: map[string]string{"id": "1", "bazzle": "dazzle"}, - }, - addrs.AbsProviderConfig{ - Provider: addrs.NewDefaultProvider("test"), - Module: childInstance.Module(), - }, - ) - - childModule.SetResourceInstanceCurrent( - addrs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "test_thing", - Name: "single_count", - }.Instance(addrs.IntKey(0)), - &states.ResourceInstanceObjectSrc{ - Status: states.ObjectReady, - AttrsJSON: []byte(`{"id": "single", "bazzle":"dazzle"}`), - }, - addrs.AbsProviderConfig{ - Provider: addrs.NewDefaultProvider("test"), - Module: childInstance.Module(), - }, - ) - - expected := &terraform.State{ - Version: 3, - Modules: []*terraform.ModuleState{ - &terraform.ModuleState{ - Path: []string{"root"}, - Outputs: map[string]*terraform.OutputState{ - "bar": { - Type: "list", - Value: []interface{}{"bar", "value"}, - }, - "secret": { - Sensitive: true, - Type: "string", - Value: "secret value", - }, - }, - Resources: map[string]*terraform.ResourceState{ - "test_thing.baz": &terraform.ResourceState{ - Type: "test_thing", - Provider: "provider.test", - Primary: &terraform.InstanceState{ - ID: "baz", - Attributes: map[string]string{ - "id": "baz", - "bazzle": "dazzle", - }, - }, - }, - "test_thing.foo": &terraform.ResourceState{ - Type: "test_thing", - Provider: "provider.test", - Primary: &terraform.InstanceState{ - ID: "foo", - Attributes: map[string]string{ - "id": "foo", - "bazzle": "dazzle", - }, - Meta: map[string]interface{}{ - "schema_version": 7, - }, - }, - }, - }, - }, - &terraform.ModuleState{ - Path: []string{"root", "child"}, - Resources: map[string]*terraform.ResourceState{ - "test_thing.baz": &terraform.ResourceState{ - Type: "test_thing", - Provider: "module.child.provider.test", - Primary: &terraform.InstanceState{ - ID: "bar", - Attributes: map[string]string{ - "id": "bar", - "fizzle": "wizzle", - }, - }, - Deposed: []*terraform.InstanceState{ - { - ID: "old", - Attributes: map[string]string{ - "id": "old", - "fizzle": "wizzle", - }, - }, - }, - }, - "data.test_data_thing.foo": &terraform.ResourceState{ - Type: "test_data_thing", - Provider: "module.child.provider.test", - Primary: &terraform.InstanceState{ - ID: "bar", - Attributes: map[string]string{ - "id": "bar", - "fuzzle": "wuzzle", - }, - }, - }, - "test_thing.lots.0": &terraform.ResourceState{ - Type: "test_thing", - Provider: "module.child.provider.test", - Primary: &terraform.InstanceState{ - ID: "0", - Attributes: map[string]string{ - "id": "0", - "bazzle": "dazzle", - }, - }, - }, - "test_thing.lots.1": &terraform.ResourceState{ - Type: "test_thing", - Provider: "module.child.provider.test", - Primary: &terraform.InstanceState{ - ID: "1", - Attributes: map[string]string{ - "id": "1", - "bazzle": "dazzle", - }, - Tainted: true, - }, - }, - "test_thing.single_count": &terraform.ResourceState{ - Type: "test_thing", - Provider: "module.child.provider.test", - Primary: &terraform.InstanceState{ - ID: "single", - Attributes: map[string]string{ - "id": "single", - "bazzle": "dazzle", - }, - }, - }, - }, - }, - }, - } - - providers := map[string]terraform.ResourceProvider{ - "test": &schema.Provider{ - ResourcesMap: map[string]*schema.Resource{ - "test_thing": &schema.Resource{ - Schema: map[string]*schema.Schema{ - "id": {Type: schema.TypeString, Computed: true}, - "fizzle": {Type: schema.TypeString, Optional: true}, - "bazzle": {Type: schema.TypeString, Optional: true}, - }, - }, - }, - DataSourcesMap: map[string]*schema.Resource{ - "test_data_thing": &schema.Resource{ - Schema: map[string]*schema.Schema{ - "id": {Type: schema.TypeString, Computed: true}, - "fuzzle": {Type: schema.TypeString, Optional: true}, - }, - }, - }, - }, - } - - shimmed, err := shimNewState(state, providers) - if err != nil { - t.Fatal(err) - } - - if !expected.Equal(shimmed) { - t.Fatalf("wrong result state\ngot:\n%s\n\nwant:\n%s", shimmed, expected) - } -} - -// TestShimLegacyState only checks the functionality unique to this func: adding -// the implied provider FQN -func TestShimLegacyState(t *testing.T) { - - input := &terraform.State{ - Version: 3, - Modules: []*terraform.ModuleState{ - &terraform.ModuleState{ - Path: []string{"root"}, - Resources: map[string]*terraform.ResourceState{ - "test_thing.baz": &terraform.ResourceState{ - Type: "test_thing", - Provider: "provider.test", - Primary: &terraform.InstanceState{ - ID: "baz", - Attributes: map[string]string{ - "id": "baz", - "bazzle": "dazzle", - }, - }, - }, - }, - }, - &terraform.ModuleState{ - Path: []string{"root", "child"}, - Resources: map[string]*terraform.ResourceState{ - "test_thing.bar": &terraform.ResourceState{ - Type: "test_thing", - Provider: "module.child.provider.test", - Primary: &terraform.InstanceState{ - ID: "bar", - Attributes: map[string]string{ - "id": "bar", - "fizzle": "wizzle", - }, - }, - }, - }, - }, - }, - } - - expected := states.NewState() - root := expected.EnsureModule(addrs.RootModuleInstance) - root.SetResourceInstanceCurrent( - addrs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "test_thing", - Name: "baz", - }.Instance(addrs.NoKey), - &states.ResourceInstanceObjectSrc{ - Status: states.ObjectReady, - AttrsFlat: map[string]string{"id": "baz", "bazzle": "dazzle"}, - Dependencies: []addrs.ConfigResource{}, - }, - addrs.AbsProviderConfig{ - Provider: addrs.NewDefaultProvider("test"), - Module: addrs.RootModule, - }, - ) - child := expected.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) - child.SetResourceInstanceCurrent( - addrs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "test_thing", - Name: "bar", - }.Instance(addrs.NoKey), - &states.ResourceInstanceObjectSrc{ - Status: states.ObjectReady, - AttrsFlat: map[string]string{"id": "bar", "fizzle": "wizzle"}, - Dependencies: []addrs.ConfigResource{}, - }, - addrs.AbsProviderConfig{ - Provider: addrs.NewDefaultProvider("test"), - Module: child.Addr.Module(), - }, - ) - - got, err := shimLegacyState(input) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - if !got.Equal(expected) { - t.Fatal("wrong result") - } -} diff --git a/helper/resource/state_test.go b/helper/resource/state_test.go deleted file mode 100644 index 6d6b329a1..000000000 --- a/helper/resource/state_test.go +++ /dev/null @@ -1,329 +0,0 @@ -package resource - -import ( - "errors" - "strings" - "sync/atomic" - "testing" - "time" -) - -func FailedStateRefreshFunc() StateRefreshFunc { - return func() (interface{}, string, error) { - return nil, "", errors.New("failed") - } -} - -func TimeoutStateRefreshFunc() StateRefreshFunc { - return func() (interface{}, string, error) { - time.Sleep(100 * time.Second) - return nil, "", errors.New("failed") - } -} - -func SuccessfulStateRefreshFunc() StateRefreshFunc { - return func() (interface{}, string, error) { - return struct{}{}, "running", nil - } -} - -type StateGenerator struct { - position int - stateSequence []string -} - -func (r *StateGenerator) NextState() (int, string, error) { - p, v := r.position, "" - if len(r.stateSequence)-1 >= p { - v = r.stateSequence[p] - } else { - return -1, "", errors.New("No more states available") - } - - r.position += 1 - - return p, v, nil -} - -func NewStateGenerator(sequence []string) *StateGenerator { - r := &StateGenerator{} - r.stateSequence = sequence - - return r -} - -func InconsistentStateRefreshFunc() StateRefreshFunc { - sequence := []string{ - "done", "replicating", - "done", "done", "done", - "replicating", - "done", "done", "done", - } - - r := NewStateGenerator(sequence) - - return func() (interface{}, string, error) { - idx, s, err := r.NextState() - if err != nil { - return nil, "", err - } - - return idx, s, nil - } -} - -func UnknownPendingStateRefreshFunc() StateRefreshFunc { - sequence := []string{ - "unknown1", "unknown2", "done", - } - - r := NewStateGenerator(sequence) - - return func() (interface{}, string, error) { - idx, s, err := r.NextState() - if err != nil { - return nil, "", err - } - - return idx, s, nil - } -} - -func TestWaitForState_inconsistent_positive(t *testing.T) { - conf := &StateChangeConf{ - Pending: []string{"replicating"}, - Target: []string{"done"}, - Refresh: InconsistentStateRefreshFunc(), - Timeout: 90 * time.Millisecond, - PollInterval: 10 * time.Millisecond, - ContinuousTargetOccurence: 3, - } - - idx, err := conf.WaitForState() - - if err != nil { - t.Fatalf("err: %s", err) - } - - if idx != 4 { - t.Fatalf("Expected index 4, given %d", idx.(int)) - } -} - -func TestWaitForState_inconsistent_negative(t *testing.T) { - refreshCount := int64(0) - f := InconsistentStateRefreshFunc() - refresh := func() (interface{}, string, error) { - atomic.AddInt64(&refreshCount, 1) - return f() - } - - conf := &StateChangeConf{ - Pending: []string{"replicating"}, - Target: []string{"done"}, - Refresh: refresh, - Timeout: 85 * time.Millisecond, - PollInterval: 10 * time.Millisecond, - ContinuousTargetOccurence: 4, - } - - _, err := conf.WaitForState() - - if err == nil { - t.Fatal("Expected timeout error. No error returned.") - } - - // we can't guarantee the exact number of refresh calls in the tests by - // timing them, but we want to make sure the test at least went through th - // required states. - if atomic.LoadInt64(&refreshCount) < 6 { - t.Fatal("refreshed called too few times") - } - - expectedErr := "timeout while waiting for state to become 'done'" - if !strings.HasPrefix(err.Error(), expectedErr) { - t.Fatalf("error prefix doesn't match.\nExpected: %q\nGiven: %q\n", expectedErr, err.Error()) - } -} - -func TestWaitForState_timeout(t *testing.T) { - old := refreshGracePeriod - refreshGracePeriod = 5 * time.Millisecond - defer func() { - refreshGracePeriod = old - }() - - conf := &StateChangeConf{ - Pending: []string{"pending", "incomplete"}, - Target: []string{"running"}, - Refresh: TimeoutStateRefreshFunc(), - Timeout: 1 * time.Millisecond, - } - - obj, err := conf.WaitForState() - - if err == nil { - t.Fatal("Expected timeout error. No error returned.") - } - - expectedErr := "timeout while waiting for state to become 'running' (timeout: 1ms)" - if err.Error() != expectedErr { - t.Fatalf("Errors don't match.\nExpected: %q\nGiven: %q\n", expectedErr, err.Error()) - } - - if obj != nil { - t.Fatalf("should not return obj") - } -} - -// Make sure a timeout actually cancels the refresh goroutine and waits for its -// return. -func TestWaitForState_cancel(t *testing.T) { - // make this refresh func block until we cancel it - cancel := make(chan struct{}) - refresh := func() (interface{}, string, error) { - <-cancel - return nil, "pending", nil - } - conf := &StateChangeConf{ - Pending: []string{"pending", "incomplete"}, - Target: []string{"running"}, - Refresh: refresh, - Timeout: 10 * time.Millisecond, - PollInterval: 10 * time.Second, - } - - var obj interface{} - var err error - - waitDone := make(chan struct{}) - go func() { - defer close(waitDone) - obj, err = conf.WaitForState() - }() - - // make sure WaitForState is blocked - select { - case <-waitDone: - t.Fatal("WaitForState returned too early") - case <-time.After(10 * time.Millisecond): - } - - // unlock the refresh function - close(cancel) - // make sure WaitForState returns - select { - case <-waitDone: - case <-time.After(time.Second): - t.Fatal("WaitForState didn't return after refresh finished") - } - - if err == nil { - t.Fatal("Expected timeout error. No error returned.") - } - - expectedErr := "timeout while waiting for state to become 'running'" - if !strings.HasPrefix(err.Error(), expectedErr) { - t.Fatalf("Errors don't match.\nExpected: %q\nGiven: %q\n", expectedErr, err.Error()) - } - - if obj != nil { - t.Fatalf("should not return obj") - } - -} - -func TestWaitForState_success(t *testing.T) { - conf := &StateChangeConf{ - Pending: []string{"pending", "incomplete"}, - Target: []string{"running"}, - Refresh: SuccessfulStateRefreshFunc(), - Timeout: 200 * time.Second, - } - - obj, err := conf.WaitForState() - if err != nil { - t.Fatalf("err: %s", err) - } - if obj == nil { - t.Fatalf("should return obj") - } -} - -func TestWaitForState_successUnknownPending(t *testing.T) { - conf := &StateChangeConf{ - Target: []string{"done"}, - Refresh: UnknownPendingStateRefreshFunc(), - Timeout: 200 * time.Second, - } - - obj, err := conf.WaitForState() - if err != nil { - t.Fatalf("err: %s", err) - } - if obj == nil { - t.Fatalf("should return obj") - } -} - -func TestWaitForState_successEmpty(t *testing.T) { - conf := &StateChangeConf{ - Pending: []string{"pending", "incomplete"}, - Target: []string{}, - Refresh: func() (interface{}, string, error) { - return nil, "", nil - }, - Timeout: 200 * time.Second, - } - - obj, err := conf.WaitForState() - if err != nil { - t.Fatalf("err: %s", err) - } - if obj != nil { - t.Fatalf("obj should be nil") - } -} - -func TestWaitForState_failureEmpty(t *testing.T) { - conf := &StateChangeConf{ - Pending: []string{"pending", "incomplete"}, - Target: []string{}, - NotFoundChecks: 1, - Refresh: func() (interface{}, string, error) { - return 42, "pending", nil - }, - PollInterval: 10 * time.Millisecond, - Timeout: 100 * time.Millisecond, - } - - _, err := conf.WaitForState() - if err == nil { - t.Fatal("Expected timeout error. Got none.") - } - expectedErr := "timeout while waiting for resource to be gone (last state: 'pending', timeout: 100ms)" - if err.Error() != expectedErr { - t.Fatalf("Errors don't match.\nExpected: %q\nGiven: %q\n", expectedErr, err.Error()) - } -} - -func TestWaitForState_failure(t *testing.T) { - conf := &StateChangeConf{ - Pending: []string{"pending", "incomplete"}, - Target: []string{"running"}, - Refresh: FailedStateRefreshFunc(), - Timeout: 200 * time.Second, - } - - obj, err := conf.WaitForState() - if err == nil { - t.Fatal("Expected error. No error returned.") - } - expectedErr := "failed" - if err.Error() != expectedErr { - t.Fatalf("Errors don't match.\nExpected: %q\nGiven: %q\n", expectedErr, err.Error()) - } - if obj != nil { - t.Fatalf("should not return obj") - } -} diff --git a/helper/resource/testing.go b/helper/resource/testing.go deleted file mode 100644 index c36ff4b2e..000000000 --- a/helper/resource/testing.go +++ /dev/null @@ -1,1285 +0,0 @@ -package resource - -import ( - "bytes" - "flag" - "fmt" - "io/ioutil" - "log" - "os" - "path/filepath" - "reflect" - "regexp" - "strings" - "testing" - - "github.com/davecgh/go-spew/spew" - "github.com/hashicorp/errwrap" - "github.com/hashicorp/go-multierror" - "github.com/mitchellh/colorstring" - - "github.com/hashicorp/terraform/addrs" - "github.com/hashicorp/terraform/command/format" - "github.com/hashicorp/terraform/configs" - "github.com/hashicorp/terraform/configs/configload" - "github.com/hashicorp/terraform/internal/initwd" - "github.com/hashicorp/terraform/providers" - "github.com/hashicorp/terraform/states" - "github.com/hashicorp/terraform/terraform" - "github.com/hashicorp/terraform/tfdiags" - - _ "github.com/hashicorp/terraform/internal/logging" -) - -// flagSweep is a flag available when running tests on the command line. It -// contains a comma seperated list of regions to for the sweeper functions to -// run in. This flag bypasses the normal Test path and instead runs functions designed to -// clean up any leaked resources a testing environment could have created. It is -// a best effort attempt, and relies on Provider authors to implement "Sweeper" -// methods for resources. - -// Adding Sweeper methods with AddTestSweepers will -// construct a list of sweeper funcs to be called here. We iterate through -// regions provided by the sweep flag, and for each region we iterate through the -// tests, and exit on any errors. At time of writing, sweepers are ran -// sequentially, however they can list dependencies to be ran first. We track -// the sweepers that have been ran, so as to not run a sweeper twice for a given -// region. -// -// WARNING: -// Sweepers are designed to be destructive. You should not use the -sweep flag -// in any environment that is not strictly a test environment. Resources will be -// destroyed. - -var flagSweep = flag.String("sweep", "", "List of Regions to run available Sweepers") -var flagSweepRun = flag.String("sweep-run", "", "Comma seperated list of Sweeper Tests to run") -var sweeperFuncs map[string]*Sweeper - -// map of sweepers that have ran, and the success/fail status based on any error -// raised -var sweeperRunList map[string]bool - -// type SweeperFunc is a signature for a function that acts as a sweeper. It -// accepts a string for the region that the sweeper is to be ran in. This -// function must be able to construct a valid client for that region. -type SweeperFunc func(r string) error - -type Sweeper struct { - // Name for sweeper. Must be unique to be ran by the Sweeper Runner - Name string - - // Dependencies list the const names of other Sweeper functions that must be ran - // prior to running this Sweeper. This is an ordered list that will be invoked - // recursively at the helper/resource level - Dependencies []string - - // Sweeper function that when invoked sweeps the Provider of specific - // resources - F SweeperFunc -} - -func init() { - sweeperFuncs = make(map[string]*Sweeper) -} - -// AddTestSweepers function adds a given name and Sweeper configuration -// pair to the internal sweeperFuncs map. Invoke this function to register a -// resource sweeper to be available for running when the -sweep flag is used -// with `go test`. Sweeper names must be unique to help ensure a given sweeper -// is only ran once per run. -func AddTestSweepers(name string, s *Sweeper) { - if _, ok := sweeperFuncs[name]; ok { - log.Fatalf("[ERR] Error adding (%s) to sweeperFuncs: function already exists in map", name) - } - - sweeperFuncs[name] = s -} - -func TestMain(m *testing.M) { - flag.Parse() - if *flagSweep != "" { - // parse flagSweep contents for regions to run - regions := strings.Split(*flagSweep, ",") - - // get filtered list of sweepers to run based on sweep-run flag - sweepers := filterSweepers(*flagSweepRun, sweeperFuncs) - for _, region := range regions { - region = strings.TrimSpace(region) - // reset sweeperRunList for each region - sweeperRunList = map[string]bool{} - - log.Printf("[DEBUG] Running Sweepers for region (%s):\n", region) - for _, sweeper := range sweepers { - if err := runSweeperWithRegion(region, sweeper); err != nil { - log.Fatalf("[ERR] error running (%s): %s", sweeper.Name, err) - } - } - - log.Printf("Sweeper Tests ran:\n") - for s, _ := range sweeperRunList { - fmt.Printf("\t- %s\n", s) - } - } - } else { - os.Exit(m.Run()) - } -} - -// filterSweepers takes a comma seperated string listing the names of sweepers -// to be ran, and returns a filtered set from the list of all of sweepers to -// run based on the names given. -func filterSweepers(f string, source map[string]*Sweeper) map[string]*Sweeper { - filterSlice := strings.Split(strings.ToLower(f), ",") - if len(filterSlice) == 1 && filterSlice[0] == "" { - // if the filter slice is a single element of "" then no sweeper list was - // given, so just return the full list - return source - } - - sweepers := make(map[string]*Sweeper) - for name, sweeper := range source { - for _, s := range filterSlice { - if strings.Contains(strings.ToLower(name), s) { - sweepers[name] = sweeper - } - } - } - return sweepers -} - -// runSweeperWithRegion recieves a sweeper and a region, and recursively calls -// itself with that region for every dependency found for that sweeper. If there -// are no dependencies, invoke the contained sweeper fun with the region, and -// add the success/fail status to the sweeperRunList. -func runSweeperWithRegion(region string, s *Sweeper) error { - for _, dep := range s.Dependencies { - if depSweeper, ok := sweeperFuncs[dep]; ok { - log.Printf("[DEBUG] Sweeper (%s) has dependency (%s), running..", s.Name, dep) - if err := runSweeperWithRegion(region, depSweeper); err != nil { - return err - } - } else { - log.Printf("[DEBUG] Sweeper (%s) has dependency (%s), but that sweeper was not found", s.Name, dep) - } - } - - if _, ok := sweeperRunList[s.Name]; ok { - log.Printf("[DEBUG] Sweeper (%s) already ran in region (%s)", s.Name, region) - return nil - } - - runE := s.F(region) - if runE == nil { - sweeperRunList[s.Name] = true - } else { - sweeperRunList[s.Name] = false - } - - return runE -} - -const TestEnvVar = "TF_ACC" - -// TestProvider can be implemented by any ResourceProvider to provide custom -// reset functionality at the start of an acceptance test. -// The helper/schema Provider implements this interface. -type TestProvider interface { - TestReset() error -} - -// TestCheckFunc is the callback type used with acceptance tests to check -// the state of a resource. The state passed in is the latest state known, -// or in the case of being after a destroy, it is the last known state when -// it was created. -type TestCheckFunc func(*terraform.State) error - -// ImportStateCheckFunc is the check function for ImportState tests -type ImportStateCheckFunc func([]*terraform.InstanceState) error - -// ImportStateIdFunc is an ID generation function to help with complex ID -// generation for ImportState tests. -type ImportStateIdFunc func(*terraform.State) (string, error) - -// TestCase is a single acceptance test case used to test the apply/destroy -// lifecycle of a resource in a specific configuration. -// -// When the destroy plan is executed, the config from the last TestStep -// is used to plan it. -type TestCase struct { - // IsUnitTest allows a test to run regardless of the TF_ACC - // environment variable. This should be used with care - only for - // fast tests on local resources (e.g. remote state with a local - // backend) but can be used to increase confidence in correct - // operation of Terraform without waiting for a full acctest run. - IsUnitTest bool - - // PreCheck, if non-nil, will be called before any test steps are - // executed. It will only be executed in the case that the steps - // would run, so it can be used for some validation before running - // acceptance tests, such as verifying that keys are setup. - PreCheck func() - - // Providers is the ResourceProvider that will be under test. - // - // Alternately, ProviderFactories can be specified for the providers - // that are valid. This takes priority over Providers. - // - // The end effect of each is the same: specifying the providers that - // are used within the tests. - Providers map[string]terraform.ResourceProvider - ProviderFactories map[string]terraform.ResourceProviderFactory - - // PreventPostDestroyRefresh can be set to true for cases where data sources - // are tested alongside real resources - PreventPostDestroyRefresh bool - - // CheckDestroy is called after the resource is finally destroyed - // to allow the tester to test that the resource is truly gone. - CheckDestroy TestCheckFunc - - // Steps are the apply sequences done within the context of the - // same state. Each step can have its own check to verify correctness. - Steps []TestStep - - // The settings below control the "ID-only refresh test." This is - // an enabled-by-default test that tests that a refresh can be - // refreshed with only an ID to result in the same attributes. - // This validates completeness of Refresh. - // - // IDRefreshName is the name of the resource to check. This will - // default to the first non-nil primary resource in the state. - // - // IDRefreshIgnore is a list of configuration keys that will be ignored. - IDRefreshName string - IDRefreshIgnore []string -} - -// TestStep is a single apply sequence of a test, done within the -// context of a state. -// -// Multiple TestSteps can be sequenced in a Test to allow testing -// potentially complex update logic. In general, simply create/destroy -// tests will only need one step. -type TestStep struct { - // ResourceName should be set to the name of the resource - // that is being tested. Example: "aws_instance.foo". Various test - // modes use this to auto-detect state information. - // - // This is only required if the test mode settings below say it is - // for the mode you're using. - ResourceName string - - // PreConfig is called before the Config is applied to perform any per-step - // setup that needs to happen. This is called regardless of "test mode" - // below. - PreConfig func() - - // Taint is a list of resource addresses to taint prior to the execution of - // the step. Be sure to only include this at a step where the referenced - // address will be present in state, as it will fail the test if the resource - // is missing. - // - // This option is ignored on ImportState tests, and currently only works for - // resources in the root module path. - Taint []string - - //--------------------------------------------------------------- - // Test modes. One of the following groups of settings must be - // set to determine what the test step will do. Ideally we would've - // used Go interfaces here but there are now hundreds of tests we don't - // want to re-type so instead we just determine which step logic - // to run based on what settings below are set. - //--------------------------------------------------------------- - - //--------------------------------------------------------------- - // Plan, Apply testing - //--------------------------------------------------------------- - - // Config a string of the configuration to give to Terraform. If this - // is set, then the TestCase will execute this step with the same logic - // as a `terraform apply`. - Config string - - // Check is called after the Config is applied. Use this step to - // make your own API calls to check the status of things, and to - // inspect the format of the ResourceState itself. - // - // If an error is returned, the test will fail. In this case, a - // destroy plan will still be attempted. - // - // If this is nil, no check is done on this step. - Check TestCheckFunc - - // Destroy will create a destroy plan if set to true. - Destroy bool - - // ExpectNonEmptyPlan can be set to true for specific types of tests that are - // looking to verify that a diff occurs - ExpectNonEmptyPlan bool - - // ExpectError allows the construction of test cases that we expect to fail - // with an error. The specified regexp must match against the error for the - // test to pass. - ExpectError *regexp.Regexp - - // PlanOnly can be set to only run `plan` with this configuration, and not - // actually apply it. This is useful for ensuring config changes result in - // no-op plans - PlanOnly bool - - // PreventDiskCleanup can be set to true for testing terraform modules which - // require access to disk at runtime. Note that this will leave files in the - // temp folder - PreventDiskCleanup bool - - // PreventPostDestroyRefresh can be set to true for cases where data sources - // are tested alongside real resources - PreventPostDestroyRefresh bool - - // SkipFunc is called before applying config, but after PreConfig - // This is useful for defining test steps with platform-dependent checks - SkipFunc func() (bool, error) - - //--------------------------------------------------------------- - // ImportState testing - //--------------------------------------------------------------- - - // ImportState, if true, will test the functionality of ImportState - // by importing the resource with ResourceName (must be set) and the - // ID of that resource. - ImportState bool - - // ImportStateId is the ID to perform an ImportState operation with. - // This is optional. If it isn't set, then the resource ID is automatically - // determined by inspecting the state for ResourceName's ID. - ImportStateId string - - // ImportStateIdPrefix is the prefix added in front of ImportStateId. - // This can be useful in complex import cases, where more than one - // attribute needs to be passed on as the Import ID. Mainly in cases - // where the ID is not known, and a known prefix needs to be added to - // the unset ImportStateId field. - ImportStateIdPrefix string - - // ImportStateIdFunc is a function that can be used to dynamically generate - // the ID for the ImportState tests. It is sent the state, which can be - // checked to derive the attributes necessary and generate the string in the - // desired format. - ImportStateIdFunc ImportStateIdFunc - - // ImportStateCheck checks the results of ImportState. It should be - // used to verify that the resulting value of ImportState has the - // proper resources, IDs, and attributes. - ImportStateCheck ImportStateCheckFunc - - // ImportStateVerify, if true, will also check that the state values - // that are finally put into the state after import match for all the - // IDs returned by the Import. Note that this checks for strict equality - // and does not respect DiffSuppressFunc or CustomizeDiff. - // - // ImportStateVerifyIgnore is a list of prefixes of fields that should - // not be verified to be equal. These can be set to ephemeral fields or - // fields that can't be refreshed and don't matter. - ImportStateVerify bool - ImportStateVerifyIgnore []string - - // provider s is used internally to maintain a reference to the - // underlying providers during the tests - providers map[string]terraform.ResourceProvider -} - -// Set to a file mask in sprintf format where %s is test name -const EnvLogPathMask = "TF_LOG_PATH_MASK" - -// ParallelTest performs an acceptance test on a resource, allowing concurrency -// with other ParallelTest. -// -// Tests will fail if they do not properly handle conditions to allow multiple -// tests to occur against the same resource or service (e.g. random naming). -// All other requirements of the Test function also apply to this function. -func ParallelTest(t TestT, c TestCase) { - t.Parallel() - Test(t, c) -} - -// Test performs an acceptance test on a resource. -// -// Tests are not run unless an environmental variable "TF_ACC" is -// set to some non-empty value. This is to avoid test cases surprising -// a user by creating real resources. -// -// Tests will fail unless the verbose flag (`go test -v`, or explicitly -// the "-test.v" flag) is set. Because some acceptance tests take quite -// long, we require the verbose flag so users are able to see progress -// output. -func Test(t TestT, c TestCase) { - // We only run acceptance tests if an env var is set because they're - // slow and generally require some outside configuration. You can opt out - // of this with OverrideEnvVar on individual TestCases. - if os.Getenv(TestEnvVar) == "" && !c.IsUnitTest { - t.Skip(fmt.Sprintf( - "Acceptance tests skipped unless env '%s' set", - TestEnvVar)) - return - } - - // We require verbose mode so that the user knows what is going on. - if !testTesting && !testing.Verbose() && !c.IsUnitTest { - t.Fatal("Acceptance tests must be run with the -v flag on tests") - return - } - - // Run the PreCheck if we have it - if c.PreCheck != nil { - c.PreCheck() - } - - providerFactories, err := testProviderFactories(c) - if err != nil { - t.Fatal(err) - } - - // get instances of all providers, so we can use the individual - // resources to shim the state during the tests. - providers := make(map[string]terraform.ResourceProvider) - legacyProviderFactories, err := testProviderFactoriesLegacy(c) - if err != nil { - t.Fatal(err) - } - for name, pf := range legacyProviderFactories { - p, err := pf() - if err != nil { - t.Fatal(err) - } - providers[name] = p - } - - opts := terraform.ContextOpts{Providers: providerFactories} - - // A single state variable to track the lifecycle, starting with no state - var state *terraform.State - - // Go through each step and run it - var idRefreshCheck *terraform.ResourceState - idRefresh := c.IDRefreshName != "" - errored := false - for i, step := range c.Steps { - // insert the providers into the step so we can get the resources for - // shimming the state - step.providers = providers - - var err error - log.Printf("[DEBUG] Test: Executing step %d", i) - - if step.SkipFunc != nil { - skip, err := step.SkipFunc() - if err != nil { - t.Fatal(err) - } - if skip { - log.Printf("[WARN] Skipping step %d", i) - continue - } - } - - if step.Config == "" && !step.ImportState { - err = fmt.Errorf( - "unknown test mode for step. Please see TestStep docs\n\n%#v", - step) - } else { - if step.ImportState { - if step.Config == "" { - step.Config = testProviderConfig(c) - } - - // Can optionally set step.Config in addition to - // step.ImportState, to provide config for the import. - state, err = testStepImportState(opts, state, step) - } else { - state, err = testStepConfig(opts, state, step) - } - } - - // If we expected an error, but did not get one, fail - if err == nil && step.ExpectError != nil { - errored = true - t.Error(fmt.Sprintf( - "Step %d, no error received, but expected a match to:\n\n%s\n\n", - i, step.ExpectError)) - break - } - - // If there was an error, exit - if err != nil { - // Perhaps we expected an error? Check if it matches - if step.ExpectError != nil { - if !step.ExpectError.MatchString(err.Error()) { - errored = true - t.Error(fmt.Sprintf( - "Step %d, expected error:\n\n%s\n\nTo match:\n\n%s\n\n", - i, err, step.ExpectError)) - break - } - } else { - errored = true - t.Error(fmt.Sprintf("Step %d error: %s", i, detailedErrorMessage(err))) - break - } - } - - // If we've never checked an id-only refresh and our state isn't - // empty, find the first resource and test it. - if idRefresh && idRefreshCheck == nil && !state.Empty() { - // Find the first non-nil resource in the state - for _, m := range state.Modules { - if len(m.Resources) > 0 { - if v, ok := m.Resources[c.IDRefreshName]; ok { - idRefreshCheck = v - } - - break - } - } - - // If we have an instance to check for refreshes, do it - // immediately. We do it in the middle of another test - // because it shouldn't affect the overall state (refresh - // is read-only semantically) and we want to fail early if - // this fails. If refresh isn't read-only, then this will have - // caught a different bug. - if idRefreshCheck != nil { - log.Printf( - "[WARN] Test: Running ID-only refresh check on %s", - idRefreshCheck.Primary.ID) - if err := testIDOnlyRefresh(c, opts, step, idRefreshCheck); err != nil { - log.Printf("[ERROR] Test: ID-only test failed: %s", err) - t.Error(fmt.Sprintf( - "[ERROR] Test: ID-only test failed: %s", err)) - break - } - } - } - } - - // If we never checked an id-only refresh, it is a failure. - if idRefresh { - if !errored && len(c.Steps) > 0 && idRefreshCheck == nil { - t.Error("ID-only refresh check never ran.") - } - } - - // If we have a state, then run the destroy - if state != nil { - lastStep := c.Steps[len(c.Steps)-1] - destroyStep := TestStep{ - Config: lastStep.Config, - Check: c.CheckDestroy, - Destroy: true, - PreventDiskCleanup: lastStep.PreventDiskCleanup, - PreventPostDestroyRefresh: c.PreventPostDestroyRefresh, - providers: providers, - } - - log.Printf("[WARN] Test: Executing destroy step") - state, err := testStep(opts, state, destroyStep) - if err != nil { - t.Error(fmt.Sprintf( - "Error destroying resource! WARNING: Dangling resources\n"+ - "may exist. The full state and error is shown below.\n\n"+ - "Error: %s\n\nState: %s", - err, - state)) - } - } else { - log.Printf("[WARN] Skipping destroy test since there is no state.") - } -} - -// testProviderConfig takes the list of Providers in a TestCase and returns a -// config with only empty provider blocks. This is useful for Import, where no -// config is provided, but the providers must be defined. -func testProviderConfig(c TestCase) string { - var lines []string - for p := range c.Providers { - lines = append(lines, fmt.Sprintf("provider %q {}\n", p)) - } - - return strings.Join(lines, "") -} - -// testProviderFactoriesLegacy is like testProviderFactories but it returns -// providers implementing the legacy interface terraform.ResourceProvider, -// rather than the current providers.Interface. -// -// It also identifies all providers as legacy-style single names rather than -// full addresses, for compatibility with legacy code that doesn't understand -// FQNs. -func testProviderFactoriesLegacy(c TestCase) (map[string]terraform.ResourceProviderFactory, error) { - ctxProviders := make(map[string]terraform.ResourceProviderFactory) - for k, pf := range c.ProviderFactories { - ctxProviders[k] = pf - } - - // add any fixed providers - for k, p := range c.Providers { - ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p) - } - return ctxProviders, nil -} - -// testProviderFactories combines the fixed Providers and -// ResourceProviderFactory functions into a single map of -// ResourceProviderFactory functions. -func testProviderFactories(c TestCase) (map[addrs.Provider]providers.Factory, error) { - ctxProviders, err := testProviderFactoriesLegacy(c) - if err != nil { - return nil, err - } - - // We additionally wrap all of the factories as a GRPCTestProvider, which - // allows them to appear as a new-style providers.Interface, rather than - // the legacy terraform.ResourceProvider. - newProviders := make(map[addrs.Provider]providers.Factory) - for legacyName, pf := range ctxProviders { - factory := pf // must copy to ensure each closure sees its own value - newProviders[addrs.NewDefaultProvider(legacyName)] = func() (providers.Interface, error) { - p, err := factory() - if err != nil { - return nil, err - } - - // The provider is wrapped in a GRPCTestProvider so that it can be - // passed back to terraform core as a providers.Interface, rather - // than the legacy ResourceProvider. - return GRPCTestProvider(p), nil - } - } - - return newProviders, nil -} - -// UnitTest is a helper to force the acceptance testing harness to run in the -// normal unit test suite. This should only be used for resource that don't -// have any external dependencies. -func UnitTest(t TestT, c TestCase) { - c.IsUnitTest = true - Test(t, c) -} - -func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r *terraform.ResourceState) error { - // TODO: We guard by this right now so master doesn't explode. We - // need to remove this eventually to make this part of the normal tests. - if os.Getenv("TF_ACC_IDONLY") == "" { - return nil - } - - addr := addrs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: r.Type, - Name: "foo", - }.Instance(addrs.NoKey) - absAddr := addr.Absolute(addrs.RootModuleInstance) - - // Build the state. The state is just the resource with an ID. There - // are no attributes. We only set what is needed to perform a refresh. - state := states.NewState() - state.RootModule().SetResourceInstanceCurrent( - addr, - &states.ResourceInstanceObjectSrc{ - AttrsFlat: r.Primary.Attributes, - Status: states.ObjectReady, - }, - addrs.AbsProviderConfig{ - Provider: addrs.NewDefaultProvider("placeholder"), - Module: addrs.RootModule, - }, - ) - - // Create the config module. We use the full config because Refresh - // doesn't have access to it and we may need things like provider - // configurations. The initial implementation of id-only checks used - // an empty config module, but that caused the aforementioned problems. - cfg, err := testConfig(opts, step) - if err != nil { - return err - } - - // Initialize the context - opts.Config = cfg - opts.State = state - ctx, ctxDiags := terraform.NewContext(&opts) - if ctxDiags.HasErrors() { - return ctxDiags.Err() - } - if diags := ctx.Validate(); len(diags) > 0 { - if diags.HasErrors() { - return errwrap.Wrapf("config is invalid: {{err}}", diags.Err()) - } - - log.Printf("[WARN] Config warnings:\n%s", diags.Err().Error()) - } - - // Refresh! - state, refreshDiags := ctx.Refresh() - if refreshDiags.HasErrors() { - return refreshDiags.Err() - } - - // Verify attribute equivalence. - actualR := state.ResourceInstance(absAddr) - if actualR == nil { - return fmt.Errorf("Resource gone!") - } - if actualR.Current == nil { - return fmt.Errorf("Resource has no primary instance") - } - actual := actualR.Current.AttrsFlat - expected := r.Primary.Attributes - // Remove fields we're ignoring - for _, v := range c.IDRefreshIgnore { - for k, _ := range actual { - if strings.HasPrefix(k, v) { - delete(actual, k) - } - } - for k, _ := range expected { - if strings.HasPrefix(k, v) { - delete(expected, k) - } - } - } - - if !reflect.DeepEqual(actual, expected) { - // Determine only the different attributes - for k, v := range expected { - if av, ok := actual[k]; ok && v == av { - delete(expected, k) - delete(actual, k) - } - } - - spewConf := spew.NewDefaultConfig() - spewConf.SortKeys = true - return fmt.Errorf( - "Attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+ - "\n\n%s\n\n%s", - spewConf.Sdump(actual), spewConf.Sdump(expected)) - } - - return nil -} - -func testConfig(opts terraform.ContextOpts, step TestStep) (*configs.Config, error) { - if step.PreConfig != nil { - step.PreConfig() - } - - cfgPath, err := ioutil.TempDir("", "tf-test") - if err != nil { - return nil, fmt.Errorf("Error creating temporary directory for config: %s", err) - } - - if step.PreventDiskCleanup { - log.Printf("[INFO] Skipping defer os.RemoveAll call") - } else { - defer os.RemoveAll(cfgPath) - } - - // Write the main configuration file - err = ioutil.WriteFile(filepath.Join(cfgPath, "main.tf"), []byte(step.Config), os.ModePerm) - if err != nil { - return nil, fmt.Errorf("Error creating temporary file for config: %s", err) - } - - // Create directory for our child modules, if any. - modulesDir := filepath.Join(cfgPath, ".modules") - err = os.Mkdir(modulesDir, os.ModePerm) - if err != nil { - return nil, fmt.Errorf("Error creating child modules directory: %s", err) - } - - inst := initwd.NewModuleInstaller(modulesDir, nil) - _, installDiags := inst.InstallModules(cfgPath, true, initwd.ModuleInstallHooksImpl{}) - if installDiags.HasErrors() { - return nil, installDiags.Err() - } - - loader, err := configload.NewLoader(&configload.Config{ - ModulesDir: modulesDir, - }) - if err != nil { - return nil, fmt.Errorf("failed to create config loader: %s", err) - } - - config, configDiags := loader.LoadConfig(cfgPath) - if configDiags.HasErrors() { - return nil, configDiags - } - - return config, nil -} - -func testResource(c TestStep, state *terraform.State) (*terraform.ResourceState, error) { - if c.ResourceName == "" { - return nil, fmt.Errorf("ResourceName must be set in TestStep") - } - - for _, m := range state.Modules { - if len(m.Resources) > 0 { - if v, ok := m.Resources[c.ResourceName]; ok { - return v, nil - } - } - } - - return nil, fmt.Errorf( - "Resource specified by ResourceName couldn't be found: %s", c.ResourceName) -} - -// ComposeTestCheckFunc lets you compose multiple TestCheckFuncs into -// a single TestCheckFunc. -// -// As a user testing their provider, this lets you decompose your checks -// into smaller pieces more easily. -func ComposeTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc { - return func(s *terraform.State) error { - for i, f := range fs { - if err := f(s); err != nil { - return fmt.Errorf("Check %d/%d error: %s", i+1, len(fs), err) - } - } - - return nil - } -} - -// ComposeAggregateTestCheckFunc lets you compose multiple TestCheckFuncs into -// a single TestCheckFunc. -// -// As a user testing their provider, this lets you decompose your checks -// into smaller pieces more easily. -// -// Unlike ComposeTestCheckFunc, ComposeAggergateTestCheckFunc runs _all_ of the -// TestCheckFuncs and aggregates failures. -func ComposeAggregateTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc { - return func(s *terraform.State) error { - var result *multierror.Error - - for i, f := range fs { - if err := f(s); err != nil { - result = multierror.Append(result, fmt.Errorf("Check %d/%d error: %s", i+1, len(fs), err)) - } - } - - return result.ErrorOrNil() - } -} - -// TestCheckResourceAttrSet is a TestCheckFunc which ensures a value -// exists in state for the given name/key combination. It is useful when -// testing that computed values were set, when it is not possible to -// know ahead of time what the values will be. -func TestCheckResourceAttrSet(name, key string) TestCheckFunc { - return func(s *terraform.State) error { - is, err := primaryInstanceState(s, name) - if err != nil { - return err - } - - return testCheckResourceAttrSet(is, name, key) - } -} - -// TestCheckModuleResourceAttrSet - as per TestCheckResourceAttrSet but with -// support for non-root modules -func TestCheckModuleResourceAttrSet(mp []string, name string, key string) TestCheckFunc { - mpt := addrs.Module(mp).UnkeyedInstanceShim() - return func(s *terraform.State) error { - is, err := modulePathPrimaryInstanceState(s, mpt, name) - if err != nil { - return err - } - - return testCheckResourceAttrSet(is, name, key) - } -} - -func testCheckResourceAttrSet(is *terraform.InstanceState, name string, key string) error { - if val, ok := is.Attributes[key]; !ok || val == "" { - return fmt.Errorf("%s: Attribute '%s' expected to be set", name, key) - } - - return nil -} - -// TestCheckResourceAttr is a TestCheckFunc which validates -// the value in state for the given name/key combination. -func TestCheckResourceAttr(name, key, value string) TestCheckFunc { - return func(s *terraform.State) error { - is, err := primaryInstanceState(s, name) - if err != nil { - return err - } - - return testCheckResourceAttr(is, name, key, value) - } -} - -// TestCheckModuleResourceAttr - as per TestCheckResourceAttr but with -// support for non-root modules -func TestCheckModuleResourceAttr(mp []string, name string, key string, value string) TestCheckFunc { - mpt := addrs.Module(mp).UnkeyedInstanceShim() - return func(s *terraform.State) error { - is, err := modulePathPrimaryInstanceState(s, mpt, name) - if err != nil { - return err - } - - return testCheckResourceAttr(is, name, key, value) - } -} - -func testCheckResourceAttr(is *terraform.InstanceState, name string, key string, value string) error { - // Empty containers may be elided from the state. - // If the intent here is to check for an empty container, allow the key to - // also be non-existent. - emptyCheck := false - if value == "0" && (strings.HasSuffix(key, ".#") || strings.HasSuffix(key, ".%")) { - emptyCheck = true - } - - if v, ok := is.Attributes[key]; !ok || v != value { - if emptyCheck && !ok { - return nil - } - - if !ok { - return fmt.Errorf("%s: Attribute '%s' not found", name, key) - } - - return fmt.Errorf( - "%s: Attribute '%s' expected %#v, got %#v", - name, - key, - value, - v) - } - return nil -} - -// TestCheckNoResourceAttr is a TestCheckFunc which ensures that -// NO value exists in state for the given name/key combination. -func TestCheckNoResourceAttr(name, key string) TestCheckFunc { - return func(s *terraform.State) error { - is, err := primaryInstanceState(s, name) - if err != nil { - return err - } - - return testCheckNoResourceAttr(is, name, key) - } -} - -// TestCheckModuleNoResourceAttr - as per TestCheckNoResourceAttr but with -// support for non-root modules -func TestCheckModuleNoResourceAttr(mp []string, name string, key string) TestCheckFunc { - mpt := addrs.Module(mp).UnkeyedInstanceShim() - return func(s *terraform.State) error { - is, err := modulePathPrimaryInstanceState(s, mpt, name) - if err != nil { - return err - } - - return testCheckNoResourceAttr(is, name, key) - } -} - -func testCheckNoResourceAttr(is *terraform.InstanceState, name string, key string) error { - // Empty containers may sometimes be included in the state. - // If the intent here is to check for an empty container, allow the value to - // also be "0". - emptyCheck := false - if strings.HasSuffix(key, ".#") || strings.HasSuffix(key, ".%") { - emptyCheck = true - } - - val, exists := is.Attributes[key] - if emptyCheck && val == "0" { - return nil - } - - if exists { - return fmt.Errorf("%s: Attribute '%s' found when not expected", name, key) - } - - return nil -} - -// TestMatchResourceAttr is a TestCheckFunc which checks that the value -// in state for the given name/key combination matches the given regex. -func TestMatchResourceAttr(name, key string, r *regexp.Regexp) TestCheckFunc { - return func(s *terraform.State) error { - is, err := primaryInstanceState(s, name) - if err != nil { - return err - } - - return testMatchResourceAttr(is, name, key, r) - } -} - -// TestModuleMatchResourceAttr - as per TestMatchResourceAttr but with -// support for non-root modules -func TestModuleMatchResourceAttr(mp []string, name string, key string, r *regexp.Regexp) TestCheckFunc { - mpt := addrs.Module(mp).UnkeyedInstanceShim() - return func(s *terraform.State) error { - is, err := modulePathPrimaryInstanceState(s, mpt, name) - if err != nil { - return err - } - - return testMatchResourceAttr(is, name, key, r) - } -} - -func testMatchResourceAttr(is *terraform.InstanceState, name string, key string, r *regexp.Regexp) error { - if !r.MatchString(is.Attributes[key]) { - return fmt.Errorf( - "%s: Attribute '%s' didn't match %q, got %#v", - name, - key, - r.String(), - is.Attributes[key]) - } - - return nil -} - -// TestCheckResourceAttrPtr is like TestCheckResourceAttr except the -// value is a pointer so that it can be updated while the test is running. -// It will only be dereferenced at the point this step is run. -func TestCheckResourceAttrPtr(name string, key string, value *string) TestCheckFunc { - return func(s *terraform.State) error { - return TestCheckResourceAttr(name, key, *value)(s) - } -} - -// TestCheckModuleResourceAttrPtr - as per TestCheckResourceAttrPtr but with -// support for non-root modules -func TestCheckModuleResourceAttrPtr(mp []string, name string, key string, value *string) TestCheckFunc { - return func(s *terraform.State) error { - return TestCheckModuleResourceAttr(mp, name, key, *value)(s) - } -} - -// TestCheckResourceAttrPair is a TestCheckFunc which validates that the values -// in state for a pair of name/key combinations are equal. -func TestCheckResourceAttrPair(nameFirst, keyFirst, nameSecond, keySecond string) TestCheckFunc { - return func(s *terraform.State) error { - isFirst, err := primaryInstanceState(s, nameFirst) - if err != nil { - return err - } - - isSecond, err := primaryInstanceState(s, nameSecond) - if err != nil { - return err - } - - return testCheckResourceAttrPair(isFirst, nameFirst, keyFirst, isSecond, nameSecond, keySecond) - } -} - -// TestCheckModuleResourceAttrPair - as per TestCheckResourceAttrPair but with -// support for non-root modules -func TestCheckModuleResourceAttrPair(mpFirst []string, nameFirst string, keyFirst string, mpSecond []string, nameSecond string, keySecond string) TestCheckFunc { - mptFirst := addrs.Module(mpFirst).UnkeyedInstanceShim() - mptSecond := addrs.Module(mpSecond).UnkeyedInstanceShim() - return func(s *terraform.State) error { - isFirst, err := modulePathPrimaryInstanceState(s, mptFirst, nameFirst) - if err != nil { - return err - } - - isSecond, err := modulePathPrimaryInstanceState(s, mptSecond, nameSecond) - if err != nil { - return err - } - - return testCheckResourceAttrPair(isFirst, nameFirst, keyFirst, isSecond, nameSecond, keySecond) - } -} - -func testCheckResourceAttrPair(isFirst *terraform.InstanceState, nameFirst string, keyFirst string, isSecond *terraform.InstanceState, nameSecond string, keySecond string) error { - vFirst, okFirst := isFirst.Attributes[keyFirst] - vSecond, okSecond := isSecond.Attributes[keySecond] - - // Container count values of 0 should not be relied upon, and not reliably - // maintained by helper/schema. For the purpose of tests, consider unset and - // 0 to be equal. - if len(keyFirst) > 2 && len(keySecond) > 2 && keyFirst[len(keyFirst)-2:] == keySecond[len(keySecond)-2:] && - (strings.HasSuffix(keyFirst, ".#") || strings.HasSuffix(keyFirst, ".%")) { - // they have the same suffix, and it is a collection count key. - if vFirst == "0" || vFirst == "" { - okFirst = false - } - if vSecond == "0" || vSecond == "" { - okSecond = false - } - } - - if okFirst != okSecond { - if !okFirst { - return fmt.Errorf("%s: Attribute %q not set, but %q is set in %s as %q", nameFirst, keyFirst, keySecond, nameSecond, vSecond) - } - return fmt.Errorf("%s: Attribute %q is %q, but %q is not set in %s", nameFirst, keyFirst, vFirst, keySecond, nameSecond) - } - if !(okFirst || okSecond) { - // If they both don't exist then they are equally unset, so that's okay. - return nil - } - - if vFirst != vSecond { - return fmt.Errorf( - "%s: Attribute '%s' expected %#v, got %#v", - nameFirst, - keyFirst, - vSecond, - vFirst) - } - - return nil -} - -// TestCheckOutput checks an output in the Terraform configuration -func TestCheckOutput(name, value string) TestCheckFunc { - return func(s *terraform.State) error { - ms := s.RootModule() - rs, ok := ms.Outputs[name] - if !ok { - return fmt.Errorf("Not found: %s", name) - } - - if rs.Value != value { - return fmt.Errorf( - "Output '%s': expected %#v, got %#v", - name, - value, - rs) - } - - return nil - } -} - -func TestMatchOutput(name string, r *regexp.Regexp) TestCheckFunc { - return func(s *terraform.State) error { - ms := s.RootModule() - rs, ok := ms.Outputs[name] - if !ok { - return fmt.Errorf("Not found: %s", name) - } - - if !r.MatchString(rs.Value.(string)) { - return fmt.Errorf( - "Output '%s': %#v didn't match %q", - name, - rs, - r.String()) - } - - return nil - } -} - -// TestT is the interface used to handle the test lifecycle of a test. -// -// Users should just use a *testing.T object, which implements this. -type TestT interface { - Error(args ...interface{}) - Fatal(args ...interface{}) - Skip(args ...interface{}) - Name() string - Parallel() -} - -// This is set to true by unit tests to alter some behavior -var testTesting = false - -// modulePrimaryInstanceState returns the instance state for the given resource -// name in a ModuleState -func modulePrimaryInstanceState(s *terraform.State, ms *terraform.ModuleState, name string) (*terraform.InstanceState, error) { - rs, ok := ms.Resources[name] - if !ok { - return nil, fmt.Errorf("Not found: %s in %s", name, ms.Path) - } - - is := rs.Primary - if is == nil { - return nil, fmt.Errorf("No primary instance: %s in %s", name, ms.Path) - } - - return is, nil -} - -// modulePathPrimaryInstanceState returns the primary instance state for the -// given resource name in a given module path. -func modulePathPrimaryInstanceState(s *terraform.State, mp addrs.ModuleInstance, name string) (*terraform.InstanceState, error) { - ms := s.ModuleByPath(mp) - if ms == nil { - return nil, fmt.Errorf("No module found at: %s", mp) - } - - return modulePrimaryInstanceState(s, ms, name) -} - -// primaryInstanceState returns the primary instance state for the given -// resource name in the root module. -func primaryInstanceState(s *terraform.State, name string) (*terraform.InstanceState, error) { - ms := s.RootModule() - return modulePrimaryInstanceState(s, ms, name) -} - -// operationError is a specialized implementation of error used to describe -// failures during one of the several operations performed for a particular -// test case. -type operationError struct { - OpName string - Diags tfdiags.Diagnostics -} - -func newOperationError(opName string, diags tfdiags.Diagnostics) error { - return operationError{opName, diags} -} - -// Error returns a terse error string containing just the basic diagnostic -// messages, for situations where normal Go error behavior is appropriate. -func (err operationError) Error() string { - return fmt.Sprintf("errors during %s: %s", err.OpName, err.Diags.Err().Error()) -} - -// ErrorDetail is like Error except it includes verbosely-rendered diagnostics -// similar to what would come from a normal Terraform run, which include -// additional context not included in Error(). -func (err operationError) ErrorDetail() string { - var buf bytes.Buffer - fmt.Fprintf(&buf, "errors during %s:", err.OpName) - clr := &colorstring.Colorize{Disable: true, Colors: colorstring.DefaultColors} - for _, diag := range err.Diags { - diagStr := format.Diagnostic(diag, nil, clr, 78) - buf.WriteByte('\n') - buf.WriteString(diagStr) - } - return buf.String() -} - -// detailedErrorMessage is a helper for calling ErrorDetail on an error if -// it is an operationError or just taking Error otherwise. -func detailedErrorMessage(err error) string { - switch tErr := err.(type) { - case operationError: - return tErr.ErrorDetail() - default: - return err.Error() - } -} diff --git a/helper/resource/testing_config.go b/helper/resource/testing_config.go deleted file mode 100644 index 74739c8a0..000000000 --- a/helper/resource/testing_config.go +++ /dev/null @@ -1,378 +0,0 @@ -package resource - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "log" - "sort" - "strings" - - "github.com/hashicorp/terraform/configs/hcl2shim" - "github.com/hashicorp/terraform/states" - - "github.com/hashicorp/errwrap" - "github.com/hashicorp/terraform/plans" - "github.com/hashicorp/terraform/terraform" - "github.com/hashicorp/terraform/tfdiags" -) - -// testStepConfig runs a config-mode test step -func testStepConfig( - opts terraform.ContextOpts, - state *terraform.State, - step TestStep) (*terraform.State, error) { - return testStep(opts, state, step) -} - -func testStep(opts terraform.ContextOpts, state *terraform.State, step TestStep) (*terraform.State, error) { - if !step.Destroy { - if err := testStepTaint(state, step); err != nil { - return state, err - } - } - - cfg, err := testConfig(opts, step) - if err != nil { - return state, err - } - - var stepDiags tfdiags.Diagnostics - - // Build the context - opts.Config = cfg - opts.State, err = shimLegacyState(state) - if err != nil { - return nil, err - } - - opts.Destroy = step.Destroy - ctx, stepDiags := terraform.NewContext(&opts) - if stepDiags.HasErrors() { - return state, fmt.Errorf("Error initializing context: %s", stepDiags.Err()) - } - if stepDiags := ctx.Validate(); len(stepDiags) > 0 { - if stepDiags.HasErrors() { - return state, errwrap.Wrapf("config is invalid: {{err}}", stepDiags.Err()) - } - - log.Printf("[WARN] Config warnings:\n%s", stepDiags) - } - - // If this step is a PlanOnly step, skip over this first Plan and subsequent - // Apply, and use the follow up Plan that checks for perpetual diffs - if !step.PlanOnly { - // Plan! - p, stepDiags := ctx.Plan() - if stepDiags.HasErrors() { - return state, newOperationError("plan", stepDiags) - } - - newState := p.State - log.Printf("[WARN] Test: Step plan: %s", legacyPlanComparisonString(newState, p.Changes)) - - // We need to keep a copy of the state prior to destroying - // such that destroy steps can verify their behavior in the check - // function - stateBeforeApplication := state.DeepCopy() - - // Apply the diff, creating real resources. - newState, stepDiags = ctx.Apply() - // shim the state first so the test can check the state on errors - state, err = shimNewState(newState, step.providers) - if err != nil { - return nil, err - } - if stepDiags.HasErrors() { - return state, newOperationError("apply", stepDiags) - } - - // Run any configured checks - if step.Check != nil { - if step.Destroy { - if err := step.Check(stateBeforeApplication); err != nil { - return state, fmt.Errorf("Check failed: %s", err) - } - } else { - if err := step.Check(state); err != nil { - return state, fmt.Errorf("Check failed: %s", err) - } - } - } - } - - // Now, verify that Plan is now empty and we don't have a perpetual diff issue - // We do this with TWO plans. One without a refresh. - p, stepDiags := ctx.Plan() - if stepDiags.HasErrors() { - return state, newOperationError("follow-up plan", stepDiags) - } - - // we don't technically need this any longer with plan handling refreshing, - // but run it anyway to ensure the context is working as expected. - p, stepDiags = ctx.Plan() - if stepDiags.HasErrors() { - return state, newOperationError("second follow-up plan", stepDiags) - } - empty := true - newState := p.State - - // the legacy tests never took outputs into account - for _, c := range p.Changes.Resources { - if c.Action != plans.NoOp { - empty = false - break - } - } - - if !empty { - if step.ExpectNonEmptyPlan { - log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", legacyPlanComparisonString(newState, p.Changes)) - } else { - return state, fmt.Errorf( - "After applying this step, the plan was not empty:\n\n%s", legacyPlanComparisonString(newState, p.Changes)) - } - } - - if !empty { - if step.ExpectNonEmptyPlan { - log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", legacyPlanComparisonString(newState, p.Changes)) - } else { - return state, fmt.Errorf( - "After applying this step and refreshing, "+ - "the plan was not empty:\n\n%s", legacyPlanComparisonString(newState, p.Changes)) - } - } - - // Made it here, but expected a non-empty plan, fail! - if step.ExpectNonEmptyPlan && empty { - return state, fmt.Errorf("Expected a non-empty plan, but got an empty plan!") - } - - // Made it here? Good job test step! - return state, nil -} - -// legacyPlanComparisonString produces a string representation of the changes -// from a plan and a given state togther, as was formerly produced by the -// String method of terraform.Plan. -// -// This is here only for compatibility with existing tests that predate our -// new plan and state types, and should not be used in new tests. Instead, use -// a library like "cmp" to do a deep equality and diff on the two -// data structures. -func legacyPlanComparisonString(state *states.State, changes *plans.Changes) string { - return fmt.Sprintf( - "DIFF:\n\n%s\n\nSTATE:\n\n%s", - legacyDiffComparisonString(changes), - state.String(), - ) -} - -// legacyDiffComparisonString produces a string representation of the changes -// from a planned changes object, as was formerly produced by the String method -// of terraform.Diff. -// -// This is here only for compatibility with existing tests that predate our -// new plan types, and should not be used in new tests. Instead, use a library -// like "cmp" to do a deep equality check and diff on the two data structures. -func legacyDiffComparisonString(changes *plans.Changes) string { - // The old string representation of a plan was grouped by module, but - // our new plan structure is not grouped in that way and so we'll need - // to preprocess it in order to produce that grouping. - type ResourceChanges struct { - Current *plans.ResourceInstanceChangeSrc - Deposed map[states.DeposedKey]*plans.ResourceInstanceChangeSrc - } - byModule := map[string]map[string]*ResourceChanges{} - resourceKeys := map[string][]string{} - requiresReplace := map[string][]string{} - var moduleKeys []string - for _, rc := range changes.Resources { - if rc.Action == plans.NoOp { - // We won't mention no-op changes here at all, since the old plan - // model we are emulating here didn't have such a concept. - continue - } - moduleKey := rc.Addr.Module.String() - if _, exists := byModule[moduleKey]; !exists { - moduleKeys = append(moduleKeys, moduleKey) - byModule[moduleKey] = make(map[string]*ResourceChanges) - } - resourceKey := rc.Addr.Resource.String() - if _, exists := byModule[moduleKey][resourceKey]; !exists { - resourceKeys[moduleKey] = append(resourceKeys[moduleKey], resourceKey) - byModule[moduleKey][resourceKey] = &ResourceChanges{ - Deposed: make(map[states.DeposedKey]*plans.ResourceInstanceChangeSrc), - } - } - - if rc.DeposedKey == states.NotDeposed { - byModule[moduleKey][resourceKey].Current = rc - } else { - byModule[moduleKey][resourceKey].Deposed[rc.DeposedKey] = rc - } - - rr := []string{} - for _, p := range rc.RequiredReplace.List() { - rr = append(rr, hcl2shim.FlatmapKeyFromPath(p)) - } - requiresReplace[resourceKey] = rr - } - sort.Strings(moduleKeys) - for _, ks := range resourceKeys { - sort.Strings(ks) - } - - var buf bytes.Buffer - - for _, moduleKey := range moduleKeys { - rcs := byModule[moduleKey] - var mBuf bytes.Buffer - - for _, resourceKey := range resourceKeys[moduleKey] { - rc := rcs[resourceKey] - - forceNewAttrs := requiresReplace[resourceKey] - - crud := "UPDATE" - if rc.Current != nil { - switch rc.Current.Action { - case plans.DeleteThenCreate: - crud = "DESTROY/CREATE" - case plans.CreateThenDelete: - crud = "CREATE/DESTROY" - case plans.Delete: - crud = "DESTROY" - case plans.Create: - crud = "CREATE" - } - } else { - // We must be working on a deposed object then, in which - // case destroying is the only possible action. - crud = "DESTROY" - } - - extra := "" - if rc.Current == nil && len(rc.Deposed) > 0 { - extra = " (deposed only)" - } - - fmt.Fprintf( - &mBuf, "%s: %s%s\n", - crud, resourceKey, extra, - ) - - attrNames := map[string]bool{} - var oldAttrs map[string]string - var newAttrs map[string]string - if rc.Current != nil { - if before := rc.Current.Before; before != nil { - ty, err := before.ImpliedType() - if err == nil { - val, err := before.Decode(ty) - if err == nil { - oldAttrs = hcl2shim.FlatmapValueFromHCL2(val) - for k := range oldAttrs { - attrNames[k] = true - } - } - } - } - if after := rc.Current.After; after != nil { - ty, err := after.ImpliedType() - if err == nil { - val, err := after.Decode(ty) - if err == nil { - newAttrs = hcl2shim.FlatmapValueFromHCL2(val) - for k := range newAttrs { - attrNames[k] = true - } - } - } - } - } - if oldAttrs == nil { - oldAttrs = make(map[string]string) - } - if newAttrs == nil { - newAttrs = make(map[string]string) - } - - attrNamesOrder := make([]string, 0, len(attrNames)) - keyLen := 0 - for n := range attrNames { - attrNamesOrder = append(attrNamesOrder, n) - if len(n) > keyLen { - keyLen = len(n) - } - } - sort.Strings(attrNamesOrder) - - for _, attrK := range attrNamesOrder { - v := newAttrs[attrK] - u := oldAttrs[attrK] - - if v == hcl2shim.UnknownVariableValue { - v = "" - } - // NOTE: we don't support here because we would - // need schema to do that. Excluding sensitive values - // is now done at the UI layer, and so should not be tested - // at the core layer. - - updateMsg := "" - - // This may not be as precise as in the old diff, as it matches - // everything under the attribute that was originally marked as - // ForceNew, but should help make it easier to determine what - // caused replacement here. - for _, k := range forceNewAttrs { - if strings.HasPrefix(attrK, k) { - updateMsg = " (forces new resource)" - break - } - } - - fmt.Fprintf( - &mBuf, " %s:%s %#v => %#v%s\n", - attrK, - strings.Repeat(" ", keyLen-len(attrK)), - u, v, - updateMsg, - ) - } - } - - if moduleKey == "" { // root module - buf.Write(mBuf.Bytes()) - buf.WriteByte('\n') - continue - } - - fmt.Fprintf(&buf, "%s:\n", moduleKey) - s := bufio.NewScanner(&mBuf) - for s.Scan() { - buf.WriteString(fmt.Sprintf(" %s\n", s.Text())) - } - } - - return buf.String() -} - -func testStepTaint(state *terraform.State, step TestStep) error { - for _, p := range step.Taint { - m := state.RootModule() - if m == nil { - return errors.New("no state") - } - rs, ok := m.Resources[p] - if !ok { - return fmt.Errorf("resource %q not found in state", p) - } - log.Printf("[WARN] Test: Explicitly tainting resource %q", p) - rs.Taint() - } - return nil -} diff --git a/helper/resource/testing_import_state.go b/helper/resource/testing_import_state.go deleted file mode 100644 index e163770e8..000000000 --- a/helper/resource/testing_import_state.go +++ /dev/null @@ -1,230 +0,0 @@ -package resource - -import ( - "fmt" - "log" - "reflect" - "strings" - - "github.com/davecgh/go-spew/spew" - "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/hcl/v2/hclsyntax" - - "github.com/hashicorp/terraform/addrs" - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/states" - "github.com/hashicorp/terraform/terraform" -) - -// testStepImportState runs an import state test step -func testStepImportState( - opts terraform.ContextOpts, - state *terraform.State, - step TestStep) (*terraform.State, error) { - - // Determine the ID to import - var importId string - switch { - case step.ImportStateIdFunc != nil: - var err error - importId, err = step.ImportStateIdFunc(state) - if err != nil { - return state, err - } - case step.ImportStateId != "": - importId = step.ImportStateId - default: - resource, err := testResource(step, state) - if err != nil { - return state, err - } - importId = resource.Primary.ID - } - - importPrefix := step.ImportStateIdPrefix - if importPrefix != "" { - importId = fmt.Sprintf("%s%s", importPrefix, importId) - } - - // Setup the context. We initialize with an empty state. We use the - // full config for provider configurations. - cfg, err := testConfig(opts, step) - if err != nil { - return state, err - } - - opts.Config = cfg - - // import tests start with empty state - opts.State = states.NewState() - - ctx, stepDiags := terraform.NewContext(&opts) - if stepDiags.HasErrors() { - return state, stepDiags.Err() - } - - // The test step provides the resource address as a string, so we need - // to parse it to get an addrs.AbsResourceAddress to pass in to the - // import method. - traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(step.ResourceName), "", hcl.Pos{}) - if hclDiags.HasErrors() { - return nil, hclDiags - } - importAddr, stepDiags := addrs.ParseAbsResourceInstance(traversal) - if stepDiags.HasErrors() { - return nil, stepDiags.Err() - } - - // Do the import - importedState, stepDiags := ctx.Import(&terraform.ImportOpts{ - Targets: []*terraform.ImportTarget{ - &terraform.ImportTarget{ - Addr: importAddr, - ID: importId, - }, - }, - }) - if stepDiags.HasErrors() { - log.Printf("[ERROR] Test: ImportState failure: %s", stepDiags.Err()) - return state, stepDiags.Err() - } - - newState, err := shimNewState(importedState, step.providers) - if err != nil { - return nil, err - } - // Go through the new state and verify - if step.ImportStateCheck != nil { - var states []*terraform.InstanceState - for _, r := range newState.RootModule().Resources { - if r.Primary != nil { - is := r.Primary.DeepCopy() - is.Ephemeral.Type = r.Type // otherwise the check function cannot see the type - states = append(states, is) - } - } - if err := step.ImportStateCheck(states); err != nil { - return state, err - } - } - - // Verify that all the states match - if step.ImportStateVerify { - new := newState.RootModule().Resources - old := state.RootModule().Resources - for _, r := range new { - // Find the existing resource - var oldR *terraform.ResourceState - for _, r2 := range old { - if r2.Primary != nil && r2.Primary.ID == r.Primary.ID && r2.Type == r.Type { - oldR = r2 - break - } - } - if oldR == nil { - return state, fmt.Errorf( - "Failed state verification, resource with ID %s not found", - r.Primary.ID) - } - - // We'll try our best to find the schema for this resource type - // so we can ignore Removed fields during validation. If we fail - // to find the schema then we won't ignore them and so the test - // will need to rely on explicit ImportStateVerifyIgnore, though - // this shouldn't happen in any reasonable case. - var rsrcSchema *schema.Resource - if providerAddr, diags := addrs.ParseAbsProviderConfigStr(r.Provider); !diags.HasErrors() { - // FIXME - providerType := providerAddr.Provider.Type - if provider, ok := step.providers[providerType]; ok { - if provider, ok := provider.(*schema.Provider); ok { - rsrcSchema = provider.ResourcesMap[r.Type] - } - } - } - - // don't add empty flatmapped containers, so we can more easily - // compare the attributes - skipEmpty := func(k, v string) bool { - if strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%") { - if v == "0" { - return true - } - } - return false - } - - // Compare their attributes - actual := make(map[string]string) - for k, v := range r.Primary.Attributes { - if skipEmpty(k, v) { - continue - } - actual[k] = v - } - - expected := make(map[string]string) - for k, v := range oldR.Primary.Attributes { - if skipEmpty(k, v) { - continue - } - expected[k] = v - } - - // Remove fields we're ignoring - for _, v := range step.ImportStateVerifyIgnore { - for k := range actual { - if strings.HasPrefix(k, v) { - delete(actual, k) - } - } - for k := range expected { - if strings.HasPrefix(k, v) { - delete(expected, k) - } - } - } - - // Also remove any attributes that are marked as "Removed" in the - // schema, if we have a schema to check that against. - if rsrcSchema != nil { - for k := range actual { - for _, schema := range rsrcSchema.SchemasForFlatmapPath(k) { - if schema.Removed != "" { - delete(actual, k) - break - } - } - } - for k := range expected { - for _, schema := range rsrcSchema.SchemasForFlatmapPath(k) { - if schema.Removed != "" { - delete(expected, k) - break - } - } - } - } - - if !reflect.DeepEqual(actual, expected) { - // Determine only the different attributes - for k, v := range expected { - if av, ok := actual[k]; ok && v == av { - delete(expected, k) - delete(actual, k) - } - } - - spewConf := spew.NewDefaultConfig() - spewConf.SortKeys = true - return state, fmt.Errorf( - "ImportStateVerify attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+ - "\n\n%s\n\n%s", - spewConf.Sdump(actual), spewConf.Sdump(expected)) - } - } - } - - // Return the old state (non-imported) so we don't change anything. - return state, nil -} diff --git a/helper/resource/testing_import_state_test.go b/helper/resource/testing_import_state_test.go deleted file mode 100644 index 9b2acc3c9..000000000 --- a/helper/resource/testing_import_state_test.go +++ /dev/null @@ -1,517 +0,0 @@ -package resource - -import ( - "errors" - "fmt" - "testing" - - "github.com/hashicorp/terraform/terraform" -) - -func TestTest_importState(t *testing.T) { - t.Skip("test requires new provider implementation") - - mp := testProvider() - mp.ImportStateReturn = []*terraform.InstanceState{ - &terraform.InstanceState{ - ID: "foo", - Ephemeral: terraform.EphemeralState{Type: "test_instance"}, - }, - } - mp.RefreshFn = func( - i *terraform.InstanceInfo, - s *terraform.InstanceState) (*terraform.InstanceState, error) { - return s, nil - } - - checked := false - checkFn := func(s []*terraform.InstanceState) error { - checked = true - - if s[0].ID != "foo" { - return fmt.Errorf("bad: %#v", s) - } - - return nil - } - - mt := new(mockT) - Test(mt, TestCase{ - Providers: map[string]terraform.ResourceProvider{ - "test": mp, - }, - - Steps: []TestStep{ - TestStep{ - Config: testConfigStrProvider, - ResourceName: "test_instance.foo", - ImportState: true, - ImportStateId: "foo", - ImportStateCheck: checkFn, - }, - }, - }) - - if mt.failed() { - t.Fatalf("test failed: %s", mt.failMessage()) - } - if !checked { - t.Fatal("didn't call check") - } -} - -func TestTest_importStateFail(t *testing.T) { - t.Skip("test requires new provider implementation") - - mp := testProvider() - mp.ImportStateReturn = []*terraform.InstanceState{ - &terraform.InstanceState{ - ID: "bar", - Ephemeral: terraform.EphemeralState{Type: "test_instance"}, - }, - } - mp.RefreshFn = func( - i *terraform.InstanceInfo, - s *terraform.InstanceState) (*terraform.InstanceState, error) { - return s, nil - } - - checked := false - checkFn := func(s []*terraform.InstanceState) error { - checked = true - - if s[0].ID != "foo" { - return fmt.Errorf("bad: %#v", s) - } - - return nil - } - - mt := new(mockT) - Test(mt, TestCase{ - Providers: map[string]terraform.ResourceProvider{ - "test": mp, - }, - - Steps: []TestStep{ - TestStep{ - Config: testConfigStrProvider, - ResourceName: "test_instance.foo", - ImportState: true, - ImportStateId: "foo", - ImportStateCheck: checkFn, - }, - }, - }) - - if !mt.failed() { - t.Fatal("should fail") - } - if !checked { - t.Fatal("didn't call check") - } -} - -func TestTest_importStateDetectId(t *testing.T) { - t.Skip("test requires new provider implementation") - - mp := testProvider() - mp.DiffReturn = nil - mp.ApplyFn = func( - info *terraform.InstanceInfo, - state *terraform.InstanceState, - diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { - if !diff.Destroy { - return &terraform.InstanceState{ - ID: "foo", - }, nil - } - - return nil, nil - } - - mp.RefreshFn = func( - i *terraform.InstanceInfo, - s *terraform.InstanceState) (*terraform.InstanceState, error) { - return s, nil - } - - mp.ImportStateFn = func( - info *terraform.InstanceInfo, id string) ([]*terraform.InstanceState, error) { - if id != "foo" { - return nil, fmt.Errorf("bad import ID: %s", id) - } - - return []*terraform.InstanceState{ - &terraform.InstanceState{ - ID: "bar", - Ephemeral: terraform.EphemeralState{Type: "test_instance"}, - }, - }, nil - } - - checked := false - checkFn := func(s []*terraform.InstanceState) error { - checked = true - - if s[0].ID != "bar" { - return fmt.Errorf("bad: %#v", s) - } - - return nil - } - - mt := new(mockT) - Test(mt, TestCase{ - Providers: map[string]terraform.ResourceProvider{ - "test": mp, - }, - - Steps: []TestStep{ - TestStep{ - Config: testConfigStr, - }, - TestStep{ - Config: testConfigStr, - ResourceName: "test_instance.foo", - ImportState: true, - ImportStateCheck: checkFn, - }, - }, - }) - - if mt.failed() { - t.Fatalf("test failed: %s", mt.failMessage()) - } - if !checked { - t.Fatal("didn't call check") - } -} - -func TestTest_importStateIdPrefix(t *testing.T) { - t.Skip("test requires new provider implementation") - - mp := testProvider() - mp.DiffReturn = nil - mp.ApplyFn = func( - info *terraform.InstanceInfo, - state *terraform.InstanceState, - diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { - if !diff.Destroy { - return &terraform.InstanceState{ - ID: "foo", - }, nil - } - - return nil, nil - } - - mp.RefreshFn = func( - i *terraform.InstanceInfo, - s *terraform.InstanceState) (*terraform.InstanceState, error) { - return s, nil - } - - mp.ImportStateFn = func( - info *terraform.InstanceInfo, id string) ([]*terraform.InstanceState, error) { - if id != "bazfoo" { - return nil, fmt.Errorf("bad import ID: %s", id) - } - - return []*terraform.InstanceState{ - { - ID: "bar", - Ephemeral: terraform.EphemeralState{Type: "test_instance"}, - }, - }, nil - } - - checked := false - checkFn := func(s []*terraform.InstanceState) error { - checked = true - - if s[0].ID != "bar" { - return fmt.Errorf("bad: %#v", s) - } - - return nil - } - - mt := new(mockT) - Test(mt, TestCase{ - Providers: map[string]terraform.ResourceProvider{ - "test": mp, - }, - - Steps: []TestStep{ - { - Config: testConfigStr, - }, - { - Config: testConfigStr, - ResourceName: "test_instance.foo", - ImportState: true, - ImportStateCheck: checkFn, - ImportStateIdPrefix: "baz", - }, - }, - }) - - if mt.failed() { - t.Fatalf("test failed: %s", mt.failMessage()) - } - if !checked { - t.Fatal("didn't call check") - } -} - -func TestTest_importStateVerify(t *testing.T) { - t.Skip("test requires new provider implementation") - - mp := testProvider() - mp.DiffReturn = nil - mp.ApplyFn = func( - info *terraform.InstanceInfo, - state *terraform.InstanceState, - diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { - if !diff.Destroy { - return &terraform.InstanceState{ - ID: "foo", - Attributes: map[string]string{ - "foo": "bar", - }, - }, nil - } - - return nil, nil - } - - mp.RefreshFn = func( - i *terraform.InstanceInfo, - s *terraform.InstanceState) (*terraform.InstanceState, error) { - if len(s.Attributes) == 0 { - s.Attributes = map[string]string{ - "id": s.ID, - "foo": "bar", - } - } - - return s, nil - } - - mp.ImportStateFn = func( - info *terraform.InstanceInfo, id string) ([]*terraform.InstanceState, error) { - if id != "foo" { - return nil, fmt.Errorf("bad import ID: %s", id) - } - - return []*terraform.InstanceState{ - &terraform.InstanceState{ - ID: "foo", - Ephemeral: terraform.EphemeralState{Type: "test_instance"}, - }, - }, nil - } - - mt := new(mockT) - Test(mt, TestCase{ - Providers: map[string]terraform.ResourceProvider{ - "test": mp, - }, - - Steps: []TestStep{ - TestStep{ - Config: testConfigStr, - }, - TestStep{ - Config: testConfigStr, - ResourceName: "test_instance.foo", - ImportState: true, - ImportStateVerify: true, - }, - }, - }) - - if mt.failed() { - t.Fatalf("test failed: %s", mt.failMessage()) - } -} - -func TestTest_importStateVerifyFail(t *testing.T) { - t.Skip("test requires new provider implementation") - - mp := testProvider() - mp.DiffReturn = nil - mp.ApplyFn = func( - info *terraform.InstanceInfo, - state *terraform.InstanceState, - diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { - if !diff.Destroy { - return &terraform.InstanceState{ - ID: "foo", - Attributes: map[string]string{ - "foo": "bar", - }, - }, nil - } - - return nil, nil - } - - mp.RefreshFn = func( - i *terraform.InstanceInfo, - s *terraform.InstanceState) (*terraform.InstanceState, error) { - return s, nil - } - - mp.ImportStateFn = func( - info *terraform.InstanceInfo, id string) ([]*terraform.InstanceState, error) { - if id != "foo" { - return nil, fmt.Errorf("bad import ID: %s", id) - } - - return []*terraform.InstanceState{ - &terraform.InstanceState{ - ID: "foo", - Ephemeral: terraform.EphemeralState{Type: "test_instance"}, - }, - }, nil - } - - mt := new(mockT) - Test(mt, TestCase{ - Providers: map[string]terraform.ResourceProvider{ - "test": mp, - }, - - Steps: []TestStep{ - TestStep{ - Config: testConfigStr, - }, - TestStep{ - Config: testConfigStr, - ResourceName: "test_instance.foo", - ImportState: true, - ImportStateVerify: true, - }, - }, - }) - - if !mt.failed() { - t.Fatalf("test should fail") - } -} - -func TestTest_importStateIdFunc(t *testing.T) { - t.Skip("test requires new provider implementation") - - mp := testProvider() - mp.ImportStateFn = func( - info *terraform.InstanceInfo, id string) ([]*terraform.InstanceState, error) { - if id != "foo:bar" { - return nil, fmt.Errorf("bad import ID: %s", id) - } - - return []*terraform.InstanceState{ - { - ID: "foo", - Ephemeral: terraform.EphemeralState{Type: "test_instance"}, - }, - }, nil - } - - mp.RefreshFn = func( - i *terraform.InstanceInfo, - s *terraform.InstanceState) (*terraform.InstanceState, error) { - return s, nil - } - - checked := false - checkFn := func(s []*terraform.InstanceState) error { - checked = true - - if s[0].ID != "foo" { - return fmt.Errorf("bad: %#v", s) - } - - return nil - } - - mt := new(mockT) - Test(mt, TestCase{ - Providers: map[string]terraform.ResourceProvider{ - "test": mp, - }, - - Steps: []TestStep{ - TestStep{ - Config: testConfigStrProvider, - ResourceName: "test_instance.foo", - ImportState: true, - ImportStateIdFunc: func(*terraform.State) (string, error) { return "foo:bar", nil }, - ImportStateCheck: checkFn, - }, - }, - }) - - if mt.failed() { - t.Fatalf("test failed: %s", mt.failMessage()) - } - if !checked { - t.Fatal("didn't call check") - } -} - -func TestTest_importStateIdFuncFail(t *testing.T) { - t.Skip("test requires new provider implementation") - - mp := testProvider() - mp.ImportStateFn = func( - info *terraform.InstanceInfo, id string) ([]*terraform.InstanceState, error) { - if id != "foo:bar" { - return nil, fmt.Errorf("bad import ID: %s", id) - } - - return []*terraform.InstanceState{ - { - ID: "foo", - Ephemeral: terraform.EphemeralState{Type: "test_instance"}, - }, - }, nil - } - - mp.RefreshFn = func( - i *terraform.InstanceInfo, - s *terraform.InstanceState) (*terraform.InstanceState, error) { - return s, nil - } - - checkFn := func(s []*terraform.InstanceState) error { - if s[0].ID != "foo" { - return fmt.Errorf("bad: %#v", s) - } - - return nil - } - - mt := new(mockT) - Test(mt, TestCase{ - Providers: map[string]terraform.ResourceProvider{ - "test": mp, - }, - - Steps: []TestStep{ - TestStep{ - Config: testConfigStrProvider, - ResourceName: "test_instance.foo", - ImportState: true, - ImportStateIdFunc: func(*terraform.State) (string, error) { return "foo:bar", errors.New("foobar") }, - ImportStateCheck: checkFn, - }, - }, - }) - - if !mt.failed() { - t.Fatalf("test should fail") - } -} diff --git a/helper/resource/testing_test.go b/helper/resource/testing_test.go deleted file mode 100644 index 5d4891778..000000000 --- a/helper/resource/testing_test.go +++ /dev/null @@ -1,1361 +0,0 @@ -package resource - -import ( - "errors" - "flag" - "fmt" - "os" - "reflect" - "regexp" - "sort" - "strings" - "sync" - "sync/atomic" - "testing" - - "github.com/hashicorp/go-multierror" - "github.com/hashicorp/terraform/terraform" -) - -func init() { - testTesting = true - - // TODO: Remove when we remove the guard on id checks - if err := os.Setenv("TF_ACC_IDONLY", "1"); err != nil { - panic(err) - } - - if err := os.Setenv(TestEnvVar, "1"); err != nil { - panic(err) - } -} - -// wrap the mock provider to implement TestProvider -type resetProvider struct { - *terraform.MockResourceProvider - mu sync.Mutex - TestResetCalled bool - TestResetError error -} - -func (p *resetProvider) TestReset() error { - p.mu.Lock() - defer p.mu.Unlock() - p.TestResetCalled = true - return p.TestResetError -} - -func TestParallelTest(t *testing.T) { - mt := new(mockT) - ParallelTest(mt, TestCase{}) - - if !mt.ParallelCalled { - t.Fatal("Parallel() not called") - } -} - -func TestTest(t *testing.T) { - t.Skip("test requires new provider implementation") - - mp := &resetProvider{ - MockResourceProvider: testProvider(), - } - - mp.DiffReturn = nil - - mp.ApplyFn = func( - info *terraform.InstanceInfo, - state *terraform.InstanceState, - diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { - if !diff.Destroy { - return &terraform.InstanceState{ - ID: "foo", - }, nil - } - - return nil, nil - } - - var refreshCount int32 - mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) { - atomic.AddInt32(&refreshCount, 1) - return &terraform.InstanceState{ID: "foo"}, nil - } - - checkDestroy := false - checkStep := false - - checkDestroyFn := func(*terraform.State) error { - checkDestroy = true - return nil - } - - checkStepFn := func(s *terraform.State) error { - checkStep = true - - rs, ok := s.RootModule().Resources["test_instance.foo"] - if !ok { - t.Error("test_instance.foo is not present") - return nil - } - is := rs.Primary - if is.ID != "foo" { - t.Errorf("bad check ID: %s", is.ID) - } - - return nil - } - - mt := new(mockT) - Test(mt, TestCase{ - Providers: map[string]terraform.ResourceProvider{ - "test": mp, - }, - CheckDestroy: checkDestroyFn, - Steps: []TestStep{ - TestStep{ - Config: testConfigStr, - Check: checkStepFn, - }, - }, - }) - - if mt.failed() { - t.Fatalf("test failed: %s", mt.failMessage()) - } - if mt.ParallelCalled { - t.Fatal("Parallel() called") - } - if !checkStep { - t.Fatal("didn't call check for step") - } - if !checkDestroy { - t.Fatal("didn't call check for destroy") - } - if !mp.TestResetCalled { - t.Fatal("didn't call TestReset") - } -} - -func TestTest_plan_only(t *testing.T) { - t.Skip("test requires new provider implementation") - - mp := testProvider() - mp.ApplyReturn = &terraform.InstanceState{ - ID: "foo", - } - - checkDestroy := false - - checkDestroyFn := func(*terraform.State) error { - checkDestroy = true - return nil - } - - mt := new(mockT) - Test(mt, TestCase{ - Providers: map[string]terraform.ResourceProvider{ - "test": mp, - }, - CheckDestroy: checkDestroyFn, - Steps: []TestStep{ - TestStep{ - Config: testConfigStr, - PlanOnly: true, - ExpectNonEmptyPlan: false, - }, - }, - }) - - if !mt.failed() { - t.Fatal("test should've failed") - } - - expected := `Step 0 error: After applying this step, the plan was not empty: - -DIFF: - -CREATE: test_instance.foo - foo: "" => "bar" - -STATE: - -` - - if mt.failMessage() != expected { - t.Fatalf("Expected message: %s\n\ngot:\n\n%s", expected, mt.failMessage()) - } - - if !checkDestroy { - t.Fatal("didn't call check for destroy") - } -} - -func TestTest_idRefresh(t *testing.T) { - t.Skip("test requires new provider implementation") - - // Refresh count should be 3: - // 1.) initial Ref/Plan/Apply - // 2.) post Ref/Plan/Apply for plan-check - // 3.) id refresh check - var expectedRefresh int32 = 3 - - mp := testProvider() - mp.DiffReturn = nil - - mp.ApplyFn = func( - info *terraform.InstanceInfo, - state *terraform.InstanceState, - diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { - if !diff.Destroy { - return &terraform.InstanceState{ - ID: "foo", - }, nil - } - - return nil, nil - } - - var refreshCount int32 - mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) { - atomic.AddInt32(&refreshCount, 1) - return &terraform.InstanceState{ID: "foo"}, nil - } - - mt := new(mockT) - Test(mt, TestCase{ - IDRefreshName: "test_instance.foo", - Providers: map[string]terraform.ResourceProvider{ - "test": mp, - }, - Steps: []TestStep{ - TestStep{ - Config: testConfigStr, - }, - }, - }) - - if mt.failed() { - t.Fatalf("test failed: %s", mt.failMessage()) - } - - // See declaration of expectedRefresh for why that number - if refreshCount != expectedRefresh { - t.Fatalf("bad refresh count: %d", refreshCount) - } -} - -func TestTest_idRefreshCustomName(t *testing.T) { - t.Skip("test requires new provider implementation") - - // Refresh count should be 3: - // 1.) initial Ref/Plan/Apply - // 2.) post Ref/Plan/Apply for plan-check - // 3.) id refresh check - var expectedRefresh int32 = 3 - - mp := testProvider() - mp.DiffReturn = nil - - mp.ApplyFn = func( - info *terraform.InstanceInfo, - state *terraform.InstanceState, - diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { - if !diff.Destroy { - return &terraform.InstanceState{ - ID: "foo", - }, nil - } - - return nil, nil - } - - var refreshCount int32 - mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) { - atomic.AddInt32(&refreshCount, 1) - return &terraform.InstanceState{ID: "foo"}, nil - } - - mt := new(mockT) - Test(mt, TestCase{ - IDRefreshName: "test_instance.foo", - Providers: map[string]terraform.ResourceProvider{ - "test": mp, - }, - Steps: []TestStep{ - TestStep{ - Config: testConfigStr, - }, - }, - }) - - if mt.failed() { - t.Fatalf("test failed: %s", mt.failMessage()) - } - - // See declaration of expectedRefresh for why that number - if refreshCount != expectedRefresh { - t.Fatalf("bad refresh count: %d", refreshCount) - } -} - -func TestTest_idRefreshFail(t *testing.T) { - t.Skip("test requires new provider implementation") - - // Refresh count should be 3: - // 1.) initial Ref/Plan/Apply - // 2.) post Ref/Plan/Apply for plan-check - // 3.) id refresh check - var expectedRefresh int32 = 3 - - mp := testProvider() - mp.DiffReturn = nil - - mp.ApplyFn = func( - info *terraform.InstanceInfo, - state *terraform.InstanceState, - diff *terraform.InstanceDiff) (*terraform.InstanceState, error) { - if !diff.Destroy { - return &terraform.InstanceState{ - ID: "foo", - }, nil - } - - return nil, nil - } - - var refreshCount int32 - mp.RefreshFn = func(*terraform.InstanceInfo, *terraform.InstanceState) (*terraform.InstanceState, error) { - atomic.AddInt32(&refreshCount, 1) - if atomic.LoadInt32(&refreshCount) == expectedRefresh-1 { - return &terraform.InstanceState{ - ID: "foo", - Attributes: map[string]string{"foo": "bar"}, - }, nil - } else if atomic.LoadInt32(&refreshCount) < expectedRefresh { - return &terraform.InstanceState{ID: "foo"}, nil - } else { - return nil, nil - } - } - - mt := new(mockT) - Test(mt, TestCase{ - IDRefreshName: "test_instance.foo", - Providers: map[string]terraform.ResourceProvider{ - "test": mp, - }, - Steps: []TestStep{ - TestStep{ - Config: testConfigStr, - }, - }, - }) - - if !mt.failed() { - t.Fatal("test didn't fail") - } - t.Logf("failure reason: %s", mt.failMessage()) - - // See declaration of expectedRefresh for why that number - if refreshCount != expectedRefresh { - t.Fatalf("bad refresh count: %d", refreshCount) - } -} - -func TestTest_empty(t *testing.T) { - t.Skip("test requires new provider implementation") - - destroyCalled := false - checkDestroyFn := func(*terraform.State) error { - destroyCalled = true - return nil - } - - mt := new(mockT) - Test(mt, TestCase{ - CheckDestroy: checkDestroyFn, - }) - - if mt.failed() { - t.Fatal("test failed") - } - if destroyCalled { - t.Fatal("should not call check destroy if there is no steps") - } -} - -func TestTest_noEnv(t *testing.T) { - t.Skip("test requires new provider implementation") - - // Unset the variable - if err := os.Setenv(TestEnvVar, ""); err != nil { - t.Fatalf("err: %s", err) - } - defer os.Setenv(TestEnvVar, "1") - - mt := new(mockT) - Test(mt, TestCase{}) - - if !mt.SkipCalled { - t.Fatal("skip not called") - } -} - -func TestTest_preCheck(t *testing.T) { - t.Skip("test requires new provider implementation") - - called := false - - mt := new(mockT) - Test(mt, TestCase{ - PreCheck: func() { called = true }, - }) - - if !called { - t.Fatal("precheck should be called") - } -} - -func TestTest_skipFunc(t *testing.T) { - t.Skip("test requires new provider implementation") - - preCheckCalled := false - skipped := false - - mp := testProvider() - mp.ApplyReturn = &terraform.InstanceState{ - ID: "foo", - } - - checkStepFn := func(*terraform.State) error { - return fmt.Errorf("error") - } - - mt := new(mockT) - Test(mt, TestCase{ - Providers: map[string]terraform.ResourceProvider{ - "test": mp, - }, - PreCheck: func() { preCheckCalled = true }, - Steps: []TestStep{ - { - Config: testConfigStr, - Check: checkStepFn, - SkipFunc: func() (bool, error) { skipped = true; return true, nil }, - }, - }, - }) - - if mt.failed() { - t.Fatal("Expected check to be skipped") - } - - if !preCheckCalled { - t.Fatal("precheck should be called") - } - if !skipped { - t.Fatal("SkipFunc should be called") - } -} - -func TestTest_stepError(t *testing.T) { - t.Skip("test requires new provider implementation") - - mp := testProvider() - mp.ApplyReturn = &terraform.InstanceState{ - ID: "foo", - } - - checkDestroy := false - - checkDestroyFn := func(*terraform.State) error { - checkDestroy = true - return nil - } - - checkStepFn := func(*terraform.State) error { - return fmt.Errorf("error") - } - - mt := new(mockT) - Test(mt, TestCase{ - Providers: map[string]terraform.ResourceProvider{ - "test": mp, - }, - CheckDestroy: checkDestroyFn, - Steps: []TestStep{ - TestStep{ - Config: testConfigStr, - Check: checkStepFn, - }, - }, - }) - - if !mt.failed() { - t.Fatal("test should've failed") - } - expected := "Step 0 error: Check failed: error" - if mt.failMessage() != expected { - t.Fatalf("Expected message: %s\n\ngot:\n\n%s", expected, mt.failMessage()) - } - - if !checkDestroy { - t.Fatal("didn't call check for destroy") - } -} - -func TestTest_factoryError(t *testing.T) { - resourceFactoryError := fmt.Errorf("resource factory error") - - factory := func() (terraform.ResourceProvider, error) { - return nil, resourceFactoryError - } - - mt := new(mockT) - Test(mt, TestCase{ - ProviderFactories: map[string]terraform.ResourceProviderFactory{ - "test": factory, - }, - Steps: []TestStep{ - TestStep{ - ExpectError: regexp.MustCompile("resource factory error"), - }, - }, - }) - - if !mt.failed() { - t.Fatal("test should've failed") - } -} - -func TestTest_resetError(t *testing.T) { - t.Skip("test requires new provider implementation") - - mp := &resetProvider{ - MockResourceProvider: testProvider(), - TestResetError: fmt.Errorf("provider reset error"), - } - - mt := new(mockT) - Test(mt, TestCase{ - Providers: map[string]terraform.ResourceProvider{ - "test": mp, - }, - Steps: []TestStep{ - TestStep{ - ExpectError: regexp.MustCompile("provider reset error"), - }, - }, - }) - - if !mt.failed() { - t.Fatal("test should've failed") - } -} - -func TestTest_expectError(t *testing.T) { - t.Skip("test requires new provider implementation") - - cases := []struct { - name string - planErr bool - applyErr bool - badErr bool - }{ - { - name: "successful apply", - planErr: false, - applyErr: false, - }, - { - name: "bad plan", - planErr: true, - applyErr: false, - }, - { - name: "bad apply", - planErr: false, - applyErr: true, - }, - { - name: "bad plan, bad err", - planErr: true, - applyErr: false, - badErr: true, - }, - { - name: "bad apply, bad err", - planErr: false, - applyErr: true, - badErr: true, - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - mp := testProvider() - expectedText := "test provider error" - var errText string - if tc.badErr { - errText = "wrong provider error" - } else { - errText = expectedText - } - noErrText := "no error received, but expected a match to" - if tc.planErr { - mp.DiffReturnError = errors.New(errText) - } - if tc.applyErr { - mp.ApplyReturnError = errors.New(errText) - } - mt := new(mockT) - Test(mt, TestCase{ - Providers: map[string]terraform.ResourceProvider{ - "test": mp, - }, - Steps: []TestStep{ - TestStep{ - Config: testConfigStr, - ExpectError: regexp.MustCompile(expectedText), - Check: func(*terraform.State) error { return nil }, - ExpectNonEmptyPlan: true, - }, - }, - }, - ) - if mt.FatalCalled { - t.Fatalf("fatal: %+v", mt.FatalArgs) - } - switch { - case len(mt.ErrorArgs) < 1 && !tc.planErr && !tc.applyErr: - t.Fatalf("expected error, got none") - case !tc.planErr && !tc.applyErr: - for _, e := range mt.ErrorArgs { - if regexp.MustCompile(noErrText).MatchString(fmt.Sprintf("%v", e)) { - return - } - } - t.Fatalf("expected error to match %s, got %+v", noErrText, mt.ErrorArgs) - case tc.badErr: - for _, e := range mt.ErrorArgs { - if regexp.MustCompile(expectedText).MatchString(fmt.Sprintf("%v", e)) { - return - } - } - t.Fatalf("expected error to match %s, got %+v", expectedText, mt.ErrorArgs) - } - }) - } -} - -func TestComposeAggregateTestCheckFunc(t *testing.T) { - check1 := func(s *terraform.State) error { - return errors.New("Error 1") - } - - check2 := func(s *terraform.State) error { - return errors.New("Error 2") - } - - f := ComposeAggregateTestCheckFunc(check1, check2) - err := f(nil) - if err == nil { - t.Fatalf("Expected errors") - } - - multi := err.(*multierror.Error) - if !strings.Contains(multi.Errors[0].Error(), "Error 1") { - t.Fatalf("Expected Error 1, Got %s", multi.Errors[0]) - } - if !strings.Contains(multi.Errors[1].Error(), "Error 2") { - t.Fatalf("Expected Error 2, Got %s", multi.Errors[1]) - } -} - -func TestComposeTestCheckFunc(t *testing.T) { - cases := []struct { - F []TestCheckFunc - Result string - }{ - { - F: []TestCheckFunc{ - func(*terraform.State) error { return nil }, - }, - Result: "", - }, - - { - F: []TestCheckFunc{ - func(*terraform.State) error { - return fmt.Errorf("error") - }, - func(*terraform.State) error { return nil }, - }, - Result: "Check 1/2 error: error", - }, - - { - F: []TestCheckFunc{ - func(*terraform.State) error { return nil }, - func(*terraform.State) error { - return fmt.Errorf("error") - }, - }, - Result: "Check 2/2 error: error", - }, - - { - F: []TestCheckFunc{ - func(*terraform.State) error { return nil }, - func(*terraform.State) error { return nil }, - }, - Result: "", - }, - } - - for i, tc := range cases { - f := ComposeTestCheckFunc(tc.F...) - err := f(nil) - if err == nil { - err = fmt.Errorf("") - } - if tc.Result != err.Error() { - t.Fatalf("Case %d bad: %s", i, err) - } - } -} - -// mockT implements TestT for testing -type mockT struct { - ErrorCalled bool - ErrorArgs []interface{} - FatalCalled bool - FatalArgs []interface{} - ParallelCalled bool - SkipCalled bool - SkipArgs []interface{} - - f bool -} - -func (t *mockT) Error(args ...interface{}) { - t.ErrorCalled = true - t.ErrorArgs = args - t.f = true -} - -func (t *mockT) Fatal(args ...interface{}) { - t.FatalCalled = true - t.FatalArgs = args - t.f = true -} - -func (t *mockT) Parallel() { - t.ParallelCalled = true -} - -func (t *mockT) Skip(args ...interface{}) { - t.SkipCalled = true - t.SkipArgs = args - t.f = true -} - -func (t *mockT) Name() string { - return "MockedName" -} - -func (t *mockT) failed() bool { - return t.f -} - -func (t *mockT) failMessage() string { - if t.FatalCalled { - return t.FatalArgs[0].(string) - } else if t.ErrorCalled { - return t.ErrorArgs[0].(string) - } else if t.SkipCalled { - return t.SkipArgs[0].(string) - } - - return "unknown" -} - -func testProvider() *terraform.MockResourceProvider { - mp := new(terraform.MockResourceProvider) - mp.DiffReturn = &terraform.InstanceDiff{ - Attributes: map[string]*terraform.ResourceAttrDiff{ - "foo": &terraform.ResourceAttrDiff{ - New: "bar", - }, - }, - } - mp.ResourcesReturn = []terraform.ResourceType{ - terraform.ResourceType{Name: "test_instance"}, - } - - return mp -} - -func TestTest_Main(t *testing.T) { - flag.Parse() - if *flagSweep == "" { - // Tests for the TestMain method used for Sweepers will panic without the -sweep - // flag specified. Mock the value for now - *flagSweep = "us-east-1" - } - - cases := []struct { - Name string - Sweepers map[string]*Sweeper - ExpectedRunList []string - SweepRun string - }{ - { - Name: "normal", - Sweepers: map[string]*Sweeper{ - "aws_dummy": &Sweeper{ - Name: "aws_dummy", - F: mockSweeperFunc, - }, - }, - ExpectedRunList: []string{"aws_dummy"}, - }, - { - Name: "with dep", - Sweepers: map[string]*Sweeper{ - "aws_dummy": &Sweeper{ - Name: "aws_dummy", - F: mockSweeperFunc, - }, - "aws_top": &Sweeper{ - Name: "aws_top", - Dependencies: []string{"aws_sub"}, - F: mockSweeperFunc, - }, - "aws_sub": &Sweeper{ - Name: "aws_sub", - F: mockSweeperFunc, - }, - }, - ExpectedRunList: []string{"aws_dummy", "aws_sub", "aws_top"}, - }, - { - Name: "with filter", - Sweepers: map[string]*Sweeper{ - "aws_dummy": &Sweeper{ - Name: "aws_dummy", - F: mockSweeperFunc, - }, - "aws_top": &Sweeper{ - Name: "aws_top", - Dependencies: []string{"aws_sub"}, - F: mockSweeperFunc, - }, - "aws_sub": &Sweeper{ - Name: "aws_sub", - F: mockSweeperFunc, - }, - }, - ExpectedRunList: []string{"aws_dummy"}, - SweepRun: "aws_dummy", - }, - { - Name: "with two filters", - Sweepers: map[string]*Sweeper{ - "aws_dummy": &Sweeper{ - Name: "aws_dummy", - F: mockSweeperFunc, - }, - "aws_top": &Sweeper{ - Name: "aws_top", - Dependencies: []string{"aws_sub"}, - F: mockSweeperFunc, - }, - "aws_sub": &Sweeper{ - Name: "aws_sub", - F: mockSweeperFunc, - }, - }, - ExpectedRunList: []string{"aws_dummy", "aws_sub"}, - SweepRun: "aws_dummy,aws_sub", - }, - { - Name: "with dep and filter", - Sweepers: map[string]*Sweeper{ - "aws_dummy": &Sweeper{ - Name: "aws_dummy", - F: mockSweeperFunc, - }, - "aws_top": &Sweeper{ - Name: "aws_top", - Dependencies: []string{"aws_sub"}, - F: mockSweeperFunc, - }, - "aws_sub": &Sweeper{ - Name: "aws_sub", - F: mockSweeperFunc, - }, - }, - ExpectedRunList: []string{"aws_top", "aws_sub"}, - SweepRun: "aws_top", - }, - { - Name: "filter and none", - Sweepers: map[string]*Sweeper{ - "aws_dummy": &Sweeper{ - Name: "aws_dummy", - F: mockSweeperFunc, - }, - "aws_top": &Sweeper{ - Name: "aws_top", - Dependencies: []string{"aws_sub"}, - F: mockSweeperFunc, - }, - "aws_sub": &Sweeper{ - Name: "aws_sub", - F: mockSweeperFunc, - }, - }, - SweepRun: "none", - }, - } - - for _, tc := range cases { - // reset sweepers - sweeperFuncs = map[string]*Sweeper{} - - t.Run(tc.Name, func(t *testing.T) { - for n, s := range tc.Sweepers { - AddTestSweepers(n, s) - } - *flagSweepRun = tc.SweepRun - - TestMain(&testing.M{}) - - // get list of tests ran from sweeperRunList keys - var keys []string - for k, _ := range sweeperRunList { - keys = append(keys, k) - } - - sort.Strings(keys) - sort.Strings(tc.ExpectedRunList) - if !reflect.DeepEqual(keys, tc.ExpectedRunList) { - t.Fatalf("Expected keys mismatch, expected:\n%#v\ngot:\n%#v\n", tc.ExpectedRunList, keys) - } - }) - } -} - -func mockSweeperFunc(s string) error { - return nil -} - -func TestTest_Taint(t *testing.T) { - t.Skip("test requires new provider implementation") - - mp := testProvider() - mp.DiffFn = func( - _ *terraform.InstanceInfo, - state *terraform.InstanceState, - _ *terraform.ResourceConfig, - ) (*terraform.InstanceDiff, error) { - return &terraform.InstanceDiff{ - DestroyTainted: state.Tainted, - }, nil - } - - mp.ApplyFn = func( - info *terraform.InstanceInfo, - state *terraform.InstanceState, - diff *terraform.InstanceDiff, - ) (*terraform.InstanceState, error) { - var id string - switch { - case diff.Destroy && !diff.DestroyTainted: - return nil, nil - case diff.DestroyTainted: - id = "tainted" - default: - id = "not_tainted" - } - - return &terraform.InstanceState{ - ID: id, - }, nil - } - - mp.RefreshFn = func( - _ *terraform.InstanceInfo, - state *terraform.InstanceState, - ) (*terraform.InstanceState, error) { - return state, nil - } - - mt := new(mockT) - Test(mt, TestCase{ - Providers: map[string]terraform.ResourceProvider{ - "test": mp, - }, - Steps: []TestStep{ - TestStep{ - Config: testConfigStr, - Check: func(s *terraform.State) error { - rs := s.RootModule().Resources["test_instance.foo"] - if rs.Primary.ID != "not_tainted" { - return fmt.Errorf("expected not_tainted, got %s", rs.Primary.ID) - } - return nil - }, - }, - TestStep{ - Taint: []string{"test_instance.foo"}, - Config: testConfigStr, - Check: func(s *terraform.State) error { - rs := s.RootModule().Resources["test_instance.foo"] - if rs.Primary.ID != "tainted" { - return fmt.Errorf("expected tainted, got %s", rs.Primary.ID) - } - return nil - }, - }, - TestStep{ - Taint: []string{"test_instance.fooo"}, - Config: testConfigStr, - ExpectError: regexp.MustCompile("resource \"test_instance.fooo\" not found in state"), - }, - }, - }) - - if mt.failed() { - t.Fatalf("test failure: %s", mt.failMessage()) - } -} - -const testConfigStr = ` -resource "test_instance" "foo" {} -` - -const testConfigStrProvider = ` -provider "test" {} -` - -func TestCheckResourceAttr_empty(t *testing.T) { - s := terraform.NewState() - s.AddModuleState(&terraform.ModuleState{ - Path: []string{"root"}, - Resources: map[string]*terraform.ResourceState{ - "test_resource": &terraform.ResourceState{ - Primary: &terraform.InstanceState{ - Attributes: map[string]string{ - "empty_list.#": "0", - "empty_map.%": "0", - }, - }, - }, - }, - }) - - for _, key := range []string{ - "empty_list.#", - "empty_map.%", - "missing_list.#", - "missing_map.%", - } { - t.Run(key, func(t *testing.T) { - check := TestCheckResourceAttr("test_resource", key, "0") - if err := check(s); err != nil { - t.Fatal(err) - } - }) - } -} - -func TestCheckNoResourceAttr_empty(t *testing.T) { - s := terraform.NewState() - s.AddModuleState(&terraform.ModuleState{ - Path: []string{"root"}, - Resources: map[string]*terraform.ResourceState{ - "test_resource": &terraform.ResourceState{ - Primary: &terraform.InstanceState{ - Attributes: map[string]string{ - "empty_list.#": "0", - "empty_map.%": "0", - }, - }, - }, - }, - }) - - for _, key := range []string{ - "empty_list.#", - "empty_map.%", - "missing_list.#", - "missing_map.%", - } { - t.Run(key, func(t *testing.T) { - check := TestCheckNoResourceAttr("test_resource", key) - if err := check(s); err != nil { - t.Fatal(err) - } - }) - } -} - -func TestTestCheckResourceAttrPair(t *testing.T) { - tests := map[string]struct { - state *terraform.State - wantErr string - }{ - "exist match": { - &terraform.State{ - Modules: []*terraform.ModuleState{ - { - Path: []string{"root"}, - Resources: map[string]*terraform.ResourceState{ - "test.a": { - Primary: &terraform.InstanceState{ - Attributes: map[string]string{ - "a": "boop", - }, - }, - }, - "test.b": { - Primary: &terraform.InstanceState{ - Attributes: map[string]string{ - "b": "boop", - }, - }, - }, - }, - }, - }, - }, - ``, - }, - "nonexist match": { - &terraform.State{ - Modules: []*terraform.ModuleState{ - { - Path: []string{"root"}, - Resources: map[string]*terraform.ResourceState{ - "test.a": { - Primary: &terraform.InstanceState{ - Attributes: map[string]string{}, - }, - }, - "test.b": { - Primary: &terraform.InstanceState{ - Attributes: map[string]string{}, - }, - }, - }, - }, - }, - }, - ``, - }, - "exist nonmatch": { - &terraform.State{ - Modules: []*terraform.ModuleState{ - { - Path: []string{"root"}, - Resources: map[string]*terraform.ResourceState{ - "test.a": { - Primary: &terraform.InstanceState{ - Attributes: map[string]string{ - "a": "beep", - }, - }, - }, - "test.b": { - Primary: &terraform.InstanceState{ - Attributes: map[string]string{ - "b": "boop", - }, - }, - }, - }, - }, - }, - }, - `test.a: Attribute 'a' expected "boop", got "beep"`, - }, - "inconsistent exist a": { - &terraform.State{ - Modules: []*terraform.ModuleState{ - { - Path: []string{"root"}, - Resources: map[string]*terraform.ResourceState{ - "test.a": { - Primary: &terraform.InstanceState{ - Attributes: map[string]string{ - "a": "beep", - }, - }, - }, - "test.b": { - Primary: &terraform.InstanceState{ - Attributes: map[string]string{}, - }, - }, - }, - }, - }, - }, - `test.a: Attribute "a" is "beep", but "b" is not set in test.b`, - }, - "inconsistent exist b": { - &terraform.State{ - Modules: []*terraform.ModuleState{ - { - Path: []string{"root"}, - Resources: map[string]*terraform.ResourceState{ - "test.a": { - Primary: &terraform.InstanceState{ - Attributes: map[string]string{}, - }, - }, - "test.b": { - Primary: &terraform.InstanceState{ - Attributes: map[string]string{ - "b": "boop", - }, - }, - }, - }, - }, - }, - }, - `test.a: Attribute "a" not set, but "b" is set in test.b as "boop"`, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - fn := TestCheckResourceAttrPair("test.a", "a", "test.b", "b") - err := fn(test.state) - - if test.wantErr != "" { - if err == nil { - t.Fatalf("succeeded; want error\nwant: %s", test.wantErr) - } - if got, want := err.Error(), test.wantErr; got != want { - t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) - } - return - } - - if err != nil { - t.Fatalf("failed; want success\ngot: %s", err.Error()) - } - }) - } -} - -func TestTestCheckResourceAttrPairCount(t *testing.T) { - tests := map[string]struct { - state *terraform.State - attr string - wantErr string - }{ - "unset and 0 equal list": { - &terraform.State{ - Modules: []*terraform.ModuleState{ - { - Path: []string{"root"}, - Resources: map[string]*terraform.ResourceState{ - "test.a": { - Primary: &terraform.InstanceState{ - Attributes: map[string]string{ - "a.#": "0", - }, - }, - }, - "test.b": { - Primary: &terraform.InstanceState{ - Attributes: map[string]string{}, - }, - }, - }, - }, - }, - }, - "a.#", - ``, - }, - "unset and 0 equal map": { - &terraform.State{ - Modules: []*terraform.ModuleState{ - { - Path: []string{"root"}, - Resources: map[string]*terraform.ResourceState{ - "test.a": { - Primary: &terraform.InstanceState{ - Attributes: map[string]string{ - "a.%": "0", - }, - }, - }, - "test.b": { - Primary: &terraform.InstanceState{ - Attributes: map[string]string{}, - }, - }, - }, - }, - }, - }, - "a.%", - ``, - }, - "count equal": { - &terraform.State{ - Modules: []*terraform.ModuleState{ - { - Path: []string{"root"}, - Resources: map[string]*terraform.ResourceState{ - "test.a": { - Primary: &terraform.InstanceState{ - Attributes: map[string]string{ - "a.%": "1", - }, - }, - }, - "test.b": { - Primary: &terraform.InstanceState{ - Attributes: map[string]string{ - "a.%": "1", - }}, - }, - }, - }, - }, - }, - "a.%", - ``, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - fn := TestCheckResourceAttrPair("test.a", test.attr, "test.b", test.attr) - err := fn(test.state) - - if test.wantErr != "" { - if err == nil { - t.Fatalf("succeeded; want error\nwant: %s", test.wantErr) - } - if got, want := err.Error(), test.wantErr; got != want { - t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) - } - return - } - - if err != nil { - t.Fatalf("failed; want success\ngot: %s", err.Error()) - } - }) - } -} diff --git a/helper/resource/wait.go b/helper/resource/wait.go deleted file mode 100644 index e56a5155d..000000000 --- a/helper/resource/wait.go +++ /dev/null @@ -1,84 +0,0 @@ -package resource - -import ( - "sync" - "time" -) - -// Retry is a basic wrapper around StateChangeConf that will just retry -// a function until it no longer returns an error. -func Retry(timeout time.Duration, f RetryFunc) error { - // These are used to pull the error out of the function; need a mutex to - // avoid a data race. - var resultErr error - var resultErrMu sync.Mutex - - c := &StateChangeConf{ - Pending: []string{"retryableerror"}, - Target: []string{"success"}, - Timeout: timeout, - MinTimeout: 500 * time.Millisecond, - Refresh: func() (interface{}, string, error) { - rerr := f() - - resultErrMu.Lock() - defer resultErrMu.Unlock() - - if rerr == nil { - resultErr = nil - return 42, "success", nil - } - - resultErr = rerr.Err - - if rerr.Retryable { - return 42, "retryableerror", nil - } - return nil, "quit", rerr.Err - }, - } - - _, waitErr := c.WaitForState() - - // Need to acquire the lock here to be able to avoid race using resultErr as - // the return value - resultErrMu.Lock() - defer resultErrMu.Unlock() - - // resultErr may be nil because the wait timed out and resultErr was never - // set; this is still an error - if resultErr == nil { - return waitErr - } - // resultErr takes precedence over waitErr if both are set because it is - // more likely to be useful - return resultErr -} - -// RetryFunc is the function retried until it succeeds. -type RetryFunc func() *RetryError - -// RetryError is the required return type of RetryFunc. It forces client code -// to choose whether or not a given error is retryable. -type RetryError struct { - Err error - Retryable bool -} - -// RetryableError is a helper to create a RetryError that's retryable from a -// given error. -func RetryableError(err error) *RetryError { - if err == nil { - return nil - } - return &RetryError{Err: err, Retryable: true} -} - -// NonRetryableError is a helper to create a RetryError that's _not_ retryable -// from a given error. -func NonRetryableError(err error) *RetryError { - if err == nil { - return nil - } - return &RetryError{Err: err, Retryable: false} -} diff --git a/helper/resource/wait_test.go b/helper/resource/wait_test.go deleted file mode 100644 index 526b21ae3..000000000 --- a/helper/resource/wait_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package resource - -import ( - "fmt" - "testing" - "time" -) - -func TestRetry(t *testing.T) { - t.Parallel() - - tries := 0 - f := func() *RetryError { - tries++ - if tries == 3 { - return nil - } - - return RetryableError(fmt.Errorf("error")) - } - - err := Retry(10*time.Second, f) - if err != nil { - t.Fatalf("err: %s", err) - } -} - -// make sure a slow StateRefreshFunc is allowed to complete after timeout -func TestRetry_grace(t *testing.T) { - t.Parallel() - - f := func() *RetryError { - time.Sleep(1 * time.Second) - return nil - } - - err := Retry(10*time.Millisecond, f) - if err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestRetry_timeout(t *testing.T) { - t.Parallel() - - f := func() *RetryError { - return RetryableError(fmt.Errorf("always")) - } - - err := Retry(1*time.Second, f) - if err == nil { - t.Fatal("should error") - } -} - -func TestRetry_hang(t *testing.T) { - old := refreshGracePeriod - refreshGracePeriod = 50 * time.Millisecond - defer func() { - refreshGracePeriod = old - }() - - f := func() *RetryError { - time.Sleep(2 * time.Second) - return nil - } - - err := Retry(50*time.Millisecond, f) - if err == nil { - t.Fatal("should error") - } -} - -func TestRetry_error(t *testing.T) { - t.Parallel() - - expected := fmt.Errorf("nope") - f := func() *RetryError { - return NonRetryableError(expected) - } - - errCh := make(chan error) - go func() { - errCh <- Retry(1*time.Second, f) - }() - - select { - case err := <-errCh: - if err != expected { - t.Fatalf("bad: %#v", err) - } - case <-time.After(5 * time.Second): - t.Fatal("timeout") - } -} diff --git a/helper/validation/validation.go b/helper/validation/validation.go deleted file mode 100644 index 484f7d7da..000000000 --- a/helper/validation/validation.go +++ /dev/null @@ -1,49 +0,0 @@ -package validation - -import ( - "fmt" - "strings" - - "github.com/hashicorp/terraform/helper/schema" -) - -// IntBetween returns a SchemaValidateFunc which tests if the provided value -// is of type int and is between min and max (inclusive) -func IntBetween(min, max int) schema.SchemaValidateFunc { - return func(i interface{}, k string) (s []string, es []error) { - v, ok := i.(int) - if !ok { - es = append(es, fmt.Errorf("expected type of %s to be int", k)) - return - } - - if v < min || v > max { - es = append(es, fmt.Errorf("expected %s to be in the range (%d - %d), got %d", k, min, max, v)) - return - } - - return - } -} - -// StringInSlice returns a SchemaValidateFunc which tests if the provided value -// is of type string and matches the value of an element in the valid slice -// will test with in lower case if ignoreCase is true -func StringInSlice(valid []string, ignoreCase bool) schema.SchemaValidateFunc { - return func(i interface{}, k string) (s []string, es []error) { - v, ok := i.(string) - if !ok { - es = append(es, fmt.Errorf("expected type of %s to be string", k)) - return - } - - for _, str := range valid { - if v == str || (ignoreCase && strings.ToLower(v) == strings.ToLower(str)) { - return - } - } - - es = append(es, fmt.Errorf("expected %s to be one of %v, got %s", k, valid, v)) - return - } -} diff --git a/helper/validation/validation_test.go b/helper/validation/validation_test.go deleted file mode 100644 index b47fe7824..000000000 --- a/helper/validation/validation_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package validation - -import ( - "regexp" - "testing" - - "github.com/hashicorp/terraform/helper/schema" -) - -type testCase struct { - val interface{} - f schema.SchemaValidateFunc - expectedErr *regexp.Regexp -} - -func TestValidationIntBetween(t *testing.T) { - runTestCases(t, []testCase{ - { - val: 1, - f: IntBetween(1, 1), - }, - { - val: 1, - f: IntBetween(0, 2), - }, - { - val: 1, - f: IntBetween(2, 3), - expectedErr: regexp.MustCompile("expected [\\w]+ to be in the range \\(2 - 3\\), got 1"), - }, - { - val: "1", - f: IntBetween(2, 3), - expectedErr: regexp.MustCompile("expected type of [\\w]+ to be int"), - }, - }) -} - -func TestValidationStringInSlice(t *testing.T) { - runTestCases(t, []testCase{ - { - val: "ValidValue", - f: StringInSlice([]string{"ValidValue", "AnotherValidValue"}, false), - }, - // ignore case - { - val: "VALIDVALUE", - f: StringInSlice([]string{"ValidValue", "AnotherValidValue"}, true), - }, - { - val: "VALIDVALUE", - f: StringInSlice([]string{"ValidValue", "AnotherValidValue"}, false), - expectedErr: regexp.MustCompile("expected [\\w]+ to be one of \\[ValidValue AnotherValidValue\\], got VALIDVALUE"), - }, - { - val: "InvalidValue", - f: StringInSlice([]string{"ValidValue", "AnotherValidValue"}, false), - expectedErr: regexp.MustCompile("expected [\\w]+ to be one of \\[ValidValue AnotherValidValue\\], got InvalidValue"), - }, - { - val: 1, - f: StringInSlice([]string{"ValidValue", "AnotherValidValue"}, false), - expectedErr: regexp.MustCompile("expected type of [\\w]+ to be string"), - }, - }) -} - -func runTestCases(t *testing.T, cases []testCase) { - matchErr := func(errs []error, r *regexp.Regexp) bool { - // err must match one provided - for _, err := range errs { - if r.MatchString(err.Error()) { - return true - } - } - - return false - } - - for i, tc := range cases { - _, errs := tc.f(tc.val, "test_property") - - if len(errs) == 0 && tc.expectedErr == nil { - continue - } - - if len(errs) != 0 && tc.expectedErr == nil { - t.Fatalf("expected test case %d to produce no errors, got %v", i, errs) - } - - if !matchErr(errs, tc.expectedErr) { - t.Fatalf("expected test case %d to produce error matching \"%s\", got %v", i, tc.expectedErr, errs) - } - } -} diff --git a/builtin/bins/provider-test/main.go b/internal/legacy/builtin/bins/provider-test/main.go similarity index 60% rename from builtin/bins/provider-test/main.go rename to internal/legacy/builtin/bins/provider-test/main.go index 97d03f258..eb6d3f796 100644 --- a/builtin/bins/provider-test/main.go +++ b/internal/legacy/builtin/bins/provider-test/main.go @@ -1,9 +1,9 @@ package main import ( - "github.com/hashicorp/terraform/builtin/providers/test" + "github.com/hashicorp/terraform/internal/legacy/builtin/providers/test" + "github.com/hashicorp/terraform/internal/legacy/terraform" "github.com/hashicorp/terraform/plugin" - "github.com/hashicorp/terraform/terraform" ) func main() { diff --git a/builtin/providers/test/data_source.go b/internal/legacy/builtin/providers/test/data_source.go similarity index 95% rename from builtin/providers/test/data_source.go rename to internal/legacy/builtin/providers/test/data_source.go index 2a735703f..11ff28a2f 100644 --- a/builtin/providers/test/data_source.go +++ b/internal/legacy/builtin/providers/test/data_source.go @@ -3,7 +3,7 @@ package test import ( "time" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) func testDataSource() *schema.Resource { diff --git a/builtin/providers/test/data_source_label.go b/internal/legacy/builtin/providers/test/data_source_label.go similarity index 86% rename from builtin/providers/test/data_source_label.go rename to internal/legacy/builtin/providers/test/data_source_label.go index 40f3bad58..8e97d2fed 100644 --- a/builtin/providers/test/data_source_label.go +++ b/internal/legacy/builtin/providers/test/data_source_label.go @@ -1,7 +1,7 @@ package test import ( - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) func providerLabelDataSource() *schema.Resource { diff --git a/builtin/providers/test/provider.go b/internal/legacy/builtin/providers/test/provider.go similarity index 94% rename from builtin/providers/test/provider.go rename to internal/legacy/builtin/providers/test/provider.go index 1066f37c2..4ca826898 100644 --- a/builtin/providers/test/provider.go +++ b/internal/legacy/builtin/providers/test/provider.go @@ -1,8 +1,8 @@ package test import ( - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) func Provider() terraform.ResourceProvider { diff --git a/builtin/providers/test/resource.go b/internal/legacy/builtin/providers/test/resource.go similarity index 98% rename from builtin/providers/test/resource.go rename to internal/legacy/builtin/providers/test/resource.go index b05fcc681..46b3a1cb5 100644 --- a/builtin/providers/test/resource.go +++ b/internal/legacy/builtin/providers/test/resource.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) func testResource() *schema.Resource { diff --git a/builtin/providers/test/resource_computed_set.go b/internal/legacy/builtin/providers/test/resource_computed_set.go similarity index 97% rename from builtin/providers/test/resource_computed_set.go rename to internal/legacy/builtin/providers/test/resource_computed_set.go index 092cd2276..fa2035c8d 100644 --- a/builtin/providers/test/resource_computed_set.go +++ b/internal/legacy/builtin/providers/test/resource_computed_set.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/hashicorp/terraform/helper/hashcode" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) func testResourceComputedSet() *schema.Resource { diff --git a/builtin/providers/test/resource_config_mode.go b/internal/legacy/builtin/providers/test/resource_config_mode.go similarity index 96% rename from builtin/providers/test/resource_config_mode.go rename to internal/legacy/builtin/providers/test/resource_config_mode.go index 82b476039..bb1da1550 100644 --- a/builtin/providers/test/resource_config_mode.go +++ b/internal/legacy/builtin/providers/test/resource_config_mode.go @@ -3,7 +3,7 @@ package test import ( "fmt" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) func testResourceConfigMode() *schema.Resource { diff --git a/builtin/providers/test/resource_defaults.go b/internal/legacy/builtin/providers/test/resource_defaults.go similarity index 95% rename from builtin/providers/test/resource_defaults.go rename to internal/legacy/builtin/providers/test/resource_defaults.go index 41038de68..f46a4bb02 100644 --- a/builtin/providers/test/resource_defaults.go +++ b/internal/legacy/builtin/providers/test/resource_defaults.go @@ -4,7 +4,7 @@ import ( "fmt" "math/rand" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) func testResourceDefaults() *schema.Resource { diff --git a/builtin/providers/test/resource_deprecated.go b/internal/legacy/builtin/providers/test/resource_deprecated.go similarity index 97% rename from builtin/providers/test/resource_deprecated.go rename to internal/legacy/builtin/providers/test/resource_deprecated.go index a176977b9..d78722208 100644 --- a/builtin/providers/test/resource_deprecated.go +++ b/internal/legacy/builtin/providers/test/resource_deprecated.go @@ -1,7 +1,7 @@ package test import ( - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) func testResourceDeprecated() *schema.Resource { diff --git a/builtin/providers/test/resource_diff_suppress.go b/internal/legacy/builtin/providers/test/resource_diff_suppress.go similarity index 97% rename from builtin/providers/test/resource_diff_suppress.go rename to internal/legacy/builtin/providers/test/resource_diff_suppress.go index f5cfc9331..309cc4488 100644 --- a/builtin/providers/test/resource_diff_suppress.go +++ b/internal/legacy/builtin/providers/test/resource_diff_suppress.go @@ -5,7 +5,7 @@ import ( "math/rand" "strings" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) func testResourceDiffSuppress() *schema.Resource { diff --git a/builtin/providers/test/resource_force_new.go b/internal/legacy/builtin/providers/test/resource_force_new.go similarity index 92% rename from builtin/providers/test/resource_force_new.go rename to internal/legacy/builtin/providers/test/resource_force_new.go index 81a06736c..85721141e 100644 --- a/builtin/providers/test/resource_force_new.go +++ b/internal/legacy/builtin/providers/test/resource_force_new.go @@ -1,7 +1,7 @@ package test import ( - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) func testResourceForceNew() *schema.Resource { diff --git a/builtin/providers/test/resource_gh12183.go b/internal/legacy/builtin/providers/test/resource_gh12183.go similarity index 95% rename from builtin/providers/test/resource_gh12183.go rename to internal/legacy/builtin/providers/test/resource_gh12183.go index d67bcf755..f75fbe2e9 100644 --- a/builtin/providers/test/resource_gh12183.go +++ b/internal/legacy/builtin/providers/test/resource_gh12183.go @@ -1,7 +1,7 @@ package test import ( - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) // This is a test resource to help reproduce GH-12183. This issue came up diff --git a/builtin/providers/test/resource_list.go b/internal/legacy/builtin/providers/test/resource_list.go similarity index 98% rename from builtin/providers/test/resource_list.go rename to internal/legacy/builtin/providers/test/resource_list.go index 895298ebb..9c69f915c 100644 --- a/builtin/providers/test/resource_list.go +++ b/internal/legacy/builtin/providers/test/resource_list.go @@ -1,7 +1,7 @@ package test import ( - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) func testResourceList() *schema.Resource { diff --git a/builtin/providers/test/resource_list_set.go b/internal/legacy/builtin/providers/test/resource_list_set.go similarity index 98% rename from builtin/providers/test/resource_list_set.go rename to internal/legacy/builtin/providers/test/resource_list_set.go index 0ce5abc8d..fe54d9235 100644 --- a/builtin/providers/test/resource_list_set.go +++ b/internal/legacy/builtin/providers/test/resource_list_set.go @@ -4,7 +4,7 @@ import ( "fmt" "math/rand" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) func testResourceListSet() *schema.Resource { diff --git a/builtin/providers/test/resource_map.go b/internal/legacy/builtin/providers/test/resource_map.go similarity index 96% rename from builtin/providers/test/resource_map.go rename to internal/legacy/builtin/providers/test/resource_map.go index c6bf62bd6..fe1ece1e9 100644 --- a/builtin/providers/test/resource_map.go +++ b/internal/legacy/builtin/providers/test/resource_map.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/hashicorp/terraform/configs/hcl2shim" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) func testResourceMap() *schema.Resource { diff --git a/builtin/providers/test/resource_nested.go b/internal/legacy/builtin/providers/test/resource_nested.go similarity index 97% rename from builtin/providers/test/resource_nested.go rename to internal/legacy/builtin/providers/test/resource_nested.go index bff743259..c78274d53 100644 --- a/builtin/providers/test/resource_nested.go +++ b/internal/legacy/builtin/providers/test/resource_nested.go @@ -4,7 +4,7 @@ import ( "fmt" "math/rand" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) func testResourceNested() *schema.Resource { diff --git a/builtin/providers/test/resource_nested_id.go b/internal/legacy/builtin/providers/test/resource_nested_id.go similarity index 93% rename from builtin/providers/test/resource_nested_id.go rename to internal/legacy/builtin/providers/test/resource_nested_id.go index c3bd41974..f5a2d9bf9 100644 --- a/builtin/providers/test/resource_nested_id.go +++ b/internal/legacy/builtin/providers/test/resource_nested_id.go @@ -1,7 +1,7 @@ package test import ( - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) func testResourceNestedId() *schema.Resource { diff --git a/builtin/providers/test/resource_nested_set.go b/internal/legacy/builtin/providers/test/resource_nested_set.go similarity index 98% rename from builtin/providers/test/resource_nested_set.go rename to internal/legacy/builtin/providers/test/resource_nested_set.go index 81d7ab0f5..4a1253400 100644 --- a/builtin/providers/test/resource_nested_set.go +++ b/internal/legacy/builtin/providers/test/resource_nested_set.go @@ -4,7 +4,7 @@ import ( "fmt" "math/rand" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) func testResourceNestedSet() *schema.Resource { diff --git a/builtin/providers/test/resource_provider_meta.go b/internal/legacy/builtin/providers/test/resource_provider_meta.go similarity index 96% rename from builtin/providers/test/resource_provider_meta.go rename to internal/legacy/builtin/providers/test/resource_provider_meta.go index c05adb170..c900c5159 100644 --- a/builtin/providers/test/resource_provider_meta.go +++ b/internal/legacy/builtin/providers/test/resource_provider_meta.go @@ -3,7 +3,7 @@ package test import ( "fmt" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) func testResourceProviderMeta() *schema.Resource { diff --git a/builtin/providers/test/resource_required_min.go b/internal/legacy/builtin/providers/test/resource_required_min.go similarity index 95% rename from builtin/providers/test/resource_required_min.go rename to internal/legacy/builtin/providers/test/resource_required_min.go index 413d4c51d..23419ed02 100644 --- a/builtin/providers/test/resource_required_min.go +++ b/internal/legacy/builtin/providers/test/resource_required_min.go @@ -1,7 +1,7 @@ package test import ( - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) func testResourceRequiredMin() *schema.Resource { diff --git a/builtin/providers/test/resource_signal.go b/internal/legacy/builtin/providers/test/resource_signal.go similarity index 93% rename from builtin/providers/test/resource_signal.go rename to internal/legacy/builtin/providers/test/resource_signal.go index 57e4bf0eb..398a996cd 100644 --- a/builtin/providers/test/resource_signal.go +++ b/internal/legacy/builtin/providers/test/resource_signal.go @@ -1,7 +1,7 @@ package test import ( - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) func testResourceSignal() *schema.Resource { diff --git a/builtin/providers/test/resource_state_func.go b/internal/legacy/builtin/providers/test/resource_state_func.go similarity index 97% rename from builtin/providers/test/resource_state_func.go rename to internal/legacy/builtin/providers/test/resource_state_func.go index 609e5ea53..9589f102e 100644 --- a/builtin/providers/test/resource_state_func.go +++ b/internal/legacy/builtin/providers/test/resource_state_func.go @@ -7,7 +7,7 @@ import ( "math/rand" "github.com/hashicorp/terraform/helper/hashcode" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) func testResourceStateFunc() *schema.Resource { diff --git a/builtin/providers/test/resource_timeout.go b/internal/legacy/builtin/providers/test/resource_timeout.go similarity index 97% rename from builtin/providers/test/resource_timeout.go rename to internal/legacy/builtin/providers/test/resource_timeout.go index a10717550..17a6da0eb 100644 --- a/builtin/providers/test/resource_timeout.go +++ b/internal/legacy/builtin/providers/test/resource_timeout.go @@ -4,7 +4,7 @@ import ( "fmt" "time" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) func testResourceTimeout() *schema.Resource { diff --git a/builtin/providers/test/resource_undeletable.go b/internal/legacy/builtin/providers/test/resource_undeletable.go similarity index 92% rename from builtin/providers/test/resource_undeletable.go rename to internal/legacy/builtin/providers/test/resource_undeletable.go index e5c9bb3b0..b4be0bff3 100644 --- a/builtin/providers/test/resource_undeletable.go +++ b/internal/legacy/builtin/providers/test/resource_undeletable.go @@ -3,7 +3,7 @@ package test import ( "fmt" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) func testResourceUndeleteable() *schema.Resource { diff --git a/builtin/providers/test/resource_with_custom_diff.go b/internal/legacy/builtin/providers/test/resource_with_custom_diff.go similarity index 98% rename from builtin/providers/test/resource_with_custom_diff.go rename to internal/legacy/builtin/providers/test/resource_with_custom_diff.go index 10756548c..397e0795e 100644 --- a/builtin/providers/test/resource_with_custom_diff.go +++ b/internal/legacy/builtin/providers/test/resource_with_custom_diff.go @@ -3,7 +3,7 @@ package test import ( "fmt" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" ) func testResourceCustomDiff() *schema.Resource { diff --git a/helper/plugin/doc.go b/internal/legacy/helper/plugin/doc.go similarity index 100% rename from helper/plugin/doc.go rename to internal/legacy/helper/plugin/doc.go diff --git a/helper/plugin/grpc_provider.go b/internal/legacy/helper/plugin/grpc_provider.go similarity index 99% rename from helper/plugin/grpc_provider.go rename to internal/legacy/helper/plugin/grpc_provider.go index 06ebaf421..511407806 100644 --- a/helper/plugin/grpc_provider.go +++ b/internal/legacy/helper/plugin/grpc_provider.go @@ -13,11 +13,11 @@ import ( "github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/configs/hcl2shim" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/terraform" proto "github.com/hashicorp/terraform/internal/tfplugin5" "github.com/hashicorp/terraform/plans/objchange" "github.com/hashicorp/terraform/plugin/convert" - "github.com/hashicorp/terraform/terraform" ) const newExtraKey = "_new_extra_shim" diff --git a/helper/plugin/grpc_provider_test.go b/internal/legacy/helper/plugin/grpc_provider_test.go similarity index 99% rename from helper/plugin/grpc_provider_test.go rename to internal/legacy/helper/plugin/grpc_provider_test.go index 736eb258e..ddebff134 100644 --- a/helper/plugin/grpc_provider_test.go +++ b/internal/legacy/helper/plugin/grpc_provider_test.go @@ -10,10 +10,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/terraform" proto "github.com/hashicorp/terraform/internal/tfplugin5" "github.com/hashicorp/terraform/plugin/convert" - "github.com/hashicorp/terraform/terraform" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/msgpack" ) diff --git a/helper/plugin/grpc_provisioner.go b/internal/legacy/helper/plugin/grpc_provisioner.go similarity index 97% rename from helper/plugin/grpc_provisioner.go rename to internal/legacy/helper/plugin/grpc_provisioner.go index 088e94e4a..6031662ec 100644 --- a/helper/plugin/grpc_provisioner.go +++ b/internal/legacy/helper/plugin/grpc_provisioner.go @@ -5,10 +5,10 @@ import ( "strings" "unicode/utf8" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/terraform" proto "github.com/hashicorp/terraform/internal/tfplugin5" "github.com/hashicorp/terraform/plugin/convert" - "github.com/hashicorp/terraform/terraform" "github.com/zclconf/go-cty/cty" ctyconvert "github.com/zclconf/go-cty/cty/convert" "github.com/zclconf/go-cty/cty/msgpack" diff --git a/helper/plugin/grpc_provisioner_test.go b/internal/legacy/helper/plugin/grpc_provisioner_test.go similarity index 93% rename from helper/plugin/grpc_provisioner_test.go rename to internal/legacy/helper/plugin/grpc_provisioner_test.go index 9b38daf4a..c13840043 100644 --- a/helper/plugin/grpc_provisioner_test.go +++ b/internal/legacy/helper/plugin/grpc_provisioner_test.go @@ -5,10 +5,10 @@ import ( "unicode/utf8" "github.com/golang/mock/gomock" - "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/helper/schema" + "github.com/hashicorp/terraform/internal/legacy/terraform" proto "github.com/hashicorp/terraform/internal/tfplugin5" mockproto "github.com/hashicorp/terraform/plugin/mock_proto" - "github.com/hashicorp/terraform/terraform" context "golang.org/x/net/context" ) diff --git a/helper/plugin/unknown.go b/internal/legacy/helper/plugin/unknown.go similarity index 100% rename from helper/plugin/unknown.go rename to internal/legacy/helper/plugin/unknown.go diff --git a/helper/plugin/unknown_test.go b/internal/legacy/helper/plugin/unknown_test.go similarity index 100% rename from helper/plugin/unknown_test.go rename to internal/legacy/helper/plugin/unknown_test.go diff --git a/helper/schema/README.md b/internal/legacy/helper/schema/README.md similarity index 100% rename from helper/schema/README.md rename to internal/legacy/helper/schema/README.md diff --git a/helper/schema/backend.go b/internal/legacy/helper/schema/backend.go similarity index 98% rename from helper/schema/backend.go rename to internal/legacy/helper/schema/backend.go index 42c2bed92..a7f440e02 100644 --- a/helper/schema/backend.go +++ b/internal/legacy/helper/schema/backend.go @@ -9,7 +9,7 @@ import ( "github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/configs/hcl2shim" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ctyconvert "github.com/zclconf/go-cty/cty/convert" ) diff --git a/helper/schema/backend_test.go b/internal/legacy/helper/schema/backend_test.go similarity index 100% rename from helper/schema/backend_test.go rename to internal/legacy/helper/schema/backend_test.go diff --git a/helper/schema/core_schema.go b/internal/legacy/helper/schema/core_schema.go similarity index 100% rename from helper/schema/core_schema.go rename to internal/legacy/helper/schema/core_schema.go diff --git a/helper/schema/core_schema_test.go b/internal/legacy/helper/schema/core_schema_test.go similarity index 100% rename from helper/schema/core_schema_test.go rename to internal/legacy/helper/schema/core_schema_test.go diff --git a/helper/schema/data_source_resource_shim.go b/internal/legacy/helper/schema/data_source_resource_shim.go similarity index 100% rename from helper/schema/data_source_resource_shim.go rename to internal/legacy/helper/schema/data_source_resource_shim.go diff --git a/helper/schema/equal.go b/internal/legacy/helper/schema/equal.go similarity index 100% rename from helper/schema/equal.go rename to internal/legacy/helper/schema/equal.go diff --git a/helper/schema/field_reader.go b/internal/legacy/helper/schema/field_reader.go similarity index 100% rename from helper/schema/field_reader.go rename to internal/legacy/helper/schema/field_reader.go diff --git a/helper/schema/field_reader_config.go b/internal/legacy/helper/schema/field_reader_config.go similarity index 99% rename from helper/schema/field_reader_config.go rename to internal/legacy/helper/schema/field_reader_config.go index 6ad3f13cb..f4a43d1fc 100644 --- a/helper/schema/field_reader_config.go +++ b/internal/legacy/helper/schema/field_reader_config.go @@ -7,7 +7,7 @@ import ( "strings" "sync" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" "github.com/mitchellh/mapstructure" ) diff --git a/helper/schema/field_reader_config_test.go b/internal/legacy/helper/schema/field_reader_config_test.go similarity index 99% rename from helper/schema/field_reader_config_test.go rename to internal/legacy/helper/schema/field_reader_config_test.go index 5e2728250..bf09eaa6f 100644 --- a/helper/schema/field_reader_config_test.go +++ b/internal/legacy/helper/schema/field_reader_config_test.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform/configs/hcl2shim" "github.com/hashicorp/terraform/helper/hashcode" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) func TestConfigFieldReader_impl(t *testing.T) { diff --git a/helper/schema/field_reader_diff.go b/internal/legacy/helper/schema/field_reader_diff.go similarity index 99% rename from helper/schema/field_reader_diff.go rename to internal/legacy/helper/schema/field_reader_diff.go index 3e70acf0b..84ebe272e 100644 --- a/helper/schema/field_reader_diff.go +++ b/internal/legacy/helper/schema/field_reader_diff.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" "github.com/mitchellh/mapstructure" ) diff --git a/helper/schema/field_reader_diff_test.go b/internal/legacy/helper/schema/field_reader_diff_test.go similarity index 99% rename from helper/schema/field_reader_diff_test.go rename to internal/legacy/helper/schema/field_reader_diff_test.go index 49b05e862..1f6fa7da1 100644 --- a/helper/schema/field_reader_diff_test.go +++ b/internal/legacy/helper/schema/field_reader_diff_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) func TestDiffFieldReader_impl(t *testing.T) { diff --git a/helper/schema/field_reader_map.go b/internal/legacy/helper/schema/field_reader_map.go similarity index 100% rename from helper/schema/field_reader_map.go rename to internal/legacy/helper/schema/field_reader_map.go diff --git a/helper/schema/field_reader_map_test.go b/internal/legacy/helper/schema/field_reader_map_test.go similarity index 100% rename from helper/schema/field_reader_map_test.go rename to internal/legacy/helper/schema/field_reader_map_test.go diff --git a/helper/schema/field_reader_multi.go b/internal/legacy/helper/schema/field_reader_multi.go similarity index 100% rename from helper/schema/field_reader_multi.go rename to internal/legacy/helper/schema/field_reader_multi.go diff --git a/helper/schema/field_reader_multi_test.go b/internal/legacy/helper/schema/field_reader_multi_test.go similarity index 98% rename from helper/schema/field_reader_multi_test.go rename to internal/legacy/helper/schema/field_reader_multi_test.go index 85286a66e..7410335f6 100644 --- a/helper/schema/field_reader_multi_test.go +++ b/internal/legacy/helper/schema/field_reader_multi_test.go @@ -5,7 +5,7 @@ import ( "strconv" "testing" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) func TestMultiLevelFieldReaderReadFieldExact(t *testing.T) { diff --git a/helper/schema/field_reader_test.go b/internal/legacy/helper/schema/field_reader_test.go similarity index 100% rename from helper/schema/field_reader_test.go rename to internal/legacy/helper/schema/field_reader_test.go diff --git a/helper/schema/field_writer.go b/internal/legacy/helper/schema/field_writer.go similarity index 100% rename from helper/schema/field_writer.go rename to internal/legacy/helper/schema/field_writer.go diff --git a/helper/schema/field_writer_map.go b/internal/legacy/helper/schema/field_writer_map.go similarity index 100% rename from helper/schema/field_writer_map.go rename to internal/legacy/helper/schema/field_writer_map.go diff --git a/helper/schema/field_writer_map_test.go b/internal/legacy/helper/schema/field_writer_map_test.go similarity index 100% rename from helper/schema/field_writer_map_test.go rename to internal/legacy/helper/schema/field_writer_map_test.go diff --git a/helper/schema/getsource_string.go b/internal/legacy/helper/schema/getsource_string.go similarity index 100% rename from helper/schema/getsource_string.go rename to internal/legacy/helper/schema/getsource_string.go diff --git a/helper/schema/provider.go b/internal/legacy/helper/schema/provider.go similarity index 99% rename from helper/schema/provider.go rename to internal/legacy/helper/schema/provider.go index 59dc750ee..24736566d 100644 --- a/helper/schema/provider.go +++ b/internal/legacy/helper/schema/provider.go @@ -9,7 +9,7 @@ import ( multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform/configs/configschema" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) var ReservedProviderFields = []string{ diff --git a/helper/schema/provider_test.go b/internal/legacy/helper/schema/provider_test.go similarity index 99% rename from helper/schema/provider_test.go rename to internal/legacy/helper/schema/provider_test.go index 1f9b5e8bf..3f3eff4e2 100644 --- a/helper/schema/provider_test.go +++ b/internal/legacy/helper/schema/provider_test.go @@ -11,7 +11,7 @@ import ( "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/configs/configschema" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) func TestProvider_impl(t *testing.T) { diff --git a/helper/schema/provisioner.go b/internal/legacy/helper/schema/provisioner.go similarity index 99% rename from helper/schema/provisioner.go rename to internal/legacy/helper/schema/provisioner.go index eee155bfb..d0ee581be 100644 --- a/helper/schema/provisioner.go +++ b/internal/legacy/helper/schema/provisioner.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform/configs/configschema" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) // Provisioner represents a resource provisioner in Terraform and properly diff --git a/helper/schema/provisioner_test.go b/internal/legacy/helper/schema/provisioner_test.go similarity index 99% rename from helper/schema/provisioner_test.go rename to internal/legacy/helper/schema/provisioner_test.go index bac6610d3..228dacd72 100644 --- a/helper/schema/provisioner_test.go +++ b/internal/legacy/helper/schema/provisioner_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) func TestProvisioner_impl(t *testing.T) { diff --git a/helper/schema/resource.go b/internal/legacy/helper/schema/resource.go similarity index 99% rename from helper/schema/resource.go rename to internal/legacy/helper/schema/resource.go index dcfb32aea..28fa54e38 100644 --- a/helper/schema/resource.go +++ b/internal/legacy/helper/schema/resource.go @@ -6,7 +6,7 @@ import ( "log" "strconv" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" "github.com/zclconf/go-cty/cty" ) diff --git a/helper/schema/resource_data.go b/internal/legacy/helper/schema/resource_data.go similarity index 99% rename from helper/schema/resource_data.go rename to internal/legacy/helper/schema/resource_data.go index fb9387e29..3a61e3493 100644 --- a/helper/schema/resource_data.go +++ b/internal/legacy/helper/schema/resource_data.go @@ -7,7 +7,7 @@ import ( "sync" "time" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/gocty" ) diff --git a/helper/schema/resource_data_get_source.go b/internal/legacy/helper/schema/resource_data_get_source.go similarity index 100% rename from helper/schema/resource_data_get_source.go rename to internal/legacy/helper/schema/resource_data_get_source.go diff --git a/helper/schema/resource_data_test.go b/internal/legacy/helper/schema/resource_data_test.go similarity index 99% rename from helper/schema/resource_data_test.go rename to internal/legacy/helper/schema/resource_data_test.go index 5af0ff8e5..22ad45b6b 100644 --- a/helper/schema/resource_data_test.go +++ b/internal/legacy/helper/schema/resource_data_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) func TestResourceDataGet(t *testing.T) { diff --git a/helper/schema/resource_diff.go b/internal/legacy/helper/schema/resource_diff.go similarity index 99% rename from helper/schema/resource_diff.go rename to internal/legacy/helper/schema/resource_diff.go index 47b548104..72d4711eb 100644 --- a/helper/schema/resource_diff.go +++ b/internal/legacy/helper/schema/resource_diff.go @@ -7,7 +7,7 @@ import ( "strings" "sync" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) // newValueWriter is a minor re-implementation of MapFieldWriter to include diff --git a/helper/schema/resource_diff_test.go b/internal/legacy/helper/schema/resource_diff_test.go similarity index 99% rename from helper/schema/resource_diff_test.go rename to internal/legacy/helper/schema/resource_diff_test.go index e6897b731..7cb9d5188 100644 --- a/helper/schema/resource_diff_test.go +++ b/internal/legacy/helper/schema/resource_diff_test.go @@ -8,7 +8,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/hashicorp/terraform/configs/hcl2shim" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) // testSetFunc is a very simple function we use to test a foo/bar complex set. diff --git a/helper/schema/resource_importer.go b/internal/legacy/helper/schema/resource_importer.go similarity index 100% rename from helper/schema/resource_importer.go rename to internal/legacy/helper/schema/resource_importer.go diff --git a/helper/schema/resource_test.go b/internal/legacy/helper/schema/resource_test.go similarity index 99% rename from helper/schema/resource_test.go rename to internal/legacy/helper/schema/resource_test.go index a532dba39..954b1a705 100644 --- a/helper/schema/resource_test.go +++ b/internal/legacy/helper/schema/resource_test.go @@ -10,7 +10,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform/configs/hcl2shim" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" "github.com/zclconf/go-cty/cty" ctyjson "github.com/zclconf/go-cty/cty/json" diff --git a/helper/schema/resource_timeout.go b/internal/legacy/helper/schema/resource_timeout.go similarity index 99% rename from helper/schema/resource_timeout.go rename to internal/legacy/helper/schema/resource_timeout.go index 5ad7aafc8..cf9654bcb 100644 --- a/helper/schema/resource_timeout.go +++ b/internal/legacy/helper/schema/resource_timeout.go @@ -6,7 +6,7 @@ import ( "time" "github.com/hashicorp/terraform/configs/hcl2shim" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" "github.com/mitchellh/copystructure" ) diff --git a/helper/schema/resource_timeout_test.go b/internal/legacy/helper/schema/resource_timeout_test.go similarity index 99% rename from helper/schema/resource_timeout_test.go rename to internal/legacy/helper/schema/resource_timeout_test.go index e53bbd849..f5091755b 100644 --- a/helper/schema/resource_timeout_test.go +++ b/internal/legacy/helper/schema/resource_timeout_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) func TestResourceTimeout_ConfigDecode_badkey(t *testing.T) { diff --git a/helper/schema/schema.go b/internal/legacy/helper/schema/schema.go similarity index 99% rename from helper/schema/schema.go rename to internal/legacy/helper/schema/schema.go index 089e6b213..5eeaed7b6 100644 --- a/helper/schema/schema.go +++ b/internal/legacy/helper/schema/schema.go @@ -23,7 +23,7 @@ import ( "sync" "github.com/hashicorp/terraform/configs/hcl2shim" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" "github.com/mitchellh/copystructure" "github.com/mitchellh/mapstructure" ) diff --git a/helper/schema/schema_test.go b/internal/legacy/helper/schema/schema_test.go similarity index 99% rename from helper/schema/schema_test.go rename to internal/legacy/helper/schema/schema_test.go index 4199f3ddf..262fcd46f 100644 --- a/helper/schema/schema_test.go +++ b/internal/legacy/helper/schema/schema_test.go @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/terraform/configs/hcl2shim" "github.com/hashicorp/terraform/helper/hashcode" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) func TestEnvDefaultFunc(t *testing.T) { diff --git a/helper/schema/serialize.go b/internal/legacy/helper/schema/serialize.go similarity index 100% rename from helper/schema/serialize.go rename to internal/legacy/helper/schema/serialize.go diff --git a/helper/schema/serialize_test.go b/internal/legacy/helper/schema/serialize_test.go similarity index 100% rename from helper/schema/serialize_test.go rename to internal/legacy/helper/schema/serialize_test.go diff --git a/helper/schema/set.go b/internal/legacy/helper/schema/set.go similarity index 100% rename from helper/schema/set.go rename to internal/legacy/helper/schema/set.go diff --git a/helper/schema/set_test.go b/internal/legacy/helper/schema/set_test.go similarity index 100% rename from helper/schema/set_test.go rename to internal/legacy/helper/schema/set_test.go diff --git a/helper/schema/shims.go b/internal/legacy/helper/schema/shims.go similarity index 98% rename from helper/schema/shims.go rename to internal/legacy/helper/schema/shims.go index d2dbff53c..b8cbf6b22 100644 --- a/helper/schema/shims.go +++ b/internal/legacy/helper/schema/shims.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/configs/hcl2shim" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) // DiffFromValues takes the current state and desired state as cty.Values and diff --git a/helper/schema/shims_test.go b/internal/legacy/helper/schema/shims_test.go similarity index 99% rename from helper/schema/shims_test.go rename to internal/legacy/helper/schema/shims_test.go index 050286a02..3d0ee065d 100644 --- a/helper/schema/shims_test.go +++ b/internal/legacy/helper/schema/shims_test.go @@ -14,8 +14,8 @@ import ( "github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/configs/hcl2shim" "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/internal/legacy/terraform" "github.com/hashicorp/terraform/providers" - "github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/tfdiags" "github.com/zclconf/go-cty/cty" ) diff --git a/helper/schema/testing.go b/internal/legacy/helper/schema/testing.go similarity index 89% rename from helper/schema/testing.go rename to internal/legacy/helper/schema/testing.go index 122782174..3b328a87c 100644 --- a/helper/schema/testing.go +++ b/internal/legacy/helper/schema/testing.go @@ -3,7 +3,7 @@ package schema import ( "testing" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) // TestResourceDataRaw creates a ResourceData from a raw configuration map. diff --git a/helper/schema/valuetype.go b/internal/legacy/helper/schema/valuetype.go similarity index 100% rename from helper/schema/valuetype.go rename to internal/legacy/helper/schema/valuetype.go diff --git a/helper/schema/valuetype_string.go b/internal/legacy/helper/schema/valuetype_string.go similarity index 100% rename from helper/schema/valuetype_string.go rename to internal/legacy/helper/schema/valuetype_string.go diff --git a/internal/legacy/terraform/context_components.go b/internal/legacy/terraform/context_components.go new file mode 100644 index 000000000..c893a16b4 --- /dev/null +++ b/internal/legacy/terraform/context_components.go @@ -0,0 +1,65 @@ +package terraform + +import ( + "fmt" + + "github.com/hashicorp/terraform/addrs" + "github.com/hashicorp/terraform/providers" + "github.com/hashicorp/terraform/provisioners" +) + +// contextComponentFactory is the interface that Context uses +// to initialize various components such as providers and provisioners. +// This factory gets more information than the raw maps using to initialize +// a Context. This information is used for debugging. +type contextComponentFactory interface { + // ResourceProvider creates a new ResourceProvider with the given type. + ResourceProvider(typ addrs.Provider) (providers.Interface, error) + ResourceProviders() []string + + // ResourceProvisioner creates a new ResourceProvisioner with the given + // type. + ResourceProvisioner(typ string) (provisioners.Interface, error) + ResourceProvisioners() []string +} + +// basicComponentFactory just calls a factory from a map directly. +type basicComponentFactory struct { + providers map[addrs.Provider]providers.Factory + provisioners map[string]ProvisionerFactory +} + +func (c *basicComponentFactory) ResourceProviders() []string { + var result []string + for k := range c.providers { + result = append(result, k.String()) + } + return result +} + +func (c *basicComponentFactory) ResourceProvisioners() []string { + var result []string + for k := range c.provisioners { + result = append(result, k) + } + + return result +} + +func (c *basicComponentFactory) ResourceProvider(typ addrs.Provider) (providers.Interface, error) { + f, ok := c.providers[typ] + if !ok { + return nil, fmt.Errorf("unknown provider %q", typ.String()) + } + + return f() +} + +func (c *basicComponentFactory) ResourceProvisioner(typ string) (provisioners.Interface, error) { + f, ok := c.provisioners[typ] + if !ok { + return nil, fmt.Errorf("unknown provisioner %q", typ) + } + + return f() +} diff --git a/internal/legacy/terraform/diff.go b/internal/legacy/terraform/diff.go new file mode 100644 index 000000000..4e834204d --- /dev/null +++ b/internal/legacy/terraform/diff.go @@ -0,0 +1,1451 @@ +package terraform + +import ( + "bufio" + "bytes" + "fmt" + "log" + "reflect" + "regexp" + "sort" + "strconv" + "strings" + "sync" + + "github.com/hashicorp/terraform/addrs" + "github.com/hashicorp/terraform/configs/configschema" + "github.com/hashicorp/terraform/configs/hcl2shim" + "github.com/zclconf/go-cty/cty" + + "github.com/mitchellh/copystructure" +) + +// DiffChangeType is an enum with the kind of changes a diff has planned. +type DiffChangeType byte + +const ( + DiffInvalid DiffChangeType = iota + DiffNone + DiffCreate + DiffUpdate + DiffDestroy + DiffDestroyCreate + + // DiffRefresh is only used in the UI for displaying diffs. + // Managed resource reads never appear in plan, and when data source + // reads appear they are represented as DiffCreate in core before + // transforming to DiffRefresh in the UI layer. + DiffRefresh // TODO: Actually use DiffRefresh in core too, for less confusion +) + +// multiVal matches the index key to a flatmapped set, list or map +var multiVal = regexp.MustCompile(`\.(#|%)$`) + +// Diff tracks the changes that are necessary to apply a configuration +// to an existing infrastructure. +type Diff struct { + // Modules contains all the modules that have a diff + Modules []*ModuleDiff +} + +// Prune cleans out unused structures in the diff without affecting +// the behavior of the diff at all. +// +// This is not safe to call concurrently. This is safe to call on a +// nil Diff. +func (d *Diff) Prune() { + if d == nil { + return + } + + // Prune all empty modules + newModules := make([]*ModuleDiff, 0, len(d.Modules)) + for _, m := range d.Modules { + // If the module isn't empty, we keep it + if !m.Empty() { + newModules = append(newModules, m) + } + } + if len(newModules) == 0 { + newModules = nil + } + d.Modules = newModules +} + +// AddModule adds the module with the given path to the diff. +// +// This should be the preferred method to add module diffs since it +// allows us to optimize lookups later as well as control sorting. +func (d *Diff) AddModule(path addrs.ModuleInstance) *ModuleDiff { + // Lower the new-style address into a legacy-style address. + // This requires that none of the steps have instance keys, which is + // true for all addresses at the time of implementing this because + // "count" and "for_each" are not yet implemented for modules. + legacyPath := make([]string, len(path)) + for i, step := range path { + if step.InstanceKey != addrs.NoKey { + // FIXME: Once the rest of Terraform is ready to use count and + // for_each, remove all of this and just write the addrs.ModuleInstance + // value itself into the ModuleState. + panic("diff cannot represent modules with count or for_each keys") + } + + legacyPath[i] = step.Name + } + + m := &ModuleDiff{Path: legacyPath} + m.init() + d.Modules = append(d.Modules, m) + return m +} + +// ModuleByPath is used to lookup the module diff for the given path. +// This should be the preferred lookup mechanism as it allows for future +// lookup optimizations. +func (d *Diff) ModuleByPath(path addrs.ModuleInstance) *ModuleDiff { + if d == nil { + return nil + } + for _, mod := range d.Modules { + if mod.Path == nil { + panic("missing module path") + } + modPath := normalizeModulePath(mod.Path) + if modPath.String() == path.String() { + return mod + } + } + return nil +} + +// RootModule returns the ModuleState for the root module +func (d *Diff) RootModule() *ModuleDiff { + root := d.ModuleByPath(addrs.RootModuleInstance) + if root == nil { + panic("missing root module") + } + return root +} + +// Empty returns true if the diff has no changes. +func (d *Diff) Empty() bool { + if d == nil { + return true + } + + for _, m := range d.Modules { + if !m.Empty() { + return false + } + } + + return true +} + +// Equal compares two diffs for exact equality. +// +// This is different from the Same comparison that is supported which +// checks for operation equality taking into account computed values. Equal +// instead checks for exact equality. +func (d *Diff) Equal(d2 *Diff) bool { + // If one is nil, they must both be nil + if d == nil || d2 == nil { + return d == d2 + } + + // Sort the modules + sort.Sort(moduleDiffSort(d.Modules)) + sort.Sort(moduleDiffSort(d2.Modules)) + + // Copy since we have to modify the module destroy flag to false so + // we don't compare that. TODO: delete this when we get rid of the + // destroy flag on modules. + dCopy := d.DeepCopy() + d2Copy := d2.DeepCopy() + for _, m := range dCopy.Modules { + m.Destroy = false + } + for _, m := range d2Copy.Modules { + m.Destroy = false + } + + // Use DeepEqual + return reflect.DeepEqual(dCopy, d2Copy) +} + +// DeepCopy performs a deep copy of all parts of the Diff, making the +// resulting Diff safe to use without modifying this one. +func (d *Diff) DeepCopy() *Diff { + copy, err := copystructure.Config{Lock: true}.Copy(d) + if err != nil { + panic(err) + } + + return copy.(*Diff) +} + +func (d *Diff) String() string { + var buf bytes.Buffer + + keys := make([]string, 0, len(d.Modules)) + lookup := make(map[string]*ModuleDiff) + for _, m := range d.Modules { + addr := normalizeModulePath(m.Path) + key := addr.String() + keys = append(keys, key) + lookup[key] = m + } + sort.Strings(keys) + + for _, key := range keys { + m := lookup[key] + mStr := m.String() + + // If we're the root module, we just write the output directly. + if reflect.DeepEqual(m.Path, rootModulePath) { + buf.WriteString(mStr + "\n") + continue + } + + buf.WriteString(fmt.Sprintf("%s:\n", key)) + + s := bufio.NewScanner(strings.NewReader(mStr)) + for s.Scan() { + buf.WriteString(fmt.Sprintf(" %s\n", s.Text())) + } + } + + return strings.TrimSpace(buf.String()) +} + +func (d *Diff) init() { + if d.Modules == nil { + rootDiff := &ModuleDiff{Path: rootModulePath} + d.Modules = []*ModuleDiff{rootDiff} + } + for _, m := range d.Modules { + m.init() + } +} + +// ModuleDiff tracks the differences between resources to apply within +// a single module. +type ModuleDiff struct { + Path []string + Resources map[string]*InstanceDiff + Destroy bool // Set only by the destroy plan +} + +func (d *ModuleDiff) init() { + if d.Resources == nil { + d.Resources = make(map[string]*InstanceDiff) + } + for _, r := range d.Resources { + r.init() + } +} + +// ChangeType returns the type of changes that the diff for this +// module includes. +// +// At a module level, this will only be DiffNone, DiffUpdate, DiffDestroy, or +// DiffCreate. If an instance within the module has a DiffDestroyCreate +// then this will register as a DiffCreate for a module. +func (d *ModuleDiff) ChangeType() DiffChangeType { + result := DiffNone + for _, r := range d.Resources { + change := r.ChangeType() + switch change { + case DiffCreate, DiffDestroy: + if result == DiffNone { + result = change + } + case DiffDestroyCreate, DiffUpdate: + result = DiffUpdate + } + } + + return result +} + +// Empty returns true if the diff has no changes within this module. +func (d *ModuleDiff) Empty() bool { + if d.Destroy { + return false + } + + if len(d.Resources) == 0 { + return true + } + + for _, rd := range d.Resources { + if !rd.Empty() { + return false + } + } + + return true +} + +// Instances returns the instance diffs for the id given. This can return +// multiple instance diffs if there are counts within the resource. +func (d *ModuleDiff) Instances(id string) []*InstanceDiff { + var result []*InstanceDiff + for k, diff := range d.Resources { + if k == id || strings.HasPrefix(k, id+".") { + if !diff.Empty() { + result = append(result, diff) + } + } + } + + return result +} + +// IsRoot says whether or not this module diff is for the root module. +func (d *ModuleDiff) IsRoot() bool { + return reflect.DeepEqual(d.Path, rootModulePath) +} + +// String outputs the diff in a long but command-line friendly output +// format that users can read to quickly inspect a diff. +func (d *ModuleDiff) String() string { + var buf bytes.Buffer + + names := make([]string, 0, len(d.Resources)) + for name, _ := range d.Resources { + names = append(names, name) + } + sort.Strings(names) + + for _, name := range names { + rdiff := d.Resources[name] + + crud := "UPDATE" + switch { + case rdiff.RequiresNew() && (rdiff.GetDestroy() || rdiff.GetDestroyTainted()): + crud = "DESTROY/CREATE" + case rdiff.GetDestroy() || rdiff.GetDestroyDeposed(): + crud = "DESTROY" + case rdiff.RequiresNew(): + crud = "CREATE" + } + + extra := "" + if !rdiff.GetDestroy() && rdiff.GetDestroyDeposed() { + extra = " (deposed only)" + } + + buf.WriteString(fmt.Sprintf( + "%s: %s%s\n", + crud, + name, + extra)) + + keyLen := 0 + rdiffAttrs := rdiff.CopyAttributes() + keys := make([]string, 0, len(rdiffAttrs)) + for key, _ := range rdiffAttrs { + if key == "id" { + continue + } + + keys = append(keys, key) + if len(key) > keyLen { + keyLen = len(key) + } + } + sort.Strings(keys) + + for _, attrK := range keys { + attrDiff, _ := rdiff.GetAttribute(attrK) + + v := attrDiff.New + u := attrDiff.Old + if attrDiff.NewComputed { + v = "" + } + + if attrDiff.Sensitive { + u = "" + v = "" + } + + updateMsg := "" + if attrDiff.RequiresNew { + updateMsg = " (forces new resource)" + } else if attrDiff.Sensitive { + updateMsg = " (attribute changed)" + } + + buf.WriteString(fmt.Sprintf( + " %s:%s %#v => %#v%s\n", + attrK, + strings.Repeat(" ", keyLen-len(attrK)), + u, + v, + updateMsg)) + } + } + + return buf.String() +} + +// InstanceDiff is the diff of a resource from some state to another. +type InstanceDiff struct { + mu sync.Mutex + Attributes map[string]*ResourceAttrDiff + Destroy bool + DestroyDeposed bool + DestroyTainted bool + + // Meta is a simple K/V map that is stored in a diff and persisted to + // plans but otherwise is completely ignored by Terraform core. It is + // meant to be used for additional data a resource may want to pass through. + // The value here must only contain Go primitives and collections. + Meta map[string]interface{} +} + +func (d *InstanceDiff) Lock() { d.mu.Lock() } +func (d *InstanceDiff) Unlock() { d.mu.Unlock() } + +// ApplyToValue merges the receiver into the given base value, returning a +// new value that incorporates the planned changes. The given value must +// conform to the given schema, or this method will panic. +// +// This method is intended for shimming old subsystems that still use this +// legacy diff type to work with the new-style types. +func (d *InstanceDiff) ApplyToValue(base cty.Value, schema *configschema.Block) (cty.Value, error) { + // Create an InstanceState attributes from our existing state. + // We can use this to more easily apply the diff changes. + attrs := hcl2shim.FlatmapValueFromHCL2(base) + applied, err := d.Apply(attrs, schema) + if err != nil { + return base, err + } + + val, err := hcl2shim.HCL2ValueFromFlatmap(applied, schema.ImpliedType()) + if err != nil { + return base, err + } + + return schema.CoerceValue(val) +} + +// Apply applies the diff to the provided flatmapped attributes, +// returning the new instance attributes. +// +// This method is intended for shimming old subsystems that still use this +// legacy diff type to work with the new-style types. +func (d *InstanceDiff) Apply(attrs map[string]string, schema *configschema.Block) (map[string]string, error) { + // We always build a new value here, even if the given diff is "empty", + // because we might be planning to create a new instance that happens + // to have no attributes set, and so we want to produce an empty object + // rather than just echoing back the null old value. + if attrs == nil { + attrs = map[string]string{} + } + + // Rather applying the diff to mutate the attrs, we'll copy new values into + // here to avoid the possibility of leaving stale values. + result := map[string]string{} + + if d.Destroy || d.DestroyDeposed || d.DestroyTainted { + return result, nil + } + + return d.applyBlockDiff(nil, attrs, schema) +} + +func (d *InstanceDiff) applyBlockDiff(path []string, attrs map[string]string, schema *configschema.Block) (map[string]string, error) { + result := map[string]string{} + name := "" + if len(path) > 0 { + name = path[len(path)-1] + } + + // localPrefix is used to build the local result map + localPrefix := "" + if name != "" { + localPrefix = name + "." + } + + // iterate over the schema rather than the attributes, so we can handle + // different block types separately from plain attributes + for n, attrSchema := range schema.Attributes { + var err error + newAttrs, err := d.applyAttrDiff(append(path, n), attrs, attrSchema) + + if err != nil { + return result, err + } + + for k, v := range newAttrs { + result[localPrefix+k] = v + } + } + + blockPrefix := strings.Join(path, ".") + if blockPrefix != "" { + blockPrefix += "." + } + for n, block := range schema.BlockTypes { + // we need to find the set of all keys that traverse this block + candidateKeys := map[string]bool{} + blockKey := blockPrefix + n + "." + localBlockPrefix := localPrefix + n + "." + + // we can only trust the diff for sets, since the path changes, so don't + // count existing values as candidate keys. If it turns out we're + // keeping the attributes, we will catch it down below with "keepBlock" + // after we check the set count. + if block.Nesting != configschema.NestingSet { + for k := range attrs { + if strings.HasPrefix(k, blockKey) { + nextDot := strings.Index(k[len(blockKey):], ".") + if nextDot < 0 { + continue + } + nextDot += len(blockKey) + candidateKeys[k[len(blockKey):nextDot]] = true + } + } + } + + for k, diff := range d.Attributes { + // helper/schema should not insert nil diff values, but don't panic + // if it does. + if diff == nil { + continue + } + + if strings.HasPrefix(k, blockKey) { + nextDot := strings.Index(k[len(blockKey):], ".") + if nextDot < 0 { + continue + } + + if diff.NewRemoved { + continue + } + + nextDot += len(blockKey) + candidateKeys[k[len(blockKey):nextDot]] = true + } + } + + // check each set candidate to see if it was removed. + // we need to do this, because when entire sets are removed, they may + // have the wrong key, and ony show diffs going to "" + if block.Nesting == configschema.NestingSet { + for k := range candidateKeys { + indexPrefix := strings.Join(append(path, n, k), ".") + "." + keep := false + // now check each set element to see if it's a new diff, or one + // that we're dropping. Since we're only applying the "New" + // portion of the set, we can ignore diffs that only contain "Old" + for attr, diff := range d.Attributes { + // helper/schema should not insert nil diff values, but don't panic + // if it does. + if diff == nil { + continue + } + + if !strings.HasPrefix(attr, indexPrefix) { + continue + } + + // check for empty "count" keys + if (strings.HasSuffix(attr, ".#") || strings.HasSuffix(attr, ".%")) && diff.New == "0" { + continue + } + + // removed items don't count either + if diff.NewRemoved { + continue + } + + // this must be a diff to keep + keep = true + break + } + if !keep { + delete(candidateKeys, k) + } + } + } + + for k := range candidateKeys { + newAttrs, err := d.applyBlockDiff(append(path, n, k), attrs, &block.Block) + if err != nil { + return result, err + } + + for attr, v := range newAttrs { + result[localBlockPrefix+attr] = v + } + } + + keepBlock := true + // check this block's count diff directly first, since we may not + // have candidates because it was removed and only set to "0" + if diff, ok := d.Attributes[blockKey+"#"]; ok { + if diff.New == "0" || diff.NewRemoved { + keepBlock = false + } + } + + // if there was no diff at all, then we need to keep the block attributes + if len(candidateKeys) == 0 && keepBlock { + for k, v := range attrs { + if strings.HasPrefix(k, blockKey) { + // we need the key relative to this block, so remove the + // entire prefix, then re-insert the block name. + localKey := localBlockPrefix + k[len(blockKey):] + result[localKey] = v + } + } + } + + countAddr := strings.Join(append(path, n, "#"), ".") + if countDiff, ok := d.Attributes[countAddr]; ok { + if countDiff.NewComputed { + result[localBlockPrefix+"#"] = hcl2shim.UnknownVariableValue + } else { + result[localBlockPrefix+"#"] = countDiff.New + + // While sets are complete, list are not, and we may not have all the + // information to track removals. If the list was truncated, we need to + // remove the extra items from the result. + if block.Nesting == configschema.NestingList && + countDiff.New != "" && countDiff.New != hcl2shim.UnknownVariableValue { + length, _ := strconv.Atoi(countDiff.New) + for k := range result { + if !strings.HasPrefix(k, localBlockPrefix) { + continue + } + + index := k[len(localBlockPrefix):] + nextDot := strings.Index(index, ".") + if nextDot < 1 { + continue + } + index = index[:nextDot] + i, err := strconv.Atoi(index) + if err != nil { + // this shouldn't happen since we added these + // ourself, but make note of it just in case. + log.Printf("[ERROR] bad list index in %q: %s", k, err) + continue + } + if i >= length { + delete(result, k) + } + } + } + } + } else if origCount, ok := attrs[countAddr]; ok && keepBlock { + result[localBlockPrefix+"#"] = origCount + } else { + result[localBlockPrefix+"#"] = countFlatmapContainerValues(localBlockPrefix+"#", result) + } + } + + return result, nil +} + +func (d *InstanceDiff) applyAttrDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) { + ty := attrSchema.Type + switch { + case ty.IsListType(), ty.IsTupleType(), ty.IsMapType(): + return d.applyCollectionDiff(path, attrs, attrSchema) + case ty.IsSetType(): + return d.applySetDiff(path, attrs, attrSchema) + default: + return d.applySingleAttrDiff(path, attrs, attrSchema) + } +} + +func (d *InstanceDiff) applySingleAttrDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) { + currentKey := strings.Join(path, ".") + + attr := path[len(path)-1] + + result := map[string]string{} + diff := d.Attributes[currentKey] + old, exists := attrs[currentKey] + + if diff != nil && diff.NewComputed { + result[attr] = hcl2shim.UnknownVariableValue + return result, nil + } + + // "id" must exist and not be an empty string, or it must be unknown. + // This only applied to top-level "id" fields. + if attr == "id" && len(path) == 1 { + if old == "" { + result[attr] = hcl2shim.UnknownVariableValue + } else { + result[attr] = old + } + return result, nil + } + + // attribute diffs are sometimes missed, so assume no diff means keep the + // old value + if diff == nil { + if exists { + result[attr] = old + } else { + // We need required values, so set those with an empty value. It + // must be set in the config, since if it were missing it would have + // failed validation. + if attrSchema.Required { + // we only set a missing string here, since bool or number types + // would have distinct zero value which shouldn't have been + // lost. + if attrSchema.Type == cty.String { + result[attr] = "" + } + } + } + return result, nil + } + + // check for missmatched diff values + if exists && + old != diff.Old && + old != hcl2shim.UnknownVariableValue && + diff.Old != hcl2shim.UnknownVariableValue { + return result, fmt.Errorf("diff apply conflict for %s: diff expects %q, but prior value has %q", attr, diff.Old, old) + } + + if diff.NewRemoved { + // don't set anything in the new value + return map[string]string{}, nil + } + + if diff.Old == diff.New && diff.New == "" { + // this can only be a valid empty string + if attrSchema.Type == cty.String { + result[attr] = "" + } + return result, nil + } + + if attrSchema.Computed && diff.NewComputed { + result[attr] = hcl2shim.UnknownVariableValue + return result, nil + } + + result[attr] = diff.New + + return result, nil +} + +func (d *InstanceDiff) applyCollectionDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) { + result := map[string]string{} + + prefix := "" + if len(path) > 1 { + prefix = strings.Join(path[:len(path)-1], ".") + "." + } + + name := "" + if len(path) > 0 { + name = path[len(path)-1] + } + + currentKey := prefix + name + + // check the index first for special handling + for k, diff := range d.Attributes { + // check the index value, which can be set, and 0 + if k == currentKey+".#" || k == currentKey+".%" || k == currentKey { + if diff.NewRemoved { + return result, nil + } + + if diff.NewComputed { + result[k[len(prefix):]] = hcl2shim.UnknownVariableValue + return result, nil + } + + // do what the diff tells us to here, so that it's consistent with applies + if diff.New == "0" { + result[k[len(prefix):]] = "0" + return result, nil + } + } + } + + // collect all the keys from the diff and the old state + noDiff := true + keys := map[string]bool{} + for k := range d.Attributes { + if !strings.HasPrefix(k, currentKey+".") { + continue + } + noDiff = false + keys[k] = true + } + + noAttrs := true + for k := range attrs { + if !strings.HasPrefix(k, currentKey+".") { + continue + } + noAttrs = false + keys[k] = true + } + + // If there's no diff and no attrs, then there's no value at all. + // This prevents an unexpected zero-count attribute in the attributes. + if noDiff && noAttrs { + return result, nil + } + + idx := "#" + if attrSchema.Type.IsMapType() { + idx = "%" + } + + for k := range keys { + // generate an schema placeholder for the values + elSchema := &configschema.Attribute{ + Type: attrSchema.Type.ElementType(), + } + + res, err := d.applySingleAttrDiff(append(path, k[len(currentKey)+1:]), attrs, elSchema) + if err != nil { + return result, err + } + + for k, v := range res { + result[name+"."+k] = v + } + } + + // Just like in nested list blocks, for simple lists we may need to fill in + // missing empty strings. + countKey := name + "." + idx + count := result[countKey] + length, _ := strconv.Atoi(count) + + if count != "" && count != hcl2shim.UnknownVariableValue && + attrSchema.Type.Equals(cty.List(cty.String)) { + // insert empty strings into missing indexes + for i := 0; i < length; i++ { + key := fmt.Sprintf("%s.%d", name, i) + if _, ok := result[key]; !ok { + result[key] = "" + } + } + } + + // now check for truncation in any type of list + if attrSchema.Type.IsListType() { + for key := range result { + if key == countKey { + continue + } + + if len(key) <= len(name)+1 { + // not sure what this is, but don't panic + continue + } + + index := key[len(name)+1:] + + // It is possible to have nested sets or maps, so look for another dot + dot := strings.Index(index, ".") + if dot > 0 { + index = index[:dot] + } + + // This shouldn't have any more dots, since the element type is only string. + num, err := strconv.Atoi(index) + if err != nil { + log.Printf("[ERROR] bad list index in %q: %s", currentKey, err) + continue + } + + if num >= length { + delete(result, key) + } + } + } + + // Fill in the count value if it wasn't present in the diff for some reason, + // or if there is no count at all. + _, countDiff := d.Attributes[countKey] + if result[countKey] == "" || (!countDiff && len(keys) != len(result)) { + result[countKey] = countFlatmapContainerValues(countKey, result) + } + + return result, nil +} + +func (d *InstanceDiff) applySetDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) { + // We only need this special behavior for sets of object. + if !attrSchema.Type.ElementType().IsObjectType() { + // The normal collection apply behavior will work okay for this one, then. + return d.applyCollectionDiff(path, attrs, attrSchema) + } + + // When we're dealing with a set of an object type we actually want to + // use our normal _block type_ apply behaviors, so we'll construct ourselves + // a synthetic schema that treats the object type as a block type and + // then delegate to our block apply method. + synthSchema := &configschema.Block{ + Attributes: make(map[string]*configschema.Attribute), + } + + for name, ty := range attrSchema.Type.ElementType().AttributeTypes() { + // We can safely make everything into an attribute here because in the + // event that there are nested set attributes we'll end up back in + // here again recursively and can then deal with the next level of + // expansion. + synthSchema.Attributes[name] = &configschema.Attribute{ + Type: ty, + Optional: true, + } + } + + parentPath := path[:len(path)-1] + childName := path[len(path)-1] + containerSchema := &configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + childName: { + Nesting: configschema.NestingSet, + Block: *synthSchema, + }, + }, + } + + return d.applyBlockDiff(parentPath, attrs, containerSchema) +} + +// countFlatmapContainerValues returns the number of values in the flatmapped container +// (set, map, list) indexed by key. The key argument is expected to include the +// trailing ".#", or ".%". +func countFlatmapContainerValues(key string, attrs map[string]string) string { + if len(key) < 3 || !(strings.HasSuffix(key, ".#") || strings.HasSuffix(key, ".%")) { + panic(fmt.Sprintf("invalid index value %q", key)) + } + + prefix := key[:len(key)-1] + items := map[string]int{} + + for k := range attrs { + if k == key { + continue + } + if !strings.HasPrefix(k, prefix) { + continue + } + + suffix := k[len(prefix):] + dot := strings.Index(suffix, ".") + if dot > 0 { + suffix = suffix[:dot] + } + + items[suffix]++ + } + return strconv.Itoa(len(items)) +} + +// ResourceAttrDiff is the diff of a single attribute of a resource. +type ResourceAttrDiff struct { + Old string // Old Value + New string // New Value + NewComputed bool // True if new value is computed (unknown currently) + NewRemoved bool // True if this attribute is being removed + NewExtra interface{} // Extra information for the provider + RequiresNew bool // True if change requires new resource + Sensitive bool // True if the data should not be displayed in UI output + Type DiffAttrType +} + +// Empty returns true if the diff for this attr is neutral +func (d *ResourceAttrDiff) Empty() bool { + return d.Old == d.New && !d.NewComputed && !d.NewRemoved +} + +func (d *ResourceAttrDiff) GoString() string { + return fmt.Sprintf("*%#v", *d) +} + +// DiffAttrType is an enum type that says whether a resource attribute +// diff is an input attribute (comes from the configuration) or an +// output attribute (comes as a result of applying the configuration). An +// example input would be "ami" for AWS and an example output would be +// "private_ip". +type DiffAttrType byte + +const ( + DiffAttrUnknown DiffAttrType = iota + DiffAttrInput + DiffAttrOutput +) + +func (d *InstanceDiff) init() { + if d.Attributes == nil { + d.Attributes = make(map[string]*ResourceAttrDiff) + } +} + +func NewInstanceDiff() *InstanceDiff { + return &InstanceDiff{Attributes: make(map[string]*ResourceAttrDiff)} +} + +func (d *InstanceDiff) Copy() (*InstanceDiff, error) { + if d == nil { + return nil, nil + } + + dCopy, err := copystructure.Config{Lock: true}.Copy(d) + if err != nil { + return nil, err + } + + return dCopy.(*InstanceDiff), nil +} + +// ChangeType returns the DiffChangeType represented by the diff +// for this single instance. +func (d *InstanceDiff) ChangeType() DiffChangeType { + if d.Empty() { + return DiffNone + } + + if d.RequiresNew() && (d.GetDestroy() || d.GetDestroyTainted()) { + return DiffDestroyCreate + } + + if d.GetDestroy() || d.GetDestroyDeposed() { + return DiffDestroy + } + + if d.RequiresNew() { + return DiffCreate + } + + return DiffUpdate +} + +// Empty returns true if this diff encapsulates no changes. +func (d *InstanceDiff) Empty() bool { + if d == nil { + return true + } + + d.mu.Lock() + defer d.mu.Unlock() + return !d.Destroy && + !d.DestroyTainted && + !d.DestroyDeposed && + len(d.Attributes) == 0 +} + +// Equal compares two diffs for exact equality. +// +// This is different from the Same comparison that is supported which +// checks for operation equality taking into account computed values. Equal +// instead checks for exact equality. +func (d *InstanceDiff) Equal(d2 *InstanceDiff) bool { + // If one is nil, they must both be nil + if d == nil || d2 == nil { + return d == d2 + } + + // Use DeepEqual + return reflect.DeepEqual(d, d2) +} + +// DeepCopy performs a deep copy of all parts of the InstanceDiff +func (d *InstanceDiff) DeepCopy() *InstanceDiff { + copy, err := copystructure.Config{Lock: true}.Copy(d) + if err != nil { + panic(err) + } + + return copy.(*InstanceDiff) +} + +func (d *InstanceDiff) GoString() string { + return fmt.Sprintf("*%#v", InstanceDiff{ + Attributes: d.Attributes, + Destroy: d.Destroy, + DestroyTainted: d.DestroyTainted, + DestroyDeposed: d.DestroyDeposed, + }) +} + +// RequiresNew returns true if the diff requires the creation of a new +// resource (implying the destruction of the old). +func (d *InstanceDiff) RequiresNew() bool { + if d == nil { + return false + } + + d.mu.Lock() + defer d.mu.Unlock() + + return d.requiresNew() +} + +func (d *InstanceDiff) requiresNew() bool { + if d == nil { + return false + } + + if d.DestroyTainted { + return true + } + + for _, rd := range d.Attributes { + if rd != nil && rd.RequiresNew { + return true + } + } + + return false +} + +func (d *InstanceDiff) GetDestroyDeposed() bool { + d.mu.Lock() + defer d.mu.Unlock() + + return d.DestroyDeposed +} + +func (d *InstanceDiff) SetDestroyDeposed(b bool) { + d.mu.Lock() + defer d.mu.Unlock() + + d.DestroyDeposed = b +} + +// These methods are properly locked, for use outside other InstanceDiff +// methods but everywhere else within the terraform package. +// TODO refactor the locking scheme +func (d *InstanceDiff) SetTainted(b bool) { + d.mu.Lock() + defer d.mu.Unlock() + + d.DestroyTainted = b +} + +func (d *InstanceDiff) GetDestroyTainted() bool { + d.mu.Lock() + defer d.mu.Unlock() + + return d.DestroyTainted +} + +func (d *InstanceDiff) SetDestroy(b bool) { + d.mu.Lock() + defer d.mu.Unlock() + + d.Destroy = b +} + +func (d *InstanceDiff) GetDestroy() bool { + d.mu.Lock() + defer d.mu.Unlock() + + return d.Destroy +} + +func (d *InstanceDiff) SetAttribute(key string, attr *ResourceAttrDiff) { + d.mu.Lock() + defer d.mu.Unlock() + + d.Attributes[key] = attr +} + +func (d *InstanceDiff) DelAttribute(key string) { + d.mu.Lock() + defer d.mu.Unlock() + + delete(d.Attributes, key) +} + +func (d *InstanceDiff) GetAttribute(key string) (*ResourceAttrDiff, bool) { + d.mu.Lock() + defer d.mu.Unlock() + + attr, ok := d.Attributes[key] + return attr, ok +} +func (d *InstanceDiff) GetAttributesLen() int { + d.mu.Lock() + defer d.mu.Unlock() + + return len(d.Attributes) +} + +// Safely copies the Attributes map +func (d *InstanceDiff) CopyAttributes() map[string]*ResourceAttrDiff { + d.mu.Lock() + defer d.mu.Unlock() + + attrs := make(map[string]*ResourceAttrDiff) + for k, v := range d.Attributes { + attrs[k] = v + } + + return attrs +} + +// Same checks whether or not two InstanceDiff's are the "same". When +// we say "same", it is not necessarily exactly equal. Instead, it is +// just checking that the same attributes are changing, a destroy +// isn't suddenly happening, etc. +func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) { + // we can safely compare the pointers without a lock + switch { + case d == nil && d2 == nil: + return true, "" + case d == nil || d2 == nil: + return false, "one nil" + case d == d2: + return true, "" + } + + d.mu.Lock() + defer d.mu.Unlock() + + // If we're going from requiring new to NOT requiring new, then we have + // to see if all required news were computed. If so, it is allowed since + // computed may also mean "same value and therefore not new". + oldNew := d.requiresNew() + newNew := d2.RequiresNew() + if oldNew && !newNew { + oldNew = false + + // This section builds a list of ignorable attributes for requiresNew + // by removing off any elements of collections going to zero elements. + // For collections going to zero, they may not exist at all in the + // new diff (and hence RequiresNew == false). + ignoreAttrs := make(map[string]struct{}) + for k, diffOld := range d.Attributes { + if !strings.HasSuffix(k, ".%") && !strings.HasSuffix(k, ".#") { + continue + } + + // This case is in here as a protection measure. The bug that this + // code originally fixed (GH-11349) didn't have to deal with computed + // so I'm not 100% sure what the correct behavior is. Best to leave + // the old behavior. + if diffOld.NewComputed { + continue + } + + // We're looking for the case a map goes to exactly 0. + if diffOld.New != "0" { + continue + } + + // Found it! Ignore all of these. The prefix here is stripping + // off the "%" so it is just "k." + prefix := k[:len(k)-1] + for k2, _ := range d.Attributes { + if strings.HasPrefix(k2, prefix) { + ignoreAttrs[k2] = struct{}{} + } + } + } + + for k, rd := range d.Attributes { + if _, ok := ignoreAttrs[k]; ok { + continue + } + + // If the field is requires new and NOT computed, then what + // we have is a diff mismatch for sure. We set that the old + // diff does REQUIRE a ForceNew. + if rd != nil && rd.RequiresNew && !rd.NewComputed { + oldNew = true + break + } + } + } + + if oldNew != newNew { + return false, fmt.Sprintf( + "diff RequiresNew; old: %t, new: %t", oldNew, newNew) + } + + // Verify that destroy matches. The second boolean here allows us to + // have mismatching Destroy if we're moving from RequiresNew true + // to false above. Therefore, the second boolean will only pass if + // we're moving from Destroy: true to false as well. + if d.Destroy != d2.GetDestroy() && d.requiresNew() == oldNew { + return false, fmt.Sprintf( + "diff: Destroy; old: %t, new: %t", d.Destroy, d2.GetDestroy()) + } + + // Go through the old diff and make sure the new diff has all the + // same attributes. To start, build up the check map to be all the keys. + checkOld := make(map[string]struct{}) + checkNew := make(map[string]struct{}) + for k, _ := range d.Attributes { + checkOld[k] = struct{}{} + } + for k, _ := range d2.CopyAttributes() { + checkNew[k] = struct{}{} + } + + // Make an ordered list so we are sure the approximated hashes are left + // to process at the end of the loop + keys := make([]string, 0, len(d.Attributes)) + for k, _ := range d.Attributes { + keys = append(keys, k) + } + sort.StringSlice(keys).Sort() + + for _, k := range keys { + diffOld := d.Attributes[k] + + if _, ok := checkOld[k]; !ok { + // We're not checking this key for whatever reason (see where + // check is modified). + continue + } + + // Remove this key since we'll never hit it again + delete(checkOld, k) + delete(checkNew, k) + + _, ok := d2.GetAttribute(k) + if !ok { + // If there's no new attribute, and the old diff expected the attribute + // to be removed, that's just fine. + if diffOld.NewRemoved { + continue + } + + // If the last diff was a computed value then the absense of + // that value is allowed since it may mean the value ended up + // being the same. + if diffOld.NewComputed { + ok = true + } + + // No exact match, but maybe this is a set containing computed + // values. So check if there is an approximate hash in the key + // and if so, try to match the key. + if strings.Contains(k, "~") { + parts := strings.Split(k, ".") + parts2 := append([]string(nil), parts...) + + re := regexp.MustCompile(`^~\d+$`) + for i, part := range parts { + if re.MatchString(part) { + // we're going to consider this the base of a + // computed hash, and remove all longer matching fields + ok = true + + parts2[i] = `\d+` + parts2 = parts2[:i+1] + break + } + } + + re, err := regexp.Compile("^" + strings.Join(parts2, `\.`)) + if err != nil { + return false, fmt.Sprintf("regexp failed to compile; err: %#v", err) + } + + for k2, _ := range checkNew { + if re.MatchString(k2) { + delete(checkNew, k2) + } + } + } + + // This is a little tricky, but when a diff contains a computed + // list, set, or map that can only be interpolated after the apply + // command has created the dependent resources, it could turn out + // that the result is actually the same as the existing state which + // would remove the key from the diff. + if diffOld.NewComputed && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) { + ok = true + } + + // Similarly, in a RequiresNew scenario, a list that shows up in the plan + // diff can disappear from the apply diff, which is calculated from an + // empty state. + if d.requiresNew() && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) { + ok = true + } + + if !ok { + return false, fmt.Sprintf("attribute mismatch: %s", k) + } + } + + // search for the suffix of the base of a [computed] map, list or set. + match := multiVal.FindStringSubmatch(k) + + if diffOld.NewComputed && len(match) == 2 { + matchLen := len(match[1]) + + // This is a computed list, set, or map, so remove any keys with + // this prefix from the check list. + kprefix := k[:len(k)-matchLen] + for k2, _ := range checkOld { + if strings.HasPrefix(k2, kprefix) { + delete(checkOld, k2) + } + } + for k2, _ := range checkNew { + if strings.HasPrefix(k2, kprefix) { + delete(checkNew, k2) + } + } + } + + // We don't compare the values because we can't currently actually + // guarantee to generate the same value two two diffs created from + // the same state+config: we have some pesky interpolation functions + // that do not behave as pure functions (uuid, timestamp) and so they + // can be different each time a diff is produced. + // FIXME: Re-organize our config handling so that we don't re-evaluate + // expressions when we produce a second comparison diff during + // apply (for EvalCompareDiff). + } + + // Check for leftover attributes + if len(checkNew) > 0 { + extras := make([]string, 0, len(checkNew)) + for attr, _ := range checkNew { + extras = append(extras, attr) + } + return false, + fmt.Sprintf("extra attributes: %s", strings.Join(extras, ", ")) + } + + return true, "" +} + +// moduleDiffSort implements sort.Interface to sort module diffs by path. +type moduleDiffSort []*ModuleDiff + +func (s moduleDiffSort) Len() int { return len(s) } +func (s moduleDiffSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s moduleDiffSort) Less(i, j int) bool { + a := s[i] + b := s[j] + + // If the lengths are different, then the shorter one always wins + if len(a.Path) != len(b.Path) { + return len(a.Path) < len(b.Path) + } + + // Otherwise, compare lexically + return strings.Join(a.Path, ".") < strings.Join(b.Path, ".") +} diff --git a/internal/legacy/terraform/diff_test.go b/internal/legacy/terraform/diff_test.go new file mode 100644 index 000000000..e7ee0d818 --- /dev/null +++ b/internal/legacy/terraform/diff_test.go @@ -0,0 +1,1252 @@ +package terraform + +import ( + "fmt" + "reflect" + "strconv" + "strings" + "testing" + + "github.com/hashicorp/terraform/addrs" +) + +func TestDiffEmpty(t *testing.T) { + var diff *Diff + if !diff.Empty() { + t.Fatal("should be empty") + } + + diff = new(Diff) + if !diff.Empty() { + t.Fatal("should be empty") + } + + mod := diff.AddModule(addrs.RootModuleInstance) + mod.Resources["nodeA"] = &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{ + Old: "foo", + New: "bar", + }, + }, + } + + if diff.Empty() { + t.Fatal("should not be empty") + } +} + +func TestDiffEmpty_taintedIsNotEmpty(t *testing.T) { + diff := new(Diff) + + mod := diff.AddModule(addrs.RootModuleInstance) + mod.Resources["nodeA"] = &InstanceDiff{ + DestroyTainted: true, + } + + if diff.Empty() { + t.Fatal("should not be empty, since DestroyTainted was set") + } +} + +func TestDiffEqual(t *testing.T) { + cases := map[string]struct { + D1, D2 *Diff + Equal bool + }{ + "nil": { + nil, + new(Diff), + false, + }, + + "empty": { + new(Diff), + new(Diff), + true, + }, + + "different module order": { + &Diff{ + Modules: []*ModuleDiff{ + &ModuleDiff{Path: []string{"root", "foo"}}, + &ModuleDiff{Path: []string{"root", "bar"}}, + }, + }, + &Diff{ + Modules: []*ModuleDiff{ + &ModuleDiff{Path: []string{"root", "bar"}}, + &ModuleDiff{Path: []string{"root", "foo"}}, + }, + }, + true, + }, + + "different module diff destroys": { + &Diff{ + Modules: []*ModuleDiff{ + &ModuleDiff{Path: []string{"root", "foo"}, Destroy: true}, + }, + }, + &Diff{ + Modules: []*ModuleDiff{ + &ModuleDiff{Path: []string{"root", "foo"}, Destroy: false}, + }, + }, + true, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + actual := tc.D1.Equal(tc.D2) + if actual != tc.Equal { + t.Fatalf("expected: %v\n\n%#v\n\n%#v", tc.Equal, tc.D1, tc.D2) + } + }) + } +} + +func TestDiffPrune(t *testing.T) { + cases := map[string]struct { + D1, D2 *Diff + }{ + "nil": { + nil, + nil, + }, + + "empty": { + new(Diff), + new(Diff), + }, + + "empty module": { + &Diff{ + Modules: []*ModuleDiff{ + &ModuleDiff{Path: []string{"root", "foo"}}, + }, + }, + &Diff{}, + }, + + "destroy module": { + &Diff{ + Modules: []*ModuleDiff{ + &ModuleDiff{Path: []string{"root", "foo"}, Destroy: true}, + }, + }, + &Diff{ + Modules: []*ModuleDiff{ + &ModuleDiff{Path: []string{"root", "foo"}, Destroy: true}, + }, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + tc.D1.Prune() + if !tc.D1.Equal(tc.D2) { + t.Fatalf("bad:\n\n%#v\n\n%#v", tc.D1, tc.D2) + } + }) + } +} + +func TestModuleDiff_ChangeType(t *testing.T) { + cases := []struct { + Diff *ModuleDiff + Result DiffChangeType + }{ + { + &ModuleDiff{}, + DiffNone, + }, + { + &ModuleDiff{ + Resources: map[string]*InstanceDiff{ + "foo": &InstanceDiff{Destroy: true}, + }, + }, + DiffDestroy, + }, + { + &ModuleDiff{ + Resources: map[string]*InstanceDiff{ + "foo": &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{ + Old: "", + New: "bar", + }, + }, + }, + }, + }, + DiffUpdate, + }, + { + &ModuleDiff{ + Resources: map[string]*InstanceDiff{ + "foo": &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{ + Old: "", + New: "bar", + RequiresNew: true, + }, + }, + }, + }, + }, + DiffCreate, + }, + { + &ModuleDiff{ + Resources: map[string]*InstanceDiff{ + "foo": &InstanceDiff{ + Destroy: true, + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{ + Old: "", + New: "bar", + RequiresNew: true, + }, + }, + }, + }, + }, + DiffUpdate, + }, + } + + for i, tc := range cases { + actual := tc.Diff.ChangeType() + if actual != tc.Result { + t.Fatalf("%d: %#v", i, actual) + } + } +} + +func TestDiff_DeepCopy(t *testing.T) { + cases := map[string]*Diff{ + "empty": &Diff{}, + + "basic diff": &Diff{ + Modules: []*ModuleDiff{ + &ModuleDiff{ + Path: []string{"root"}, + Resources: map[string]*InstanceDiff{ + "aws_instance.foo": &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "num": &ResourceAttrDiff{ + Old: "0", + New: "2", + }, + }, + }, + }, + }, + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + dup := tc.DeepCopy() + if !reflect.DeepEqual(dup, tc) { + t.Fatalf("\n%#v\n\n%#v", dup, tc) + } + }) + } +} + +func TestModuleDiff_Empty(t *testing.T) { + diff := new(ModuleDiff) + if !diff.Empty() { + t.Fatal("should be empty") + } + + diff.Resources = map[string]*InstanceDiff{ + "nodeA": &InstanceDiff{}, + } + + if !diff.Empty() { + t.Fatal("should be empty") + } + + diff.Resources["nodeA"].Attributes = map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{ + Old: "foo", + New: "bar", + }, + } + + if diff.Empty() { + t.Fatal("should not be empty") + } + + diff.Resources["nodeA"].Attributes = nil + diff.Resources["nodeA"].Destroy = true + + if diff.Empty() { + t.Fatal("should not be empty") + } +} + +func TestModuleDiff_String(t *testing.T) { + diff := &ModuleDiff{ + Resources: map[string]*InstanceDiff{ + "nodeA": &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{ + Old: "foo", + New: "bar", + }, + "bar": &ResourceAttrDiff{ + Old: "foo", + NewComputed: true, + }, + "longfoo": &ResourceAttrDiff{ + Old: "foo", + New: "bar", + RequiresNew: true, + }, + "secretfoo": &ResourceAttrDiff{ + Old: "foo", + New: "bar", + Sensitive: true, + }, + }, + }, + }, + } + + actual := strings.TrimSpace(diff.String()) + expected := strings.TrimSpace(moduleDiffStrBasic) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + +func TestInstanceDiff_ChangeType(t *testing.T) { + cases := []struct { + Diff *InstanceDiff + Result DiffChangeType + }{ + { + &InstanceDiff{}, + DiffNone, + }, + { + &InstanceDiff{Destroy: true}, + DiffDestroy, + }, + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{ + Old: "", + New: "bar", + }, + }, + }, + DiffUpdate, + }, + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{ + Old: "", + New: "bar", + RequiresNew: true, + }, + }, + }, + DiffCreate, + }, + { + &InstanceDiff{ + Destroy: true, + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{ + Old: "", + New: "bar", + RequiresNew: true, + }, + }, + }, + DiffDestroyCreate, + }, + { + &InstanceDiff{ + DestroyTainted: true, + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{ + Old: "", + New: "bar", + RequiresNew: true, + }, + }, + }, + DiffDestroyCreate, + }, + } + + for i, tc := range cases { + actual := tc.Diff.ChangeType() + if actual != tc.Result { + t.Fatalf("%d: %#v", i, actual) + } + } +} + +func TestInstanceDiff_Empty(t *testing.T) { + var rd *InstanceDiff + + if !rd.Empty() { + t.Fatal("should be empty") + } + + rd = new(InstanceDiff) + + if !rd.Empty() { + t.Fatal("should be empty") + } + + rd = &InstanceDiff{Destroy: true} + + if rd.Empty() { + t.Fatal("should not be empty") + } + + rd = &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{ + New: "bar", + }, + }, + } + + if rd.Empty() { + t.Fatal("should not be empty") + } +} + +func TestModuleDiff_Instances(t *testing.T) { + yesDiff := &InstanceDiff{Destroy: true} + noDiff := &InstanceDiff{Destroy: true, DestroyTainted: true} + + cases := []struct { + Diff *ModuleDiff + Id string + Result []*InstanceDiff + }{ + { + &ModuleDiff{ + Resources: map[string]*InstanceDiff{ + "foo": yesDiff, + "bar": noDiff, + }, + }, + "foo", + []*InstanceDiff{ + yesDiff, + }, + }, + + { + &ModuleDiff{ + Resources: map[string]*InstanceDiff{ + "foo": yesDiff, + "foo.0": yesDiff, + "bar": noDiff, + }, + }, + "foo", + []*InstanceDiff{ + yesDiff, + yesDiff, + }, + }, + + { + &ModuleDiff{ + Resources: map[string]*InstanceDiff{ + "foo": yesDiff, + "foo.0": yesDiff, + "foo_bar": noDiff, + "bar": noDiff, + }, + }, + "foo", + []*InstanceDiff{ + yesDiff, + yesDiff, + }, + }, + } + + for i, tc := range cases { + actual := tc.Diff.Instances(tc.Id) + if !reflect.DeepEqual(actual, tc.Result) { + t.Fatalf("%d: %#v", i, actual) + } + } +} + +func TestInstanceDiff_RequiresNew(t *testing.T) { + rd := &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{}, + }, + } + + if rd.RequiresNew() { + t.Fatal("should not require new") + } + + rd.Attributes["foo"].RequiresNew = true + + if !rd.RequiresNew() { + t.Fatal("should require new") + } +} + +func TestInstanceDiff_RequiresNew_nil(t *testing.T) { + var rd *InstanceDiff + + if rd.RequiresNew() { + t.Fatal("should not require new") + } +} + +func TestInstanceDiffSame(t *testing.T) { + cases := []struct { + One, Two *InstanceDiff + Same bool + Reason string + }{ + { + &InstanceDiff{}, + &InstanceDiff{}, + true, + "", + }, + + { + nil, + nil, + true, + "", + }, + + { + &InstanceDiff{Destroy: false}, + &InstanceDiff{Destroy: true}, + false, + "diff: Destroy; old: false, new: true", + }, + + { + &InstanceDiff{Destroy: true}, + &InstanceDiff{Destroy: true}, + true, + "", + }, + + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{}, + }, + }, + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{}, + }, + }, + true, + "", + }, + + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "bar": &ResourceAttrDiff{}, + }, + }, + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{}, + }, + }, + false, + "attribute mismatch: bar", + }, + + // Extra attributes + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{}, + }, + }, + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{}, + "bar": &ResourceAttrDiff{}, + }, + }, + false, + "extra attributes: bar", + }, + + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{RequiresNew: true}, + }, + }, + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{RequiresNew: false}, + }, + }, + false, + "diff RequiresNew; old: true, new: false", + }, + + // NewComputed on primitive + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{ + Old: "", + New: "${var.foo}", + NewComputed: true, + }, + }, + }, + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{ + Old: "0", + New: "1", + }, + }, + }, + true, + "", + }, + + // NewComputed on primitive, removed + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{ + Old: "", + New: "${var.foo}", + NewComputed: true, + }, + }, + }, + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{}, + }, + true, + "", + }, + + // NewComputed on set, removed + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.#": &ResourceAttrDiff{ + Old: "", + New: "", + NewComputed: true, + }, + }, + }, + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.1": &ResourceAttrDiff{ + Old: "foo", + New: "", + NewRemoved: true, + }, + "foo.2": &ResourceAttrDiff{ + Old: "", + New: "bar", + }, + }, + }, + true, + "", + }, + + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.#": &ResourceAttrDiff{NewComputed: true}, + }, + }, + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.#": &ResourceAttrDiff{ + Old: "0", + New: "1", + }, + "foo.0": &ResourceAttrDiff{ + Old: "", + New: "12", + }, + }, + }, + true, + "", + }, + + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.#": &ResourceAttrDiff{ + Old: "0", + New: "1", + }, + "foo.~35964334.bar": &ResourceAttrDiff{ + Old: "", + New: "${var.foo}", + }, + }, + }, + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.#": &ResourceAttrDiff{ + Old: "0", + New: "1", + }, + "foo.87654323.bar": &ResourceAttrDiff{ + Old: "", + New: "12", + }, + }, + }, + true, + "", + }, + + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.#": &ResourceAttrDiff{ + Old: "0", + NewComputed: true, + }, + }, + }, + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{}, + }, + true, + "", + }, + + // Computed can change RequiresNew by removal, and that's okay + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.#": &ResourceAttrDiff{ + Old: "0", + NewComputed: true, + RequiresNew: true, + }, + }, + }, + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{}, + }, + true, + "", + }, + + // Computed can change Destroy by removal, and that's okay + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.#": &ResourceAttrDiff{ + Old: "0", + NewComputed: true, + RequiresNew: true, + }, + }, + + Destroy: true, + }, + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{}, + }, + true, + "", + }, + + // Computed can change Destroy by elements + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.#": &ResourceAttrDiff{ + Old: "0", + NewComputed: true, + RequiresNew: true, + }, + }, + + Destroy: true, + }, + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.#": &ResourceAttrDiff{ + Old: "1", + New: "1", + }, + "foo.12": &ResourceAttrDiff{ + Old: "4", + New: "12", + RequiresNew: true, + }, + }, + + Destroy: true, + }, + true, + "", + }, + + // Computed sets may not contain all fields in the original diff, and + // because multiple entries for the same set can compute to the same + // hash before the values are computed or interpolated, the overall + // count can change as well. + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.#": &ResourceAttrDiff{ + Old: "0", + New: "1", + }, + "foo.~35964334.bar": &ResourceAttrDiff{ + Old: "", + New: "${var.foo}", + }, + }, + }, + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.#": &ResourceAttrDiff{ + Old: "0", + New: "2", + }, + "foo.87654323.bar": &ResourceAttrDiff{ + Old: "", + New: "12", + }, + "foo.87654325.bar": &ResourceAttrDiff{ + Old: "", + New: "12", + }, + "foo.87654325.baz": &ResourceAttrDiff{ + Old: "", + New: "12", + }, + }, + }, + true, + "", + }, + + // Computed values in maps will fail the "Same" check as well + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.%": &ResourceAttrDiff{ + Old: "", + New: "", + NewComputed: true, + }, + }, + }, + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.%": &ResourceAttrDiff{ + Old: "0", + New: "1", + NewComputed: false, + }, + "foo.val": &ResourceAttrDiff{ + Old: "", + New: "something", + }, + }, + }, + true, + "", + }, + + // In a DESTROY/CREATE scenario, the plan diff will be run against the + // state of the old instance, while the apply diff will be run against an + // empty state (because the state is cleared when the destroy runs.) + // For complex attributes, this can result in keys that seem to disappear + // between the two diffs, when in reality everything is working just fine. + // + // Same() needs to take into account this scenario by analyzing NewRemoved + // and treating as "Same" a diff that does indeed have that key removed. + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "somemap.oldkey": &ResourceAttrDiff{ + Old: "long ago", + New: "", + NewRemoved: true, + }, + "somemap.newkey": &ResourceAttrDiff{ + Old: "", + New: "brave new world", + }, + }, + }, + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "somemap.newkey": &ResourceAttrDiff{ + Old: "", + New: "brave new world", + }, + }, + }, + true, + "", + }, + + // Another thing that can occur in DESTROY/CREATE scenarios is that list + // values that are going to zero have diffs that show up at plan time but + // are gone at apply time. The NewRemoved handling catches the fields and + // treats them as OK, but it also needs to treat the .# field itself as + // okay to be present in the old diff but not in the new one. + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "reqnew": &ResourceAttrDiff{ + Old: "old", + New: "new", + RequiresNew: true, + }, + "somemap.#": &ResourceAttrDiff{ + Old: "1", + New: "0", + }, + "somemap.oldkey": &ResourceAttrDiff{ + Old: "long ago", + New: "", + NewRemoved: true, + }, + }, + }, + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "reqnew": &ResourceAttrDiff{ + Old: "", + New: "new", + RequiresNew: true, + }, + }, + }, + true, + "", + }, + + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "reqnew": &ResourceAttrDiff{ + Old: "old", + New: "new", + RequiresNew: true, + }, + "somemap.%": &ResourceAttrDiff{ + Old: "1", + New: "0", + }, + "somemap.oldkey": &ResourceAttrDiff{ + Old: "long ago", + New: "", + NewRemoved: true, + }, + }, + }, + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "reqnew": &ResourceAttrDiff{ + Old: "", + New: "new", + RequiresNew: true, + }, + }, + }, + true, + "", + }, + + // Innner computed set should allow outer change in key + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.#": &ResourceAttrDiff{ + Old: "0", + New: "1", + }, + "foo.~1.outer_val": &ResourceAttrDiff{ + Old: "", + New: "foo", + }, + "foo.~1.inner.#": &ResourceAttrDiff{ + Old: "0", + New: "1", + }, + "foo.~1.inner.~2.value": &ResourceAttrDiff{ + Old: "", + New: "${var.bar}", + NewComputed: true, + }, + }, + }, + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.#": &ResourceAttrDiff{ + Old: "0", + New: "1", + }, + "foo.12.outer_val": &ResourceAttrDiff{ + Old: "", + New: "foo", + }, + "foo.12.inner.#": &ResourceAttrDiff{ + Old: "0", + New: "1", + }, + "foo.12.inner.42.value": &ResourceAttrDiff{ + Old: "", + New: "baz", + }, + }, + }, + true, + "", + }, + + // Innner computed list should allow outer change in key + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.#": &ResourceAttrDiff{ + Old: "0", + New: "1", + }, + "foo.~1.outer_val": &ResourceAttrDiff{ + Old: "", + New: "foo", + }, + "foo.~1.inner.#": &ResourceAttrDiff{ + Old: "0", + New: "1", + }, + "foo.~1.inner.0.value": &ResourceAttrDiff{ + Old: "", + New: "${var.bar}", + NewComputed: true, + }, + }, + }, + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.#": &ResourceAttrDiff{ + Old: "0", + New: "1", + }, + "foo.12.outer_val": &ResourceAttrDiff{ + Old: "", + New: "foo", + }, + "foo.12.inner.#": &ResourceAttrDiff{ + Old: "0", + New: "1", + }, + "foo.12.inner.0.value": &ResourceAttrDiff{ + Old: "", + New: "baz", + }, + }, + }, + true, + "", + }, + + // When removing all collection items, the diff is allowed to contain + // nothing when re-creating the resource. This should be the "Same" + // since we said we were going from 1 to 0. + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.%": &ResourceAttrDiff{ + Old: "1", + New: "0", + RequiresNew: true, + }, + "foo.bar": &ResourceAttrDiff{ + Old: "baz", + New: "", + NewRemoved: true, + RequiresNew: true, + }, + }, + }, + &InstanceDiff{}, + true, + "", + }, + + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.#": &ResourceAttrDiff{ + Old: "1", + New: "0", + RequiresNew: true, + }, + "foo.0": &ResourceAttrDiff{ + Old: "baz", + New: "", + NewRemoved: true, + RequiresNew: true, + }, + }, + }, + &InstanceDiff{}, + true, + "", + }, + + // Make sure that DestroyTainted diffs pass as well, especially when diff + // two works off of no state. + { + &InstanceDiff{ + DestroyTainted: true, + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{ + Old: "foo", + New: "foo", + }, + }, + }, + &InstanceDiff{ + DestroyTainted: true, + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{ + Old: "", + New: "foo", + }, + }, + }, + true, + "", + }, + // RequiresNew in different attribute + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{ + Old: "foo", + New: "foo", + }, + "bar": &ResourceAttrDiff{ + Old: "bar", + New: "baz", + RequiresNew: true, + }, + }, + }, + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{ + Old: "", + New: "foo", + }, + "bar": &ResourceAttrDiff{ + Old: "", + New: "baz", + RequiresNew: true, + }, + }, + }, + true, + "", + }, + } + + for i, tc := range cases { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + same, reason := tc.One.Same(tc.Two) + if same != tc.Same { + t.Fatalf("%d: expected same: %t, got %t (%s)\n\n one: %#v\n\ntwo: %#v", + i, tc.Same, same, reason, tc.One, tc.Two) + } + if reason != tc.Reason { + t.Fatalf( + "%d: bad reason\n\nexpected: %#v\n\ngot: %#v", i, tc.Reason, reason) + } + }) + } +} + +const moduleDiffStrBasic = ` +CREATE: nodeA + bar: "foo" => "" + foo: "foo" => "bar" + longfoo: "foo" => "bar" (forces new resource) + secretfoo: "" => "" (attribute changed) +` + +func TestCountFlatmapContainerValues(t *testing.T) { + for i, tc := range []struct { + attrs map[string]string + key string + count string + }{ + { + attrs: map[string]string{"set.2.list.#": "9999", "set.2.list.0": "x", "set.2.list.0.z": "y", "set.2.attr": "bar", "set.#": "9999"}, + key: "set.2.list.#", + count: "1", + }, + { + attrs: map[string]string{"set.2.list.#": "9999", "set.2.list.0": "x", "set.2.list.0.z": "y", "set.2.attr": "bar", "set.#": "9999"}, + key: "set.#", + count: "1", + }, + { + attrs: map[string]string{"set.2.list.0": "x", "set.2.list.0.z": "y", "set.2.attr": "bar", "set.#": "9999"}, + key: "set.#", + count: "1", + }, + { + attrs: map[string]string{"map.#": "3", "map.a": "b", "map.a.#": "0", "map.b": "4"}, + key: "map.#", + count: "2", + }, + } { + t.Run(strconv.Itoa(i), func(t *testing.T) { + count := countFlatmapContainerValues(tc.key, tc.attrs) + if count != tc.count { + t.Fatalf("expected %q, got %q", tc.count, count) + } + }) + } +} diff --git a/internal/legacy/terraform/features.go b/internal/legacy/terraform/features.go new file mode 100644 index 000000000..97c77bdbd --- /dev/null +++ b/internal/legacy/terraform/features.go @@ -0,0 +1,7 @@ +package terraform + +import "os" + +// This file holds feature flags for the next release + +var flagWarnOutputErrors = os.Getenv("TF_WARN_OUTPUT_ERRORS") != "" diff --git a/internal/legacy/terraform/instancetype.go b/internal/legacy/terraform/instancetype.go new file mode 100644 index 000000000..375a8638a --- /dev/null +++ b/internal/legacy/terraform/instancetype.go @@ -0,0 +1,13 @@ +package terraform + +//go:generate go run golang.org/x/tools/cmd/stringer -type=InstanceType instancetype.go + +// InstanceType is an enum of the various types of instances store in the State +type InstanceType int + +const ( + TypeInvalid InstanceType = iota + TypePrimary + TypeTainted + TypeDeposed +) diff --git a/internal/legacy/terraform/instancetype_string.go b/internal/legacy/terraform/instancetype_string.go new file mode 100644 index 000000000..95b7a9802 --- /dev/null +++ b/internal/legacy/terraform/instancetype_string.go @@ -0,0 +1,26 @@ +// Code generated by "stringer -type=InstanceType instancetype.go"; DO NOT EDIT. + +package terraform + +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[TypeInvalid-0] + _ = x[TypePrimary-1] + _ = x[TypeTainted-2] + _ = x[TypeDeposed-3] +} + +const _InstanceType_name = "TypeInvalidTypePrimaryTypeTaintedTypeDeposed" + +var _InstanceType_index = [...]uint8{0, 11, 22, 33, 44} + +func (i InstanceType) String() string { + if i < 0 || i >= InstanceType(len(_InstanceType_index)-1) { + return "InstanceType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _InstanceType_name[_InstanceType_index[i]:_InstanceType_index[i+1]] +} diff --git a/internal/legacy/terraform/provider_mock.go b/internal/legacy/terraform/provider_mock.go new file mode 100644 index 000000000..2a6f6dbf0 --- /dev/null +++ b/internal/legacy/terraform/provider_mock.go @@ -0,0 +1,364 @@ +package terraform + +import ( + "encoding/json" + "sync" + + "github.com/zclconf/go-cty/cty" + ctyjson "github.com/zclconf/go-cty/cty/json" + + "github.com/hashicorp/terraform/configs/hcl2shim" + "github.com/hashicorp/terraform/providers" +) + +var _ providers.Interface = (*MockProvider)(nil) + +// MockProvider implements providers.Interface but mocks out all the +// calls for testing purposes. +type MockProvider struct { + sync.Mutex + + // Anything you want, in case you need to store extra data with the mock. + Meta interface{} + + GetSchemaCalled bool + GetSchemaReturn *ProviderSchema // This is using ProviderSchema directly rather than providers.GetSchemaResponse for compatibility with old tests + + PrepareProviderConfigCalled bool + PrepareProviderConfigResponse providers.PrepareProviderConfigResponse + PrepareProviderConfigRequest providers.PrepareProviderConfigRequest + PrepareProviderConfigFn func(providers.PrepareProviderConfigRequest) providers.PrepareProviderConfigResponse + + ValidateResourceTypeConfigCalled bool + ValidateResourceTypeConfigTypeName string + ValidateResourceTypeConfigResponse providers.ValidateResourceTypeConfigResponse + ValidateResourceTypeConfigRequest providers.ValidateResourceTypeConfigRequest + ValidateResourceTypeConfigFn func(providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse + + ValidateDataSourceConfigCalled bool + ValidateDataSourceConfigTypeName string + ValidateDataSourceConfigResponse providers.ValidateDataSourceConfigResponse + ValidateDataSourceConfigRequest providers.ValidateDataSourceConfigRequest + ValidateDataSourceConfigFn func(providers.ValidateDataSourceConfigRequest) providers.ValidateDataSourceConfigResponse + + UpgradeResourceStateCalled bool + UpgradeResourceStateTypeName string + UpgradeResourceStateResponse providers.UpgradeResourceStateResponse + UpgradeResourceStateRequest providers.UpgradeResourceStateRequest + UpgradeResourceStateFn func(providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse + + ConfigureCalled bool + ConfigureResponse providers.ConfigureResponse + ConfigureRequest providers.ConfigureRequest + ConfigureFn func(providers.ConfigureRequest) providers.ConfigureResponse + + StopCalled bool + StopFn func() error + StopResponse error + + ReadResourceCalled bool + ReadResourceResponse providers.ReadResourceResponse + ReadResourceRequest providers.ReadResourceRequest + ReadResourceFn func(providers.ReadResourceRequest) providers.ReadResourceResponse + + PlanResourceChangeCalled bool + PlanResourceChangeResponse providers.PlanResourceChangeResponse + PlanResourceChangeRequest providers.PlanResourceChangeRequest + PlanResourceChangeFn func(providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse + + ApplyResourceChangeCalled bool + ApplyResourceChangeResponse providers.ApplyResourceChangeResponse + ApplyResourceChangeRequest providers.ApplyResourceChangeRequest + ApplyResourceChangeFn func(providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse + + ImportResourceStateCalled bool + ImportResourceStateResponse providers.ImportResourceStateResponse + ImportResourceStateRequest providers.ImportResourceStateRequest + ImportResourceStateFn func(providers.ImportResourceStateRequest) providers.ImportResourceStateResponse + // Legacy return type for existing tests, which will be shimmed into an + // ImportResourceStateResponse if set + ImportStateReturn []*InstanceState + + ReadDataSourceCalled bool + ReadDataSourceResponse providers.ReadDataSourceResponse + ReadDataSourceRequest providers.ReadDataSourceRequest + ReadDataSourceFn func(providers.ReadDataSourceRequest) providers.ReadDataSourceResponse + + CloseCalled bool + CloseError error +} + +func (p *MockProvider) GetSchema() providers.GetSchemaResponse { + p.Lock() + defer p.Unlock() + p.GetSchemaCalled = true + return p.getSchema() +} + +func (p *MockProvider) getSchema() providers.GetSchemaResponse { + // This version of getSchema doesn't do any locking, so it's suitable to + // call from other methods of this mock as long as they are already + // holding the lock. + + ret := providers.GetSchemaResponse{ + Provider: providers.Schema{}, + DataSources: map[string]providers.Schema{}, + ResourceTypes: map[string]providers.Schema{}, + } + if p.GetSchemaReturn != nil { + ret.Provider.Block = p.GetSchemaReturn.Provider + ret.ProviderMeta.Block = p.GetSchemaReturn.ProviderMeta + for n, s := range p.GetSchemaReturn.DataSources { + ret.DataSources[n] = providers.Schema{ + Block: s, + } + } + for n, s := range p.GetSchemaReturn.ResourceTypes { + ret.ResourceTypes[n] = providers.Schema{ + Version: int64(p.GetSchemaReturn.ResourceTypeSchemaVersions[n]), + Block: s, + } + } + } + + return ret +} + +func (p *MockProvider) PrepareProviderConfig(r providers.PrepareProviderConfigRequest) providers.PrepareProviderConfigResponse { + p.Lock() + defer p.Unlock() + + p.PrepareProviderConfigCalled = true + p.PrepareProviderConfigRequest = r + if p.PrepareProviderConfigFn != nil { + return p.PrepareProviderConfigFn(r) + } + p.PrepareProviderConfigResponse.PreparedConfig = r.Config + return p.PrepareProviderConfigResponse +} + +func (p *MockProvider) ValidateResourceTypeConfig(r providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse { + p.Lock() + defer p.Unlock() + + p.ValidateResourceTypeConfigCalled = true + p.ValidateResourceTypeConfigRequest = r + + if p.ValidateResourceTypeConfigFn != nil { + return p.ValidateResourceTypeConfigFn(r) + } + + return p.ValidateResourceTypeConfigResponse +} + +func (p *MockProvider) ValidateDataSourceConfig(r providers.ValidateDataSourceConfigRequest) providers.ValidateDataSourceConfigResponse { + p.Lock() + defer p.Unlock() + + p.ValidateDataSourceConfigCalled = true + p.ValidateDataSourceConfigRequest = r + + if p.ValidateDataSourceConfigFn != nil { + return p.ValidateDataSourceConfigFn(r) + } + + return p.ValidateDataSourceConfigResponse +} + +func (p *MockProvider) UpgradeResourceState(r providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse { + p.Lock() + defer p.Unlock() + + schemas := p.getSchema() + schema := schemas.ResourceTypes[r.TypeName] + schemaType := schema.Block.ImpliedType() + + p.UpgradeResourceStateCalled = true + p.UpgradeResourceStateRequest = r + + if p.UpgradeResourceStateFn != nil { + return p.UpgradeResourceStateFn(r) + } + + resp := p.UpgradeResourceStateResponse + + if resp.UpgradedState == cty.NilVal { + switch { + case r.RawStateFlatmap != nil: + v, err := hcl2shim.HCL2ValueFromFlatmap(r.RawStateFlatmap, schemaType) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp + } + resp.UpgradedState = v + case len(r.RawStateJSON) > 0: + v, err := ctyjson.Unmarshal(r.RawStateJSON, schemaType) + + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp + } + resp.UpgradedState = v + } + } + return resp +} + +func (p *MockProvider) Configure(r providers.ConfigureRequest) providers.ConfigureResponse { + p.Lock() + defer p.Unlock() + + p.ConfigureCalled = true + p.ConfigureRequest = r + + if p.ConfigureFn != nil { + return p.ConfigureFn(r) + } + + return p.ConfigureResponse +} + +func (p *MockProvider) Stop() error { + // We intentionally don't lock in this one because the whole point of this + // method is to be called concurrently with another operation that can + // be cancelled. The provider itself is responsible for handling + // any concurrency concerns in this case. + + p.StopCalled = true + if p.StopFn != nil { + return p.StopFn() + } + + return p.StopResponse +} + +func (p *MockProvider) ReadResource(r providers.ReadResourceRequest) providers.ReadResourceResponse { + p.Lock() + defer p.Unlock() + + p.ReadResourceCalled = true + p.ReadResourceRequest = r + + if p.ReadResourceFn != nil { + return p.ReadResourceFn(r) + } + + resp := p.ReadResourceResponse + if resp.NewState != cty.NilVal { + // make sure the NewState fits the schema + // This isn't always the case for the existing tests + newState, err := p.GetSchemaReturn.ResourceTypes[r.TypeName].CoerceValue(resp.NewState) + if err != nil { + panic(err) + } + resp.NewState = newState + return resp + } + + // just return the same state we received + resp.NewState = r.PriorState + return resp +} + +func (p *MockProvider) PlanResourceChange(r providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { + p.Lock() + defer p.Unlock() + + p.PlanResourceChangeCalled = true + p.PlanResourceChangeRequest = r + + if p.PlanResourceChangeFn != nil { + return p.PlanResourceChangeFn(r) + } + + return p.PlanResourceChangeResponse +} + +func (p *MockProvider) ApplyResourceChange(r providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { + p.Lock() + p.ApplyResourceChangeCalled = true + p.ApplyResourceChangeRequest = r + p.Unlock() + + if p.ApplyResourceChangeFn != nil { + return p.ApplyResourceChangeFn(r) + } + + return p.ApplyResourceChangeResponse +} + +func (p *MockProvider) ImportResourceState(r providers.ImportResourceStateRequest) providers.ImportResourceStateResponse { + p.Lock() + defer p.Unlock() + + if p.ImportStateReturn != nil { + for _, is := range p.ImportStateReturn { + if is.Attributes == nil { + is.Attributes = make(map[string]string) + } + is.Attributes["id"] = is.ID + + typeName := is.Ephemeral.Type + // Use the requested type if the resource has no type of it's own. + // We still return the empty type, which will error, but this prevents a panic. + if typeName == "" { + typeName = r.TypeName + } + + schema := p.GetSchemaReturn.ResourceTypes[typeName] + if schema == nil { + panic("no schema found for " + typeName) + } + + private, err := json.Marshal(is.Meta) + if err != nil { + panic(err) + } + + state, err := hcl2shim.HCL2ValueFromFlatmap(is.Attributes, schema.ImpliedType()) + if err != nil { + panic(err) + } + + state, err = schema.CoerceValue(state) + if err != nil { + panic(err) + } + + p.ImportResourceStateResponse.ImportedResources = append( + p.ImportResourceStateResponse.ImportedResources, + providers.ImportedResource{ + TypeName: is.Ephemeral.Type, + State: state, + Private: private, + }) + } + } + + p.ImportResourceStateCalled = true + p.ImportResourceStateRequest = r + if p.ImportResourceStateFn != nil { + return p.ImportResourceStateFn(r) + } + + return p.ImportResourceStateResponse +} + +func (p *MockProvider) ReadDataSource(r providers.ReadDataSourceRequest) providers.ReadDataSourceResponse { + p.Lock() + defer p.Unlock() + + p.ReadDataSourceCalled = true + p.ReadDataSourceRequest = r + + if p.ReadDataSourceFn != nil { + return p.ReadDataSourceFn(r) + } + + return p.ReadDataSourceResponse +} + +func (p *MockProvider) Close() error { + p.CloseCalled = true + return p.CloseError +} diff --git a/internal/legacy/terraform/provisioner_mock.go b/internal/legacy/terraform/provisioner_mock.go new file mode 100644 index 000000000..2a3323541 --- /dev/null +++ b/internal/legacy/terraform/provisioner_mock.go @@ -0,0 +1,104 @@ +package terraform + +import ( + "sync" + + "github.com/hashicorp/terraform/provisioners" +) + +var _ provisioners.Interface = (*MockProvisioner)(nil) + +// MockProvisioner implements provisioners.Interface but mocks out all the +// calls for testing purposes. +type MockProvisioner struct { + sync.Mutex + // Anything you want, in case you need to store extra data with the mock. + Meta interface{} + + GetSchemaCalled bool + GetSchemaResponse provisioners.GetSchemaResponse + + ValidateProvisionerConfigCalled bool + ValidateProvisionerConfigRequest provisioners.ValidateProvisionerConfigRequest + ValidateProvisionerConfigResponse provisioners.ValidateProvisionerConfigResponse + ValidateProvisionerConfigFn func(provisioners.ValidateProvisionerConfigRequest) provisioners.ValidateProvisionerConfigResponse + + ProvisionResourceCalled bool + ProvisionResourceRequest provisioners.ProvisionResourceRequest + ProvisionResourceResponse provisioners.ProvisionResourceResponse + ProvisionResourceFn func(provisioners.ProvisionResourceRequest) provisioners.ProvisionResourceResponse + + StopCalled bool + StopResponse error + StopFn func() error + + CloseCalled bool + CloseResponse error + CloseFn func() error +} + +func (p *MockProvisioner) GetSchema() provisioners.GetSchemaResponse { + p.Lock() + defer p.Unlock() + + p.GetSchemaCalled = true + return p.getSchema() +} + +// getSchema is the implementation of GetSchema, which can be called from other +// methods on MockProvisioner that may already be holding the lock. +func (p *MockProvisioner) getSchema() provisioners.GetSchemaResponse { + return p.GetSchemaResponse +} + +func (p *MockProvisioner) ValidateProvisionerConfig(r provisioners.ValidateProvisionerConfigRequest) provisioners.ValidateProvisionerConfigResponse { + p.Lock() + defer p.Unlock() + + p.ValidateProvisionerConfigCalled = true + p.ValidateProvisionerConfigRequest = r + if p.ValidateProvisionerConfigFn != nil { + return p.ValidateProvisionerConfigFn(r) + } + return p.ValidateProvisionerConfigResponse +} + +func (p *MockProvisioner) ProvisionResource(r provisioners.ProvisionResourceRequest) provisioners.ProvisionResourceResponse { + p.Lock() + defer p.Unlock() + + p.ProvisionResourceCalled = true + p.ProvisionResourceRequest = r + if p.ProvisionResourceFn != nil { + fn := p.ProvisionResourceFn + return fn(r) + } + + return p.ProvisionResourceResponse +} + +func (p *MockProvisioner) Stop() error { + // We intentionally don't lock in this one because the whole point of this + // method is to be called concurrently with another operation that can + // be cancelled. The provisioner itself is responsible for handling + // any concurrency concerns in this case. + + p.StopCalled = true + if p.StopFn != nil { + return p.StopFn() + } + + return p.StopResponse +} + +func (p *MockProvisioner) Close() error { + p.Lock() + defer p.Unlock() + + p.CloseCalled = true + if p.CloseFn != nil { + return p.CloseFn() + } + + return p.CloseResponse +} diff --git a/internal/legacy/terraform/resource.go b/internal/legacy/terraform/resource.go new file mode 100644 index 000000000..6273c8ace --- /dev/null +++ b/internal/legacy/terraform/resource.go @@ -0,0 +1,516 @@ +package terraform + +import ( + "fmt" + "reflect" + "sort" + "strconv" + "strings" + + "github.com/mitchellh/copystructure" + "github.com/mitchellh/reflectwalk" + "github.com/zclconf/go-cty/cty" + + "github.com/hashicorp/terraform/addrs" + "github.com/hashicorp/terraform/configs/configschema" + "github.com/hashicorp/terraform/configs/hcl2shim" +) + +// Resource is a legacy way to identify a particular resource instance. +// +// New code should use addrs.ResourceInstance instead. This is still here +// only for codepaths that haven't been updated yet. +type Resource struct { + // These are all used by the new EvalNode stuff. + Name string + Type string + CountIndex int + + // These aren't really used anymore anywhere, but we keep them around + // since we haven't done a proper cleanup yet. + Id string + Info *InstanceInfo + Config *ResourceConfig + Dependencies []string + Diff *InstanceDiff + Provider ResourceProvider + State *InstanceState + Flags ResourceFlag +} + +// NewResource constructs a legacy Resource object from an +// addrs.ResourceInstance value. +// +// This is provided to shim to old codepaths that haven't been updated away +// from this type yet. Since this old type is not able to represent instances +// that have string keys, this function will panic if given a resource address +// that has a string key. +func NewResource(addr addrs.ResourceInstance) *Resource { + ret := &Resource{ + Name: addr.Resource.Name, + Type: addr.Resource.Type, + } + + if addr.Key != addrs.NoKey { + switch tk := addr.Key.(type) { + case addrs.IntKey: + ret.CountIndex = int(tk) + default: + panic(fmt.Errorf("resource instance with key %#v is not supported", addr.Key)) + } + } + + return ret +} + +// ResourceKind specifies what kind of instance we're working with, whether +// its a primary instance, a tainted instance, or an orphan. +type ResourceFlag byte + +// InstanceInfo is used to hold information about the instance and/or +// resource being modified. +type InstanceInfo struct { + // Id is a unique name to represent this instance. This is not related + // to InstanceState.ID in any way. + Id string + + // ModulePath is the complete path of the module containing this + // instance. + ModulePath []string + + // Type is the resource type of this instance + Type string + + // uniqueExtra is an internal field that can be populated to supply + // extra metadata that is used to identify a unique instance in + // the graph walk. This will be appended to HumanID when uniqueId + // is called. + uniqueExtra string +} + +// NewInstanceInfo constructs an InstanceInfo from an addrs.AbsResourceInstance. +// +// InstanceInfo is a legacy type, and uses of it should be gradually replaced +// by direct use of addrs.AbsResource or addrs.AbsResourceInstance as +// appropriate. +// +// The legacy InstanceInfo type cannot represent module instances with instance +// keys, so this function will panic if given such a path. Uses of this type +// should all be removed or replaced before implementing "count" and "for_each" +// arguments on modules in order to avoid such panics. +// +// This legacy type also cannot represent resource instances with string +// instance keys. It will panic if the given key is not either NoKey or an +// IntKey. +func NewInstanceInfo(addr addrs.AbsResourceInstance) *InstanceInfo { + // We need an old-style []string module path for InstanceInfo. + path := make([]string, len(addr.Module)) + for i, step := range addr.Module { + if step.InstanceKey != addrs.NoKey { + panic("NewInstanceInfo cannot convert module instance with key") + } + path[i] = step.Name + } + + // This is a funny old meaning of "id" that is no longer current. It should + // not be used for anything users might see. Note that it does not include + // a representation of the resource mode, and so it's impossible to + // determine from an InstanceInfo alone whether it is a managed or data + // resource that is being referred to. + id := fmt.Sprintf("%s.%s", addr.Resource.Resource.Type, addr.Resource.Resource.Name) + if addr.Resource.Resource.Mode == addrs.DataResourceMode { + id = "data." + id + } + if addr.Resource.Key != addrs.NoKey { + switch k := addr.Resource.Key.(type) { + case addrs.IntKey: + id = id + fmt.Sprintf(".%d", int(k)) + default: + panic(fmt.Sprintf("NewInstanceInfo cannot convert resource instance with %T instance key", addr.Resource.Key)) + } + } + + return &InstanceInfo{ + Id: id, + ModulePath: path, + Type: addr.Resource.Resource.Type, + } +} + +// ResourceAddress returns the address of the resource that the receiver is describing. +func (i *InstanceInfo) ResourceAddress() *ResourceAddress { + // GROSS: for tainted and deposed instances, their status gets appended + // to i.Id to create a unique id for the graph node. Historically these + // ids were displayed to the user, so it's designed to be human-readable: + // "aws_instance.bar.0 (deposed #0)" + // + // So here we detect such suffixes and try to interpret them back to + // their original meaning so we can then produce a ResourceAddress + // with a suitable InstanceType. + id := i.Id + instanceType := TypeInvalid + if idx := strings.Index(id, " ("); idx != -1 { + remain := id[idx:] + id = id[:idx] + + switch { + case strings.Contains(remain, "tainted"): + instanceType = TypeTainted + case strings.Contains(remain, "deposed"): + instanceType = TypeDeposed + } + } + + addr, err := parseResourceAddressInternal(id) + if err != nil { + // should never happen, since that would indicate a bug in the + // code that constructed this InstanceInfo. + panic(fmt.Errorf("InstanceInfo has invalid Id %s", id)) + } + if len(i.ModulePath) > 1 { + addr.Path = i.ModulePath[1:] // trim off "root" prefix, which is implied + } + if instanceType != TypeInvalid { + addr.InstanceTypeSet = true + addr.InstanceType = instanceType + } + return addr +} + +// ResourceConfig is a legacy type that was formerly used to represent +// interpolatable configuration blocks. It is now only used to shim to old +// APIs that still use this type, via NewResourceConfigShimmed. +type ResourceConfig struct { + ComputedKeys []string + Raw map[string]interface{} + Config map[string]interface{} +} + +// NewResourceConfigRaw constructs a ResourceConfig whose content is exactly +// the given value. +// +// The given value may contain hcl2shim.UnknownVariableValue to signal that +// something is computed, but it must not contain unprocessed interpolation +// sequences as we might've seen in Terraform v0.11 and prior. +func NewResourceConfigRaw(raw map[string]interface{}) *ResourceConfig { + v := hcl2shim.HCL2ValueFromConfigValue(raw) + + // This is a little weird but we round-trip the value through the hcl2shim + // package here for two reasons: firstly, because that reduces the risk + // of it including something unlike what NewResourceConfigShimmed would + // produce, and secondly because it creates a copy of "raw" just in case + // something is relying on the fact that in the old world the raw and + // config maps were always distinct, and thus you could in principle mutate + // one without affecting the other. (I sure hope nobody was doing that, though!) + cfg := hcl2shim.ConfigValueFromHCL2(v).(map[string]interface{}) + + return &ResourceConfig{ + Raw: raw, + Config: cfg, + + ComputedKeys: newResourceConfigShimmedComputedKeys(v, ""), + } +} + +// NewResourceConfigShimmed wraps a cty.Value of object type in a legacy +// ResourceConfig object, so that it can be passed to older APIs that expect +// this wrapping. +// +// The returned ResourceConfig is already interpolated and cannot be +// re-interpolated. It is, therefore, useful only to functions that expect +// an already-populated ResourceConfig which they then treat as read-only. +// +// If the given value is not of an object type that conforms to the given +// schema then this function will panic. +func NewResourceConfigShimmed(val cty.Value, schema *configschema.Block) *ResourceConfig { + if !val.Type().IsObjectType() { + panic(fmt.Errorf("NewResourceConfigShimmed given %#v; an object type is required", val.Type())) + } + ret := &ResourceConfig{} + + legacyVal := hcl2shim.ConfigValueFromHCL2Block(val, schema) + if legacyVal != nil { + ret.Config = legacyVal + + // Now we need to walk through our structure and find any unknown values, + // producing the separate list ComputedKeys to represent these. We use the + // schema here so that we can preserve the expected invariant + // that an attribute is always either wholly known or wholly unknown, while + // a child block can be partially unknown. + ret.ComputedKeys = newResourceConfigShimmedComputedKeys(val, "") + } else { + ret.Config = make(map[string]interface{}) + } + ret.Raw = ret.Config + + return ret +} + +// Record the any config values in ComputedKeys. This field had been unused in +// helper/schema, but in the new protocol we're using this so that the SDK can +// now handle having an unknown collection. The legacy diff code doesn't +// properly handle the unknown, because it can't be expressed in the same way +// between the config and diff. +func newResourceConfigShimmedComputedKeys(val cty.Value, path string) []string { + var ret []string + ty := val.Type() + + if val.IsNull() { + return ret + } + + if !val.IsKnown() { + // we shouldn't have an entirely unknown resource, but prevent empty + // strings just in case + if len(path) > 0 { + ret = append(ret, path) + } + return ret + } + + if path != "" { + path += "." + } + switch { + case ty.IsListType(), ty.IsTupleType(), ty.IsSetType(): + i := 0 + for it := val.ElementIterator(); it.Next(); i++ { + _, subVal := it.Element() + keys := newResourceConfigShimmedComputedKeys(subVal, fmt.Sprintf("%s%d", path, i)) + ret = append(ret, keys...) + } + + case ty.IsMapType(), ty.IsObjectType(): + for it := val.ElementIterator(); it.Next(); { + subK, subVal := it.Element() + keys := newResourceConfigShimmedComputedKeys(subVal, fmt.Sprintf("%s%s", path, subK.AsString())) + ret = append(ret, keys...) + } + } + + return ret +} + +// DeepCopy performs a deep copy of the configuration. This makes it safe +// to modify any of the structures that are part of the resource config without +// affecting the original configuration. +func (c *ResourceConfig) DeepCopy() *ResourceConfig { + // DeepCopying a nil should return a nil to avoid panics + if c == nil { + return nil + } + + // Copy, this will copy all the exported attributes + copy, err := copystructure.Config{Lock: true}.Copy(c) + if err != nil { + panic(err) + } + + // Force the type + result := copy.(*ResourceConfig) + + return result +} + +// Equal checks the equality of two resource configs. +func (c *ResourceConfig) Equal(c2 *ResourceConfig) bool { + // If either are nil, then they're only equal if they're both nil + if c == nil || c2 == nil { + return c == c2 + } + + // Sort the computed keys so they're deterministic + sort.Strings(c.ComputedKeys) + sort.Strings(c2.ComputedKeys) + + // Two resource configs if their exported properties are equal. + // We don't compare "raw" because it is never used again after + // initialization and for all intents and purposes they are equal + // if the exported properties are equal. + check := [][2]interface{}{ + {c.ComputedKeys, c2.ComputedKeys}, + {c.Raw, c2.Raw}, + {c.Config, c2.Config}, + } + for _, pair := range check { + if !reflect.DeepEqual(pair[0], pair[1]) { + return false + } + } + + return true +} + +// CheckSet checks that the given list of configuration keys is +// properly set. If not, errors are returned for each unset key. +// +// This is useful to be called in the Validate method of a ResourceProvider. +func (c *ResourceConfig) CheckSet(keys []string) []error { + var errs []error + + for _, k := range keys { + if !c.IsSet(k) { + errs = append(errs, fmt.Errorf("%s must be set", k)) + } + } + + return errs +} + +// Get looks up a configuration value by key and returns the value. +// +// The second return value is true if the get was successful. Get will +// return the raw value if the key is computed, so you should pair this +// with IsComputed. +func (c *ResourceConfig) Get(k string) (interface{}, bool) { + // We aim to get a value from the configuration. If it is computed, + // then we return the pure raw value. + source := c.Config + if c.IsComputed(k) { + source = c.Raw + } + + return c.get(k, source) +} + +// GetRaw looks up a configuration value by key and returns the value, +// from the raw, uninterpolated config. +// +// The second return value is true if the get was successful. Get will +// not succeed if the value is being computed. +func (c *ResourceConfig) GetRaw(k string) (interface{}, bool) { + return c.get(k, c.Raw) +} + +// IsComputed returns whether the given key is computed or not. +func (c *ResourceConfig) IsComputed(k string) bool { + // The next thing we do is check the config if we get a computed + // value out of it. + v, ok := c.get(k, c.Config) + if !ok { + return false + } + + // If value is nil, then it isn't computed + if v == nil { + return false + } + + // Test if the value contains an unknown value + var w unknownCheckWalker + if err := reflectwalk.Walk(v, &w); err != nil { + panic(err) + } + + return w.Unknown +} + +// IsSet checks if the key in the configuration is set. A key is set if +// it has a value or the value is being computed (is unknown currently). +// +// This function should be used rather than checking the keys of the +// raw configuration itself, since a key may be omitted from the raw +// configuration if it is being computed. +func (c *ResourceConfig) IsSet(k string) bool { + if c == nil { + return false + } + + if c.IsComputed(k) { + return true + } + + if _, ok := c.Get(k); ok { + return true + } + + return false +} + +func (c *ResourceConfig) get( + k string, raw map[string]interface{}) (interface{}, bool) { + parts := strings.Split(k, ".") + if len(parts) == 1 && parts[0] == "" { + parts = nil + } + + var current interface{} = raw + var previous interface{} = nil + for i, part := range parts { + if current == nil { + return nil, false + } + + cv := reflect.ValueOf(current) + switch cv.Kind() { + case reflect.Map: + previous = current + v := cv.MapIndex(reflect.ValueOf(part)) + if !v.IsValid() { + if i > 0 && i != (len(parts)-1) { + tryKey := strings.Join(parts[i:], ".") + v := cv.MapIndex(reflect.ValueOf(tryKey)) + if !v.IsValid() { + return nil, false + } + + return v.Interface(), true + } + + return nil, false + } + + current = v.Interface() + case reflect.Slice: + previous = current + + if part == "#" { + // If any value in a list is computed, this whole thing + // is computed and we can't read any part of it. + for i := 0; i < cv.Len(); i++ { + if v := cv.Index(i).Interface(); v == hcl2shim.UnknownVariableValue { + return v, true + } + } + + current = cv.Len() + } else { + i, err := strconv.ParseInt(part, 0, 0) + if err != nil { + return nil, false + } + if int(i) < 0 || int(i) >= cv.Len() { + return nil, false + } + current = cv.Index(int(i)).Interface() + } + case reflect.String: + // This happens when map keys contain "." and have a common + // prefix so were split as path components above. + actualKey := strings.Join(parts[i-1:], ".") + if prevMap, ok := previous.(map[string]interface{}); ok { + v, ok := prevMap[actualKey] + return v, ok + } + + return nil, false + default: + panic(fmt.Sprintf("Unknown kind: %s", cv.Kind())) + } + } + + return current, true +} + +// unknownCheckWalker +type unknownCheckWalker struct { + Unknown bool +} + +func (w *unknownCheckWalker) Primitive(v reflect.Value) error { + if v.Interface() == hcl2shim.UnknownVariableValue { + w.Unknown = true + } + + return nil +} diff --git a/internal/legacy/terraform/resource_address.go b/internal/legacy/terraform/resource_address.go new file mode 100644 index 000000000..4acf122b3 --- /dev/null +++ b/internal/legacy/terraform/resource_address.go @@ -0,0 +1,618 @@ +package terraform + +import ( + "fmt" + "reflect" + "regexp" + "strconv" + "strings" + + "github.com/hashicorp/terraform/addrs" + "github.com/hashicorp/terraform/configs" +) + +// ResourceAddress is a way of identifying an individual resource (or, +// eventually, a subset of resources) within the state. It is used for Targets. +type ResourceAddress struct { + // Addresses a resource falling somewhere in the module path + // When specified alone, addresses all resources within a module path + Path []string + + // Addresses a specific resource that occurs in a list + Index int + + InstanceType InstanceType + InstanceTypeSet bool + Name string + Type string + Mode ResourceMode // significant only if InstanceTypeSet +} + +// Copy returns a copy of this ResourceAddress +func (r *ResourceAddress) Copy() *ResourceAddress { + if r == nil { + return nil + } + + n := &ResourceAddress{ + Path: make([]string, 0, len(r.Path)), + Index: r.Index, + InstanceType: r.InstanceType, + Name: r.Name, + Type: r.Type, + Mode: r.Mode, + } + + n.Path = append(n.Path, r.Path...) + + return n +} + +// String outputs the address that parses into this address. +func (r *ResourceAddress) String() string { + var result []string + for _, p := range r.Path { + result = append(result, "module", p) + } + + switch r.Mode { + case ManagedResourceMode: + // nothing to do + case DataResourceMode: + result = append(result, "data") + default: + panic(fmt.Errorf("unsupported resource mode %s", r.Mode)) + } + + if r.Type != "" { + result = append(result, r.Type) + } + + if r.Name != "" { + name := r.Name + if r.InstanceTypeSet { + switch r.InstanceType { + case TypePrimary: + name += ".primary" + case TypeDeposed: + name += ".deposed" + case TypeTainted: + name += ".tainted" + } + } + + if r.Index >= 0 { + name += fmt.Sprintf("[%d]", r.Index) + } + result = append(result, name) + } + + return strings.Join(result, ".") +} + +// HasResourceSpec returns true if the address has a resource spec, as +// defined in the documentation: +// https://www.terraform.io/docs/internals/resource-addressing.html +// In particular, this returns false if the address contains only +// a module path, thus addressing the entire module. +func (r *ResourceAddress) HasResourceSpec() bool { + return r.Type != "" && r.Name != "" +} + +// WholeModuleAddress returns the resource address that refers to all +// resources in the same module as the receiver address. +func (r *ResourceAddress) WholeModuleAddress() *ResourceAddress { + return &ResourceAddress{ + Path: r.Path, + Index: -1, + InstanceTypeSet: false, + } +} + +// MatchesResourceConfig returns true if the receiver matches the given +// configuration resource within the given _static_ module path. Note that +// the module path in a resource address is a _dynamic_ module path, and +// multiple dynamic resource paths may map to a single static path if +// count and for_each are in use on module calls. +// +// Since resource configuration blocks represent all of the instances of +// a multi-instance resource, the index of the address (if any) is not +// considered. +func (r *ResourceAddress) MatchesResourceConfig(path addrs.Module, rc *configs.Resource) bool { + if r.HasResourceSpec() { + // FIXME: Some ugliness while we are between worlds. Functionality + // in "addrs" should eventually replace this ResourceAddress idea + // completely, but for now we'll need to translate to the old + // way of representing resource modes. + switch r.Mode { + case ManagedResourceMode: + if rc.Mode != addrs.ManagedResourceMode { + return false + } + case DataResourceMode: + if rc.Mode != addrs.DataResourceMode { + return false + } + } + if r.Type != rc.Type || r.Name != rc.Name { + return false + } + } + + addrPath := r.Path + + // normalize + if len(addrPath) == 0 { + addrPath = nil + } + if len(path) == 0 { + path = nil + } + rawPath := []string(path) + return reflect.DeepEqual(addrPath, rawPath) +} + +// stateId returns the ID that this resource should be entered with +// in the state. This is also used for diffs. In the future, we'd like to +// move away from this string field so I don't export this. +func (r *ResourceAddress) stateId() string { + result := fmt.Sprintf("%s.%s", r.Type, r.Name) + switch r.Mode { + case ManagedResourceMode: + // Done + case DataResourceMode: + result = fmt.Sprintf("data.%s", result) + default: + panic(fmt.Errorf("unknown resource mode: %s", r.Mode)) + } + if r.Index >= 0 { + result += fmt.Sprintf(".%d", r.Index) + } + + return result +} + +// parseResourceAddressInternal parses the somewhat bespoke resource +// identifier used in states and diffs, such as "instance.name.0". +func parseResourceAddressInternal(s string) (*ResourceAddress, error) { + // Split based on ".". Every resource address should have at least two + // elements (type and name). + parts := strings.Split(s, ".") + if len(parts) < 2 || len(parts) > 4 { + return nil, fmt.Errorf("Invalid internal resource address format: %s", s) + } + + // Data resource if we have at least 3 parts and the first one is data + mode := ManagedResourceMode + if len(parts) > 2 && parts[0] == "data" { + mode = DataResourceMode + parts = parts[1:] + } + + // If we're not a data resource and we have more than 3, then it is an error + if len(parts) > 3 && mode != DataResourceMode { + return nil, fmt.Errorf("Invalid internal resource address format: %s", s) + } + + // Build the parts of the resource address that are guaranteed to exist + addr := &ResourceAddress{ + Type: parts[0], + Name: parts[1], + Index: -1, + InstanceType: TypePrimary, + Mode: mode, + } + + // If we have more parts, then we have an index. Parse that. + if len(parts) > 2 { + idx, err := strconv.ParseInt(parts[2], 0, 0) + if err != nil { + return nil, fmt.Errorf("Error parsing resource address %q: %s", s, err) + } + + addr.Index = int(idx) + } + + return addr, nil +} + +func ParseResourceAddress(s string) (*ResourceAddress, error) { + matches, err := tokenizeResourceAddress(s) + if err != nil { + return nil, err + } + mode := ManagedResourceMode + if matches["data_prefix"] != "" { + mode = DataResourceMode + } + resourceIndex, err := ParseResourceIndex(matches["index"]) + if err != nil { + return nil, err + } + instanceType, err := ParseInstanceType(matches["instance_type"]) + if err != nil { + return nil, err + } + path := ParseResourcePath(matches["path"]) + + // not allowed to say "data." without a type following + if mode == DataResourceMode && matches["type"] == "" { + return nil, fmt.Errorf( + "invalid resource address %q: must target specific data instance", + s, + ) + } + + return &ResourceAddress{ + Path: path, + Index: resourceIndex, + InstanceType: instanceType, + InstanceTypeSet: matches["instance_type"] != "", + Name: matches["name"], + Type: matches["type"], + Mode: mode, + }, nil +} + +// ParseResourceAddressForInstanceDiff creates a ResourceAddress for a +// resource name as described in a module diff. +// +// For historical reasons a different addressing format is used in this +// context. The internal format should not be shown in the UI and instead +// this function should be used to translate to a ResourceAddress and +// then, where appropriate, use the String method to produce a canonical +// resource address string for display in the UI. +// +// The given path slice must be empty (or nil) for the root module, and +// otherwise consist of a sequence of module names traversing down into +// the module tree. If a non-nil path is provided, the caller must not +// modify its underlying array after passing it to this function. +func ParseResourceAddressForInstanceDiff(path []string, key string) (*ResourceAddress, error) { + addr, err := parseResourceAddressInternal(key) + if err != nil { + return nil, err + } + addr.Path = path + return addr, nil +} + +// NewLegacyResourceAddress creates a ResourceAddress from a new-style +// addrs.AbsResource value. +// +// This is provided for shimming purposes so that we can still easily call into +// older functions that expect the ResourceAddress type. +func NewLegacyResourceAddress(addr addrs.AbsResource) *ResourceAddress { + ret := &ResourceAddress{ + Type: addr.Resource.Type, + Name: addr.Resource.Name, + } + + switch addr.Resource.Mode { + case addrs.ManagedResourceMode: + ret.Mode = ManagedResourceMode + case addrs.DataResourceMode: + ret.Mode = DataResourceMode + default: + panic(fmt.Errorf("cannot shim %s to legacy ResourceMode value", addr.Resource.Mode)) + } + + path := make([]string, len(addr.Module)) + for i, step := range addr.Module { + if step.InstanceKey != addrs.NoKey { + // At the time of writing this can't happen because we don't + // ket generate keyed module instances. This legacy codepath must + // be removed before we can support "count" and "for_each" for + // modules. + panic(fmt.Errorf("cannot shim module instance step with key %#v to legacy ResourceAddress.Path", step.InstanceKey)) + } + + path[i] = step.Name + } + ret.Path = path + ret.Index = -1 + + return ret +} + +// NewLegacyResourceInstanceAddress creates a ResourceAddress from a new-style +// addrs.AbsResource value. +// +// This is provided for shimming purposes so that we can still easily call into +// older functions that expect the ResourceAddress type. +func NewLegacyResourceInstanceAddress(addr addrs.AbsResourceInstance) *ResourceAddress { + ret := &ResourceAddress{ + Type: addr.Resource.Resource.Type, + Name: addr.Resource.Resource.Name, + } + + switch addr.Resource.Resource.Mode { + case addrs.ManagedResourceMode: + ret.Mode = ManagedResourceMode + case addrs.DataResourceMode: + ret.Mode = DataResourceMode + default: + panic(fmt.Errorf("cannot shim %s to legacy ResourceMode value", addr.Resource.Resource.Mode)) + } + + path := make([]string, len(addr.Module)) + for i, step := range addr.Module { + if step.InstanceKey != addrs.NoKey { + // At the time of writing this can't happen because we don't + // ket generate keyed module instances. This legacy codepath must + // be removed before we can support "count" and "for_each" for + // modules. + panic(fmt.Errorf("cannot shim module instance step with key %#v to legacy ResourceAddress.Path", step.InstanceKey)) + } + + path[i] = step.Name + } + ret.Path = path + + if addr.Resource.Key == addrs.NoKey { + ret.Index = -1 + } else if ik, ok := addr.Resource.Key.(addrs.IntKey); ok { + ret.Index = int(ik) + } else if _, ok := addr.Resource.Key.(addrs.StringKey); ok { + ret.Index = -1 + } else { + panic(fmt.Errorf("cannot shim resource instance with key %#v to legacy ResourceAddress.Index", addr.Resource.Key)) + } + + return ret +} + +// AbsResourceInstanceAddr converts the receiver, a legacy resource address, to +// the new resource address type addrs.AbsResourceInstance. +// +// This method can be used only on an address that has a resource specification. +// It will panic if called on a module-path-only ResourceAddress. Use +// method HasResourceSpec to check before calling, in contexts where it is +// unclear. +// +// addrs.AbsResourceInstance does not represent the "tainted" and "deposed" +// states, and so if these are present on the receiver then they are discarded. +// +// This is provided for shimming purposes so that we can easily adapt functions +// that are returning the legacy ResourceAddress type, for situations where +// the new type is required. +func (addr *ResourceAddress) AbsResourceInstanceAddr() addrs.AbsResourceInstance { + if !addr.HasResourceSpec() { + panic("AbsResourceInstanceAddr called on ResourceAddress with no resource spec") + } + + ret := addrs.AbsResourceInstance{ + Module: addr.ModuleInstanceAddr(), + Resource: addrs.ResourceInstance{ + Resource: addrs.Resource{ + Type: addr.Type, + Name: addr.Name, + }, + }, + } + + switch addr.Mode { + case ManagedResourceMode: + ret.Resource.Resource.Mode = addrs.ManagedResourceMode + case DataResourceMode: + ret.Resource.Resource.Mode = addrs.DataResourceMode + default: + panic(fmt.Errorf("cannot shim %s to addrs.ResourceMode value", addr.Mode)) + } + + if addr.Index != -1 { + ret.Resource.Key = addrs.IntKey(addr.Index) + } + + return ret +} + +// ModuleInstanceAddr returns the module path portion of the receiver as a +// addrs.ModuleInstance value. +func (addr *ResourceAddress) ModuleInstanceAddr() addrs.ModuleInstance { + path := make(addrs.ModuleInstance, len(addr.Path)) + for i, name := range addr.Path { + path[i] = addrs.ModuleInstanceStep{Name: name} + } + return path +} + +// Contains returns true if and only if the given node is contained within +// the receiver. +// +// Containment is defined in terms of the module and resource heirarchy: +// a resource is contained within its module and any ancestor modules, +// an indexed resource instance is contained with the unindexed resource, etc. +func (addr *ResourceAddress) Contains(other *ResourceAddress) bool { + ourPath := addr.Path + givenPath := other.Path + if len(givenPath) < len(ourPath) { + return false + } + for i := range ourPath { + if ourPath[i] != givenPath[i] { + return false + } + } + + // If the receiver is a whole-module address then the path prefix + // matching is all we need. + if !addr.HasResourceSpec() { + return true + } + + if addr.Type != other.Type || addr.Name != other.Name || addr.Mode != other.Mode { + return false + } + + if addr.Index != -1 && addr.Index != other.Index { + return false + } + + if addr.InstanceTypeSet && (addr.InstanceTypeSet != other.InstanceTypeSet || addr.InstanceType != other.InstanceType) { + return false + } + + return true +} + +// Equals returns true if the receiver matches the given address. +// +// The name of this method is a misnomer, since it doesn't test for exact +// equality. Instead, it tests that the _specified_ parts of each +// address match, treating any unspecified parts as wildcards. +// +// See also Contains, which takes a more hierarchical approach to comparing +// addresses. +func (addr *ResourceAddress) Equals(raw interface{}) bool { + other, ok := raw.(*ResourceAddress) + if !ok { + return false + } + + pathMatch := len(addr.Path) == 0 && len(other.Path) == 0 || + reflect.DeepEqual(addr.Path, other.Path) + + indexMatch := addr.Index == -1 || + other.Index == -1 || + addr.Index == other.Index + + nameMatch := addr.Name == "" || + other.Name == "" || + addr.Name == other.Name + + typeMatch := addr.Type == "" || + other.Type == "" || + addr.Type == other.Type + + // mode is significant only when type is set + modeMatch := addr.Type == "" || + other.Type == "" || + addr.Mode == other.Mode + + return pathMatch && + indexMatch && + addr.InstanceType == other.InstanceType && + nameMatch && + typeMatch && + modeMatch +} + +// Less returns true if and only if the receiver should be sorted before +// the given address when presenting a list of resource addresses to +// an end-user. +// +// This sort uses lexicographic sorting for most components, but uses +// numeric sort for indices, thus causing index 10 to sort after +// index 9, rather than after index 1. +func (addr *ResourceAddress) Less(other *ResourceAddress) bool { + + switch { + + case len(addr.Path) != len(other.Path): + return len(addr.Path) < len(other.Path) + + case !reflect.DeepEqual(addr.Path, other.Path): + // If the two paths are the same length but don't match, we'll just + // cheat and compare the string forms since it's easier than + // comparing all of the path segments in turn, and lexicographic + // comparison is correct for the module path portion. + addrStr := addr.String() + otherStr := other.String() + return addrStr < otherStr + + case addr.Mode != other.Mode: + return addr.Mode == DataResourceMode + + case addr.Type != other.Type: + return addr.Type < other.Type + + case addr.Name != other.Name: + return addr.Name < other.Name + + case addr.Index != other.Index: + // Since "Index" is -1 for an un-indexed address, this also conveniently + // sorts unindexed addresses before indexed ones, should they both + // appear for some reason. + return addr.Index < other.Index + + case addr.InstanceTypeSet != other.InstanceTypeSet: + return !addr.InstanceTypeSet + + case addr.InstanceType != other.InstanceType: + // InstanceType is actually an enum, so this is just an arbitrary + // sort based on the enum numeric values, and thus not particularly + // meaningful. + return addr.InstanceType < other.InstanceType + + default: + return false + + } +} + +func ParseResourceIndex(s string) (int, error) { + if s == "" { + return -1, nil + } + return strconv.Atoi(s) +} + +func ParseResourcePath(s string) []string { + if s == "" { + return nil + } + parts := strings.Split(s, ".") + path := make([]string, 0, len(parts)) + for _, s := range parts { + // Due to the limitations of the regexp match below, the path match has + // some noise in it we have to filter out :| + if s == "" || s == "module" { + continue + } + path = append(path, s) + } + return path +} + +func ParseInstanceType(s string) (InstanceType, error) { + switch s { + case "", "primary": + return TypePrimary, nil + case "deposed": + return TypeDeposed, nil + case "tainted": + return TypeTainted, nil + default: + return TypeInvalid, fmt.Errorf("Unexpected value for InstanceType field: %q", s) + } +} + +func tokenizeResourceAddress(s string) (map[string]string, error) { + // Example of portions of the regexp below using the + // string "aws_instance.web.tainted[1]" + re := regexp.MustCompile(`\A` + + // "module.foo.module.bar" (optional) + `(?P(?:module\.(?P[^.]+)\.?)*)` + + // possibly "data.", if targeting is a data resource + `(?P(?:data\.)?)` + + // "aws_instance.web" (optional when module path specified) + `(?:(?P[^.]+)\.(?P[^.[]+))?` + + // "tainted" (optional, omission implies: "primary") + `(?:\.(?P\w+))?` + + // "1" (optional, omission implies: "0") + `(?:\[(?P\d+)\])?` + + `\z`) + + groupNames := re.SubexpNames() + rawMatches := re.FindAllStringSubmatch(s, -1) + if len(rawMatches) != 1 { + return nil, fmt.Errorf("invalid resource address %q", s) + } + + matches := make(map[string]string) + for i, m := range rawMatches[0] { + matches[groupNames[i]] = m + } + + return matches, nil +} diff --git a/internal/legacy/terraform/resource_address_test.go b/internal/legacy/terraform/resource_address_test.go new file mode 100644 index 000000000..3bb5f2082 --- /dev/null +++ b/internal/legacy/terraform/resource_address_test.go @@ -0,0 +1,1329 @@ +package terraform + +import ( + "fmt" + "reflect" + "testing" + + "github.com/hashicorp/terraform/addrs" + "github.com/hashicorp/terraform/configs" +) + +func TestParseResourceAddressInternal(t *testing.T) { + cases := map[string]struct { + Input string + Expected *ResourceAddress + Output string + }{ + "basic resource": { + "aws_instance.foo", + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: -1, + }, + "aws_instance.foo", + }, + + "basic resource with count": { + "aws_instance.foo.1", + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 1, + }, + "aws_instance.foo[1]", + }, + + "data resource": { + "data.aws_ami.foo", + &ResourceAddress{ + Mode: DataResourceMode, + Type: "aws_ami", + Name: "foo", + InstanceType: TypePrimary, + Index: -1, + }, + "data.aws_ami.foo", + }, + + "data resource with count": { + "data.aws_ami.foo.1", + &ResourceAddress{ + Mode: DataResourceMode, + Type: "aws_ami", + Name: "foo", + InstanceType: TypePrimary, + Index: 1, + }, + "data.aws_ami.foo[1]", + }, + + "non-data resource with 4 elements": { + "aws_instance.foo.bar.1", + nil, + "", + }, + } + + for tn, tc := range cases { + t.Run(tc.Input, func(t *testing.T) { + out, err := parseResourceAddressInternal(tc.Input) + if (err != nil) != (tc.Expected == nil) { + t.Fatalf("%s: unexpected err: %#v", tn, err) + } + if err != nil { + return + } + + if !reflect.DeepEqual(out, tc.Expected) { + t.Fatalf("bad: %q\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.Expected, out) + } + + // Compare outputs if those exist + expected := tc.Input + if tc.Output != "" { + expected = tc.Output + } + if out.String() != expected { + t.Fatalf("bad: %q\n\nexpected: %s\n\ngot: %s", tn, expected, out) + } + + // Compare equality because the internal parse is used + // to compare equality to equal inputs. + if !out.Equals(tc.Expected) { + t.Fatalf("expected equality:\n\n%#v\n\n%#v", out, tc.Expected) + } + }) + } +} + +func TestParseResourceAddress(t *testing.T) { + cases := map[string]struct { + Input string + Expected *ResourceAddress + Output string + Err bool + }{ + "implicit primary managed instance, no specific index": { + "aws_instance.foo", + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: -1, + }, + "", + false, + }, + "implicit primary data instance, no specific index": { + "data.aws_instance.foo", + &ResourceAddress{ + Mode: DataResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: -1, + }, + "", + false, + }, + "implicit primary, explicit index": { + "aws_instance.foo[2]", + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 2, + }, + "", + false, + }, + "implicit primary, explicit index over ten": { + "aws_instance.foo[12]", + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 12, + }, + "", + false, + }, + "explicit primary, explicit index": { + "aws_instance.foo.primary[2]", + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + InstanceTypeSet: true, + Index: 2, + }, + "", + false, + }, + "tainted": { + "aws_instance.foo.tainted", + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypeTainted, + InstanceTypeSet: true, + Index: -1, + }, + "", + false, + }, + "deposed": { + "aws_instance.foo.deposed", + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypeDeposed, + InstanceTypeSet: true, + Index: -1, + }, + "", + false, + }, + "with a hyphen": { + "aws_instance.foo-bar", + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo-bar", + InstanceType: TypePrimary, + Index: -1, + }, + "", + false, + }, + "managed in a module": { + "module.child.aws_instance.foo", + &ResourceAddress{ + Path: []string{"child"}, + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: -1, + }, + "", + false, + }, + "data in a module": { + "module.child.data.aws_instance.foo", + &ResourceAddress{ + Path: []string{"child"}, + Mode: DataResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: -1, + }, + "", + false, + }, + "nested modules": { + "module.a.module.b.module.forever.aws_instance.foo", + &ResourceAddress{ + Path: []string{"a", "b", "forever"}, + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: -1, + }, + "", + false, + }, + "just a module": { + "module.a", + &ResourceAddress{ + Path: []string{"a"}, + Type: "", + Name: "", + InstanceType: TypePrimary, + Index: -1, + }, + "", + false, + }, + "just a nested module": { + "module.a.module.b", + &ResourceAddress{ + Path: []string{"a", "b"}, + Type: "", + Name: "", + InstanceType: TypePrimary, + Index: -1, + }, + "", + false, + }, + "module missing resource type": { + "module.name.foo", + nil, + "", + true, + }, + } + + for tn, tc := range cases { + t.Run(tn, func(t *testing.T) { + out, err := ParseResourceAddress(tc.Input) + if (err != nil) != tc.Err { + t.Fatalf("%s: unexpected err: %#v", tn, err) + } + if tc.Err { + return + } + + if !reflect.DeepEqual(out, tc.Expected) { + t.Fatalf("bad: %q\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.Expected, out) + } + + expected := tc.Input + if tc.Output != "" { + expected = tc.Output + } + if out.String() != expected { + t.Fatalf("bad: %q\n\nexpected: %s\n\ngot: %s", tn, expected, out) + } + }) + } +} + +func TestResourceAddressContains(t *testing.T) { + tests := []struct { + Address *ResourceAddress + Other *ResourceAddress + Want bool + }{ + { + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceTypeSet: true, + InstanceType: TypePrimary, + Index: 0, + }, + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceTypeSet: true, + InstanceType: TypePrimary, + Index: 0, + }, + true, + }, + { + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceTypeSet: false, + Index: 0, + }, + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceTypeSet: true, + InstanceType: TypePrimary, + Index: 0, + }, + true, + }, + { + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceTypeSet: false, + Index: -1, + }, + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceTypeSet: true, + InstanceType: TypePrimary, + Index: 0, + }, + true, + }, + { + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceTypeSet: false, + Index: -1, + }, + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceTypeSet: false, + Index: -1, + }, + true, + }, + { + &ResourceAddress{ + InstanceTypeSet: false, + Index: -1, + }, + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceTypeSet: false, + Index: -1, + }, + true, + }, + { + &ResourceAddress{ + InstanceTypeSet: false, + Index: -1, + }, + &ResourceAddress{ + Path: []string{"bar"}, + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceTypeSet: false, + Index: -1, + }, + true, + }, + { + &ResourceAddress{ + Path: []string{"bar"}, + InstanceTypeSet: false, + Index: -1, + }, + &ResourceAddress{ + Path: []string{"bar"}, + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceTypeSet: false, + Index: -1, + }, + true, + }, + { + &ResourceAddress{ + Path: []string{"bar"}, + InstanceTypeSet: false, + Index: -1, + }, + &ResourceAddress{ + Path: []string{"bar", "baz"}, + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceTypeSet: false, + Index: -1, + }, + true, + }, + { + &ResourceAddress{ + Path: []string{"bar"}, + InstanceTypeSet: false, + Index: -1, + }, + &ResourceAddress{ + Path: []string{"bar", "baz"}, + InstanceTypeSet: false, + Index: -1, + }, + true, + }, + { + &ResourceAddress{ + Path: []string{"bar"}, + InstanceTypeSet: false, + Index: -1, + }, + &ResourceAddress{ + Path: []string{"bar", "baz", "foo", "pizza"}, + InstanceTypeSet: false, + Index: -1, + }, + true, + }, + + { + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "bar", + InstanceTypeSet: true, + InstanceType: TypePrimary, + Index: 0, + }, + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceTypeSet: true, + InstanceType: TypePrimary, + Index: 0, + }, + false, + }, + { + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceTypeSet: true, + InstanceType: TypePrimary, + Index: 0, + }, + &ResourceAddress{ + Mode: DataResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceTypeSet: true, + InstanceType: TypePrimary, + Index: 0, + }, + false, + }, + { + &ResourceAddress{ + Path: []string{"bar"}, + InstanceTypeSet: false, + Index: -1, + }, + &ResourceAddress{ + Path: []string{"baz"}, + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceTypeSet: false, + Index: -1, + }, + false, + }, + { + &ResourceAddress{ + Path: []string{"bar"}, + InstanceTypeSet: false, + Index: -1, + }, + &ResourceAddress{ + Path: []string{"baz", "bar"}, + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceTypeSet: false, + Index: -1, + }, + false, + }, + { + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceTypeSet: true, + InstanceType: TypePrimary, + Index: 0, + }, + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceTypeSet: false, + Index: 0, + }, + false, + }, + { + &ResourceAddress{ + Path: []string{"bar", "baz"}, + InstanceTypeSet: false, + Index: -1, + }, + &ResourceAddress{ + Path: []string{"bar"}, + InstanceTypeSet: false, + Index: -1, + }, + false, + }, + { + &ResourceAddress{ + Type: "aws_instance", + Name: "foo", + Index: 1, + InstanceType: TypePrimary, + Mode: ManagedResourceMode, + }, + &ResourceAddress{ + Type: "aws_instance", + Name: "foo", + Index: -1, + InstanceType: TypePrimary, + Mode: ManagedResourceMode, + }, + false, + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%s contains %s", test.Address, test.Other), func(t *testing.T) { + got := test.Address.Contains(test.Other) + if got != test.Want { + t.Errorf( + "wrong result\nrecv: %s\ngiven: %s\ngot: %#v\nwant: %#v", + test.Address, test.Other, + got, test.Want, + ) + } + }) + } +} + +func TestResourceAddressEquals(t *testing.T) { + cases := map[string]struct { + Address *ResourceAddress + Other interface{} + Expect bool + }{ + "basic match": { + Address: &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 0, + }, + Other: &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 0, + }, + Expect: true, + }, + "address does not set index": { + Address: &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: -1, + }, + Other: &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 3, + }, + Expect: true, + }, + "other does not set index": { + Address: &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 3, + }, + Other: &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: -1, + }, + Expect: true, + }, + "neither sets index": { + Address: &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: -1, + }, + Other: &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: -1, + }, + Expect: true, + }, + "index over ten": { + Address: &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 1, + }, + Other: &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 13, + }, + Expect: false, + }, + "different type": { + Address: &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 0, + }, + Other: &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_vpc", + Name: "foo", + InstanceType: TypePrimary, + Index: 0, + }, + Expect: false, + }, + "different mode": { + Address: &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 0, + }, + Other: &ResourceAddress{ + Mode: DataResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 0, + }, + Expect: false, + }, + "different name": { + Address: &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 0, + }, + Other: &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "bar", + InstanceType: TypePrimary, + Index: 0, + }, + Expect: false, + }, + "different instance type": { + Address: &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 0, + }, + Other: &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypeTainted, + Index: 0, + }, + Expect: false, + }, + "different index": { + Address: &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 0, + }, + Other: &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 1, + }, + Expect: false, + }, + "module address matches address of managed resource inside module": { + Address: &ResourceAddress{ + Path: []string{"a", "b"}, + Type: "", + Name: "", + InstanceType: TypePrimary, + Index: -1, + }, + Other: &ResourceAddress{ + Path: []string{"a", "b"}, + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 0, + }, + Expect: true, + }, + "module address matches address of data resource inside module": { + Address: &ResourceAddress{ + Path: []string{"a", "b"}, + Type: "", + Name: "", + InstanceType: TypePrimary, + Index: -1, + }, + Other: &ResourceAddress{ + Path: []string{"a", "b"}, + Mode: DataResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 0, + }, + Expect: true, + }, + "module address doesn't match managed resource outside module": { + Address: &ResourceAddress{ + Path: []string{"a", "b"}, + Type: "", + Name: "", + InstanceType: TypePrimary, + Index: -1, + }, + Other: &ResourceAddress{ + Path: []string{"a"}, + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 0, + }, + Expect: false, + }, + "module address doesn't match data resource outside module": { + Address: &ResourceAddress{ + Path: []string{"a", "b"}, + Type: "", + Name: "", + InstanceType: TypePrimary, + Index: -1, + }, + Other: &ResourceAddress{ + Path: []string{"a"}, + Mode: DataResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 0, + }, + Expect: false, + }, + "nil path vs empty path should match": { + Address: &ResourceAddress{ + Path: []string{}, + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: -1, + }, + Other: &ResourceAddress{ + Path: nil, + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 0, + }, + Expect: true, + }, + } + + for tn, tc := range cases { + actual := tc.Address.Equals(tc.Other) + if actual != tc.Expect { + t.Fatalf("%q: expected equals: %t, got %t for:\n%#v\n%#v", + tn, tc.Expect, actual, tc.Address, tc.Other) + } + } +} + +func TestResourceAddressStateId(t *testing.T) { + cases := map[string]struct { + Input *ResourceAddress + Expected string + }{ + "basic resource": { + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: -1, + }, + "aws_instance.foo", + }, + + "basic resource with index": { + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 2, + }, + "aws_instance.foo.2", + }, + + "data resource": { + &ResourceAddress{ + Mode: DataResourceMode, + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: -1, + }, + "data.aws_instance.foo", + }, + } + + for tn, tc := range cases { + t.Run(tn, func(t *testing.T) { + actual := tc.Input.stateId() + if actual != tc.Expected { + t.Fatalf("bad: %q\n\nexpected: %s\n\ngot: %s", tn, tc.Expected, actual) + } + }) + } +} + +func TestResourceAddressHasResourceSpec(t *testing.T) { + cases := []struct { + Input string + Want bool + }{ + { + "module.foo", + false, + }, + { + "module.foo.module.bar", + false, + }, + { + "null_resource.baz", + true, + }, + { + "null_resource.baz[0]", + true, + }, + { + "data.null_data_source.baz", + true, + }, + { + "data.null_data_source.baz[0]", + true, + }, + { + "module.foo.null_resource.baz", + true, + }, + { + "module.foo.data.null_data_source.baz", + true, + }, + { + "module.foo.module.bar.null_resource.baz", + true, + }, + } + + for _, test := range cases { + t.Run(test.Input, func(t *testing.T) { + addr, err := ParseResourceAddress(test.Input) + if err != nil { + t.Fatalf("error parsing address: %s", err) + } + got := addr.HasResourceSpec() + if got != test.Want { + t.Fatalf("%q: wrong result %#v; want %#v", test.Input, got, test.Want) + } + }) + } +} + +func TestResourceAddressWholeModuleAddress(t *testing.T) { + cases := []struct { + Input string + Want string + }{ + { + "module.foo", + "module.foo", + }, + { + "module.foo.module.bar", + "module.foo.module.bar", + }, + { + "null_resource.baz", + "", + }, + { + "null_resource.baz[0]", + "", + }, + { + "data.null_data_source.baz", + "", + }, + { + "data.null_data_source.baz[0]", + "", + }, + { + "module.foo.null_resource.baz", + "module.foo", + }, + { + "module.foo.data.null_data_source.baz", + "module.foo", + }, + { + "module.foo.module.bar.null_resource.baz", + "module.foo.module.bar", + }, + } + + for _, test := range cases { + t.Run(test.Input, func(t *testing.T) { + addr, err := ParseResourceAddress(test.Input) + if err != nil { + t.Fatalf("error parsing address: %s", err) + } + gotAddr := addr.WholeModuleAddress() + got := gotAddr.String() + if got != test.Want { + t.Fatalf("%q: wrong result %#v; want %#v", test.Input, got, test.Want) + } + }) + } +} + +func TestResourceAddressMatchesResourceConfig(t *testing.T) { + root := []string(nil) + child := []string{"child"} + grandchild := []string{"child", "grandchild"} + irrelevant := []string{"irrelevant"} + + tests := []struct { + Addr *ResourceAddress + ModulePath []string + Resource *configs.Resource + Want bool + }{ + { + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "null_resource", + Name: "baz", + Index: -1, + }, + root, + &configs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + }, + true, + }, + { + &ResourceAddress{ + Path: []string{"child"}, + Mode: ManagedResourceMode, + Type: "null_resource", + Name: "baz", + Index: -1, + }, + child, + &configs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + }, + true, + }, + { + &ResourceAddress{ + Path: []string{"child", "grandchild"}, + Mode: ManagedResourceMode, + Type: "null_resource", + Name: "baz", + Index: -1, + }, + grandchild, + &configs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + }, + true, + }, + { + &ResourceAddress{ + Path: []string{"child"}, + Index: -1, + }, + child, + &configs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + }, + true, + }, + { + &ResourceAddress{ + Path: []string{"child", "grandchild"}, + Index: -1, + }, + grandchild, + &configs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + }, + true, + }, + { + &ResourceAddress{ + Mode: DataResourceMode, + Type: "null_resource", + Name: "baz", + Index: -1, + }, + irrelevant, + &configs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + }, + false, + }, + { + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "null_resource", + Name: "baz", + Index: -1, + }, + irrelevant, + &configs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "null_resource", + Name: "pizza", + }, + false, + }, + { + &ResourceAddress{ + Mode: ManagedResourceMode, + Type: "null_resource", + Name: "baz", + Index: -1, + }, + irrelevant, + &configs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "aws_instance", + Name: "baz", + }, + false, + }, + { + &ResourceAddress{ + Path: []string{"child", "grandchild"}, + Mode: ManagedResourceMode, + Type: "null_resource", + Name: "baz", + Index: -1, + }, + child, + &configs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + }, + false, + }, + { + &ResourceAddress{ + Path: []string{"child"}, + Mode: ManagedResourceMode, + Type: "null_resource", + Name: "baz", + Index: -1, + }, + grandchild, + &configs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + }, + false, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("%02d-%s", i, test.Addr), func(t *testing.T) { + got := test.Addr.MatchesResourceConfig(test.ModulePath, test.Resource) + if got != test.Want { + t.Errorf( + "wrong result\naddr: %s\nmod: %#v\nrsrc: %#v\ngot: %#v\nwant: %#v", + test.Addr, test.ModulePath, test.Resource, got, test.Want, + ) + } + }) + } +} + +func TestResourceAddressLess(t *testing.T) { + tests := []struct { + A string + B string + Want bool + }{ + { + "foo.bar", + "module.baz.foo.bar", + true, + }, + { + "module.baz.foo.bar", + "zzz.bar", // would sort after "module" in lexicographical sort + false, + }, + { + "module.baz.foo.bar", + "module.baz.foo.bar", + false, + }, + { + "module.baz.foo.bar", + "module.boz.foo.bar", + true, + }, + { + "module.boz.foo.bar", + "module.baz.foo.bar", + false, + }, + { + "a.b", + "b.c", + true, + }, + { + "a.b", + "a.c", + true, + }, + { + "c.b", + "b.c", + false, + }, + { + "a.b[9]", + "a.b[10]", + true, + }, + { + "b.b[9]", + "a.b[10]", + false, + }, + { + "a.b", + "a.b.deposed", + true, + }, + { + "a.b.tainted", + "a.b.deposed", + true, + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%s < %s", test.A, test.B), func(t *testing.T) { + addrA, err := ParseResourceAddress(test.A) + if err != nil { + t.Fatal(err) + } + addrB, err := ParseResourceAddress(test.B) + if err != nil { + t.Fatal(err) + } + got := addrA.Less(addrB) + invGot := addrB.Less(addrA) + if got != test.Want { + t.Errorf( + "wrong result\ntest: %s < %s\ngot: %#v\nwant: %#v", + test.A, test.B, got, test.Want, + ) + } + if test.A != test.B { // inverse test doesn't apply when equal + if invGot != !test.Want { + t.Errorf( + "wrong inverse result\ntest: %s < %s\ngot: %#v\nwant: %#v", + test.B, test.A, invGot, !test.Want, + ) + } + } else { + if invGot != test.Want { + t.Errorf( + "wrong inverse result\ntest: %s < %s\ngot: %#v\nwant: %#v", + test.B, test.A, invGot, test.Want, + ) + } + } + }) + } +} diff --git a/internal/legacy/terraform/resource_mode.go b/internal/legacy/terraform/resource_mode.go new file mode 100644 index 000000000..c83643a65 --- /dev/null +++ b/internal/legacy/terraform/resource_mode.go @@ -0,0 +1,12 @@ +package terraform + +//go:generate go run golang.org/x/tools/cmd/stringer -type=ResourceMode -output=resource_mode_string.go resource_mode.go + +// ResourceMode is deprecated, use addrs.ResourceMode instead. +// It has been preserved for backwards compatibility. +type ResourceMode int + +const ( + ManagedResourceMode ResourceMode = iota + DataResourceMode +) diff --git a/internal/legacy/terraform/resource_mode_string.go b/internal/legacy/terraform/resource_mode_string.go new file mode 100644 index 000000000..ba84346a2 --- /dev/null +++ b/internal/legacy/terraform/resource_mode_string.go @@ -0,0 +1,24 @@ +// Code generated by "stringer -type=ResourceMode -output=resource_mode_string.go resource_mode.go"; DO NOT EDIT. + +package terraform + +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[ManagedResourceMode-0] + _ = x[DataResourceMode-1] +} + +const _ResourceMode_name = "ManagedResourceModeDataResourceMode" + +var _ResourceMode_index = [...]uint8{0, 19, 35} + +func (i ResourceMode) String() string { + if i < 0 || i >= ResourceMode(len(_ResourceMode_index)-1) { + return "ResourceMode(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _ResourceMode_name[_ResourceMode_index[i]:_ResourceMode_index[i+1]] +} diff --git a/internal/legacy/terraform/resource_provider.go b/internal/legacy/terraform/resource_provider.go new file mode 100644 index 000000000..dccfec68b --- /dev/null +++ b/internal/legacy/terraform/resource_provider.go @@ -0,0 +1,236 @@ +package terraform + +// ResourceProvider is a legacy interface for providers. +// +// This is retained only for compatibility with legacy code. The current +// interface for providers is providers.Interface, in the sibling directory +// named "providers". +type ResourceProvider interface { + /********************************************************************* + * Functions related to the provider + *********************************************************************/ + + // ProviderSchema returns the config schema for the main provider + // configuration, as would appear in a "provider" block in the + // configuration files. + // + // Currently not all providers support schema. Callers must therefore + // first call Resources and DataSources and ensure that at least one + // resource or data source has the SchemaAvailable flag set. + GetSchema(*ProviderSchemaRequest) (*ProviderSchema, error) + + // Input was used prior to v0.12 to ask the provider to prompt the user + // for input to complete the configuration. + // + // From v0.12 onwards this method is never called because Terraform Core + // is able to handle the necessary input logic itself based on the + // schema returned from GetSchema. + Input(UIInput, *ResourceConfig) (*ResourceConfig, error) + + // Validate is called once at the beginning with the raw configuration + // (no interpolation done) and can return a list of warnings and/or + // errors. + // + // This is called once with the provider configuration only. It may not + // be called at all if no provider configuration is given. + // + // This should not assume that any values of the configurations are valid. + // The primary use case of this call is to check that required keys are + // set. + Validate(*ResourceConfig) ([]string, []error) + + // Configure configures the provider itself with the configuration + // given. This is useful for setting things like access keys. + // + // This won't be called at all if no provider configuration is given. + // + // Configure returns an error if it occurred. + Configure(*ResourceConfig) error + + // Resources returns all the available resource types that this provider + // knows how to manage. + Resources() []ResourceType + + // Stop is called when the provider should halt any in-flight actions. + // + // This can be used to make a nicer Ctrl-C experience for Terraform. + // Even if this isn't implemented to do anything (just returns nil), + // Terraform will still cleanly stop after the currently executing + // graph node is complete. However, this API can be used to make more + // efficient halts. + // + // Stop doesn't have to and shouldn't block waiting for in-flight actions + // to complete. It should take any action it wants and return immediately + // acknowledging it has received the stop request. Terraform core will + // automatically not make any further API calls to the provider soon + // after Stop is called (technically exactly once the currently executing + // graph nodes are complete). + // + // The error returned, if non-nil, is assumed to mean that signaling the + // stop somehow failed and that the user should expect potentially waiting + // a longer period of time. + Stop() error + + /********************************************************************* + * Functions related to individual resources + *********************************************************************/ + + // ValidateResource is called once at the beginning with the raw + // configuration (no interpolation done) and can return a list of warnings + // and/or errors. + // + // This is called once per resource. + // + // This should not assume any of the values in the resource configuration + // are valid since it is possible they have to be interpolated still. + // The primary use case of this call is to check that the required keys + // are set and that the general structure is correct. + ValidateResource(string, *ResourceConfig) ([]string, []error) + + // Apply applies a diff to a specific resource and returns the new + // resource state along with an error. + // + // If the resource state given has an empty ID, then a new resource + // is expected to be created. + Apply( + *InstanceInfo, + *InstanceState, + *InstanceDiff) (*InstanceState, error) + + // Diff diffs a resource versus a desired state and returns + // a diff. + Diff( + *InstanceInfo, + *InstanceState, + *ResourceConfig) (*InstanceDiff, error) + + // Refresh refreshes a resource and updates all of its attributes + // with the latest information. + Refresh(*InstanceInfo, *InstanceState) (*InstanceState, error) + + /********************************************************************* + * Functions related to importing + *********************************************************************/ + + // ImportState requests that the given resource be imported. + // + // The returned InstanceState only requires ID be set. Importing + // will always call Refresh after the state to complete it. + // + // IMPORTANT: InstanceState doesn't have the resource type attached + // to it. A type must be specified on the state via the Ephemeral + // field on the state. + // + // This function can return multiple states. Normally, an import + // will map 1:1 to a physical resource. However, some resources map + // to multiple. For example, an AWS security group may contain many rules. + // Each rule is represented by a separate resource in Terraform, + // therefore multiple states are returned. + ImportState(*InstanceInfo, string) ([]*InstanceState, error) + + /********************************************************************* + * Functions related to data resources + *********************************************************************/ + + // ValidateDataSource is called once at the beginning with the raw + // configuration (no interpolation done) and can return a list of warnings + // and/or errors. + // + // This is called once per data source instance. + // + // This should not assume any of the values in the resource configuration + // are valid since it is possible they have to be interpolated still. + // The primary use case of this call is to check that the required keys + // are set and that the general structure is correct. + ValidateDataSource(string, *ResourceConfig) ([]string, []error) + + // DataSources returns all of the available data sources that this + // provider implements. + DataSources() []DataSource + + // ReadDataDiff produces a diff that represents the state that will + // be produced when the given data source is read using a later call + // to ReadDataApply. + ReadDataDiff(*InstanceInfo, *ResourceConfig) (*InstanceDiff, error) + + // ReadDataApply initializes a data instance using the configuration + // in a diff produced by ReadDataDiff. + ReadDataApply(*InstanceInfo, *InstanceDiff) (*InstanceState, error) +} + +// ResourceProviderCloser is an interface that providers that can close +// connections that aren't needed anymore must implement. +type ResourceProviderCloser interface { + Close() error +} + +// ResourceType is a type of resource that a resource provider can manage. +type ResourceType struct { + Name string // Name of the resource, example "instance" (no provider prefix) + Importable bool // Whether this resource supports importing + + // SchemaAvailable is set if the provider supports the ProviderSchema, + // ResourceTypeSchema and DataSourceSchema methods. Although it is + // included on each resource type, it's actually a provider-wide setting + // that's smuggled here only because that avoids a breaking change to + // the plugin protocol. + SchemaAvailable bool +} + +// DataSource is a data source that a resource provider implements. +type DataSource struct { + Name string + + // SchemaAvailable is set if the provider supports the ProviderSchema, + // ResourceTypeSchema and DataSourceSchema methods. Although it is + // included on each resource type, it's actually a provider-wide setting + // that's smuggled here only because that avoids a breaking change to + // the plugin protocol. + SchemaAvailable bool +} + +// ResourceProviderFactory is a function type that creates a new instance +// of a resource provider. +type ResourceProviderFactory func() (ResourceProvider, error) + +// ResourceProviderFactoryFixed is a helper that creates a +// ResourceProviderFactory that just returns some fixed provider. +func ResourceProviderFactoryFixed(p ResourceProvider) ResourceProviderFactory { + return func() (ResourceProvider, error) { + return p, nil + } +} + +func ProviderHasResource(p ResourceProvider, n string) bool { + for _, rt := range p.Resources() { + if rt.Name == n { + return true + } + } + + return false +} + +func ProviderHasDataSource(p ResourceProvider, n string) bool { + for _, rt := range p.DataSources() { + if rt.Name == n { + return true + } + } + + return false +} + +const errPluginInit = ` +Plugin reinitialization required. Please run "terraform init". + +Plugins are external binaries that Terraform uses to access and manipulate +resources. The configuration provided requires plugins which can't be located, +don't satisfy the version constraints, or are otherwise incompatible. + +Terraform automatically discovers provider requirements from your +configuration, including providers used in child modules. To see the +requirements and constraints, run "terraform providers". + +%s +` diff --git a/internal/legacy/terraform/resource_provider_mock.go b/internal/legacy/terraform/resource_provider_mock.go new file mode 100644 index 000000000..4000e3d21 --- /dev/null +++ b/internal/legacy/terraform/resource_provider_mock.go @@ -0,0 +1,315 @@ +package terraform + +import ( + "sync" +) + +// MockResourceProvider implements ResourceProvider but mocks out all the +// calls for testing purposes. +type MockResourceProvider struct { + sync.Mutex + + // Anything you want, in case you need to store extra data with the mock. + Meta interface{} + + CloseCalled bool + CloseError error + GetSchemaCalled bool + GetSchemaRequest *ProviderSchemaRequest + GetSchemaReturn *ProviderSchema + GetSchemaReturnError error + InputCalled bool + InputInput UIInput + InputConfig *ResourceConfig + InputReturnConfig *ResourceConfig + InputReturnError error + InputFn func(UIInput, *ResourceConfig) (*ResourceConfig, error) + ApplyCalled bool + ApplyInfo *InstanceInfo + ApplyState *InstanceState + ApplyDiff *InstanceDiff + ApplyFn func(*InstanceInfo, *InstanceState, *InstanceDiff) (*InstanceState, error) + ApplyReturn *InstanceState + ApplyReturnError error + ConfigureCalled bool + ConfigureConfig *ResourceConfig + ConfigureFn func(*ResourceConfig) error + ConfigureReturnError error + DiffCalled bool + DiffInfo *InstanceInfo + DiffState *InstanceState + DiffDesired *ResourceConfig + DiffFn func(*InstanceInfo, *InstanceState, *ResourceConfig) (*InstanceDiff, error) + DiffReturn *InstanceDiff + DiffReturnError error + RefreshCalled bool + RefreshInfo *InstanceInfo + RefreshState *InstanceState + RefreshFn func(*InstanceInfo, *InstanceState) (*InstanceState, error) + RefreshReturn *InstanceState + RefreshReturnError error + ResourcesCalled bool + ResourcesReturn []ResourceType + ReadDataApplyCalled bool + ReadDataApplyInfo *InstanceInfo + ReadDataApplyDiff *InstanceDiff + ReadDataApplyFn func(*InstanceInfo, *InstanceDiff) (*InstanceState, error) + ReadDataApplyReturn *InstanceState + ReadDataApplyReturnError error + ReadDataDiffCalled bool + ReadDataDiffInfo *InstanceInfo + ReadDataDiffDesired *ResourceConfig + ReadDataDiffFn func(*InstanceInfo, *ResourceConfig) (*InstanceDiff, error) + ReadDataDiffReturn *InstanceDiff + ReadDataDiffReturnError error + StopCalled bool + StopFn func() error + StopReturnError error + DataSourcesCalled bool + DataSourcesReturn []DataSource + ValidateCalled bool + ValidateConfig *ResourceConfig + ValidateFn func(*ResourceConfig) ([]string, []error) + ValidateReturnWarns []string + ValidateReturnErrors []error + ValidateResourceFn func(string, *ResourceConfig) ([]string, []error) + ValidateResourceCalled bool + ValidateResourceType string + ValidateResourceConfig *ResourceConfig + ValidateResourceReturnWarns []string + ValidateResourceReturnErrors []error + ValidateDataSourceFn func(string, *ResourceConfig) ([]string, []error) + ValidateDataSourceCalled bool + ValidateDataSourceType string + ValidateDataSourceConfig *ResourceConfig + ValidateDataSourceReturnWarns []string + ValidateDataSourceReturnErrors []error + + ImportStateCalled bool + ImportStateInfo *InstanceInfo + ImportStateID string + ImportStateReturn []*InstanceState + ImportStateReturnError error + ImportStateFn func(*InstanceInfo, string) ([]*InstanceState, error) +} + +func (p *MockResourceProvider) Close() error { + p.CloseCalled = true + return p.CloseError +} + +func (p *MockResourceProvider) GetSchema(req *ProviderSchemaRequest) (*ProviderSchema, error) { + p.Lock() + defer p.Unlock() + + p.GetSchemaCalled = true + p.GetSchemaRequest = req + return p.GetSchemaReturn, p.GetSchemaReturnError +} + +func (p *MockResourceProvider) Input( + input UIInput, c *ResourceConfig) (*ResourceConfig, error) { + p.Lock() + defer p.Unlock() + p.InputCalled = true + p.InputInput = input + p.InputConfig = c + if p.InputFn != nil { + return p.InputFn(input, c) + } + return p.InputReturnConfig, p.InputReturnError +} + +func (p *MockResourceProvider) Validate(c *ResourceConfig) ([]string, []error) { + p.Lock() + defer p.Unlock() + + p.ValidateCalled = true + p.ValidateConfig = c + if p.ValidateFn != nil { + return p.ValidateFn(c) + } + return p.ValidateReturnWarns, p.ValidateReturnErrors +} + +func (p *MockResourceProvider) ValidateResource(t string, c *ResourceConfig) ([]string, []error) { + p.Lock() + defer p.Unlock() + + p.ValidateResourceCalled = true + p.ValidateResourceType = t + p.ValidateResourceConfig = c + + if p.ValidateResourceFn != nil { + return p.ValidateResourceFn(t, c) + } + + return p.ValidateResourceReturnWarns, p.ValidateResourceReturnErrors +} + +func (p *MockResourceProvider) Configure(c *ResourceConfig) error { + p.Lock() + defer p.Unlock() + + p.ConfigureCalled = true + p.ConfigureConfig = c + + if p.ConfigureFn != nil { + return p.ConfigureFn(c) + } + + return p.ConfigureReturnError +} + +func (p *MockResourceProvider) Stop() error { + p.Lock() + defer p.Unlock() + + p.StopCalled = true + if p.StopFn != nil { + return p.StopFn() + } + + return p.StopReturnError +} + +func (p *MockResourceProvider) Apply( + info *InstanceInfo, + state *InstanceState, + diff *InstanceDiff) (*InstanceState, error) { + // We only lock while writing data. Reading is fine + p.Lock() + p.ApplyCalled = true + p.ApplyInfo = info + p.ApplyState = state + p.ApplyDiff = diff + p.Unlock() + + if p.ApplyFn != nil { + return p.ApplyFn(info, state, diff) + } + + return p.ApplyReturn.DeepCopy(), p.ApplyReturnError +} + +func (p *MockResourceProvider) Diff( + info *InstanceInfo, + state *InstanceState, + desired *ResourceConfig) (*InstanceDiff, error) { + p.Lock() + defer p.Unlock() + + p.DiffCalled = true + p.DiffInfo = info + p.DiffState = state + p.DiffDesired = desired + + if p.DiffFn != nil { + return p.DiffFn(info, state, desired) + } + + return p.DiffReturn.DeepCopy(), p.DiffReturnError +} + +func (p *MockResourceProvider) Refresh( + info *InstanceInfo, + s *InstanceState) (*InstanceState, error) { + p.Lock() + defer p.Unlock() + + p.RefreshCalled = true + p.RefreshInfo = info + p.RefreshState = s + + if p.RefreshFn != nil { + return p.RefreshFn(info, s) + } + + return p.RefreshReturn.DeepCopy(), p.RefreshReturnError +} + +func (p *MockResourceProvider) Resources() []ResourceType { + p.Lock() + defer p.Unlock() + + p.ResourcesCalled = true + return p.ResourcesReturn +} + +func (p *MockResourceProvider) ImportState(info *InstanceInfo, id string) ([]*InstanceState, error) { + p.Lock() + defer p.Unlock() + + p.ImportStateCalled = true + p.ImportStateInfo = info + p.ImportStateID = id + if p.ImportStateFn != nil { + return p.ImportStateFn(info, id) + } + + var result []*InstanceState + if p.ImportStateReturn != nil { + result = make([]*InstanceState, len(p.ImportStateReturn)) + for i, v := range p.ImportStateReturn { + result[i] = v.DeepCopy() + } + } + + return result, p.ImportStateReturnError +} + +func (p *MockResourceProvider) ValidateDataSource(t string, c *ResourceConfig) ([]string, []error) { + p.Lock() + defer p.Unlock() + + p.ValidateDataSourceCalled = true + p.ValidateDataSourceType = t + p.ValidateDataSourceConfig = c + + if p.ValidateDataSourceFn != nil { + return p.ValidateDataSourceFn(t, c) + } + + return p.ValidateDataSourceReturnWarns, p.ValidateDataSourceReturnErrors +} + +func (p *MockResourceProvider) ReadDataDiff( + info *InstanceInfo, + desired *ResourceConfig) (*InstanceDiff, error) { + p.Lock() + defer p.Unlock() + + p.ReadDataDiffCalled = true + p.ReadDataDiffInfo = info + p.ReadDataDiffDesired = desired + if p.ReadDataDiffFn != nil { + return p.ReadDataDiffFn(info, desired) + } + + return p.ReadDataDiffReturn.DeepCopy(), p.ReadDataDiffReturnError +} + +func (p *MockResourceProvider) ReadDataApply( + info *InstanceInfo, + d *InstanceDiff) (*InstanceState, error) { + p.Lock() + defer p.Unlock() + + p.ReadDataApplyCalled = true + p.ReadDataApplyInfo = info + p.ReadDataApplyDiff = d + + if p.ReadDataApplyFn != nil { + return p.ReadDataApplyFn(info, d) + } + + return p.ReadDataApplyReturn.DeepCopy(), p.ReadDataApplyReturnError +} + +func (p *MockResourceProvider) DataSources() []DataSource { + p.Lock() + defer p.Unlock() + + p.DataSourcesCalled = true + return p.DataSourcesReturn +} diff --git a/internal/legacy/terraform/resource_provisioner.go b/internal/legacy/terraform/resource_provisioner.go new file mode 100644 index 000000000..d5f707880 --- /dev/null +++ b/internal/legacy/terraform/resource_provisioner.go @@ -0,0 +1,69 @@ +package terraform + +import ( + "github.com/hashicorp/terraform/configs/configschema" + "github.com/hashicorp/terraform/provisioners" +) + +// ResourceProvisioner is an interface that must be implemented by any +// resource provisioner: the thing that initializes resources in +// a Terraform configuration. +type ResourceProvisioner interface { + // GetConfigSchema returns the schema for the provisioner type's main + // configuration block. This is called prior to Validate to enable some + // basic structural validation to be performed automatically and to allow + // the configuration to be properly extracted from potentially-ambiguous + // configuration file formats. + GetConfigSchema() (*configschema.Block, error) + + // Validate is called once at the beginning with the raw + // configuration (no interpolation done) and can return a list of warnings + // and/or errors. + // + // This is called once per resource. + // + // This should not assume any of the values in the resource configuration + // are valid since it is possible they have to be interpolated still. + // The primary use case of this call is to check that the required keys + // are set and that the general structure is correct. + Validate(*ResourceConfig) ([]string, []error) + + // Apply runs the provisioner on a specific resource and returns an error. + // Instead of a diff, the ResourceConfig is provided since provisioners + // only run after a resource has been newly created. + Apply(UIOutput, *InstanceState, *ResourceConfig) error + + // Stop is called when the provisioner should halt any in-flight actions. + // + // This can be used to make a nicer Ctrl-C experience for Terraform. + // Even if this isn't implemented to do anything (just returns nil), + // Terraform will still cleanly stop after the currently executing + // graph node is complete. However, this API can be used to make more + // efficient halts. + // + // Stop doesn't have to and shouldn't block waiting for in-flight actions + // to complete. It should take any action it wants and return immediately + // acknowledging it has received the stop request. Terraform core will + // automatically not make any further API calls to the provider soon + // after Stop is called (technically exactly once the currently executing + // graph nodes are complete). + // + // The error returned, if non-nil, is assumed to mean that signaling the + // stop somehow failed and that the user should expect potentially waiting + // a longer period of time. + Stop() error +} + +// ResourceProvisionerCloser is an interface that provisioners that can close +// connections that aren't needed anymore must implement. +type ResourceProvisionerCloser interface { + Close() error +} + +// ResourceProvisionerFactory is a function type that creates a new instance +// of a resource provisioner. +type ResourceProvisionerFactory func() (ResourceProvisioner, error) + +// ProvisionerFactory is a function type that creates a new instance +// of a provisioners.Interface. +type ProvisionerFactory = provisioners.Factory diff --git a/internal/legacy/terraform/resource_provisioner_mock.go b/internal/legacy/terraform/resource_provisioner_mock.go new file mode 100644 index 000000000..7b88cf733 --- /dev/null +++ b/internal/legacy/terraform/resource_provisioner_mock.go @@ -0,0 +1,87 @@ +package terraform + +import ( + "sync" + + "github.com/hashicorp/terraform/configs/configschema" +) + +// MockResourceProvisioner implements ResourceProvisioner but mocks out all the +// calls for testing purposes. +type MockResourceProvisioner struct { + sync.Mutex + // Anything you want, in case you need to store extra data with the mock. + Meta interface{} + + GetConfigSchemaCalled bool + GetConfigSchemaReturnSchema *configschema.Block + GetConfigSchemaReturnError error + + ApplyCalled bool + ApplyOutput UIOutput + ApplyState *InstanceState + ApplyConfig *ResourceConfig + ApplyFn func(*InstanceState, *ResourceConfig) error + ApplyReturnError error + + ValidateCalled bool + ValidateConfig *ResourceConfig + ValidateFn func(c *ResourceConfig) ([]string, []error) + ValidateReturnWarns []string + ValidateReturnErrors []error + + StopCalled bool + StopFn func() error + StopReturnError error +} + +var _ ResourceProvisioner = (*MockResourceProvisioner)(nil) + +func (p *MockResourceProvisioner) GetConfigSchema() (*configschema.Block, error) { + p.GetConfigSchemaCalled = true + return p.GetConfigSchemaReturnSchema, p.GetConfigSchemaReturnError +} + +func (p *MockResourceProvisioner) Validate(c *ResourceConfig) ([]string, []error) { + p.Lock() + defer p.Unlock() + + p.ValidateCalled = true + p.ValidateConfig = c + if p.ValidateFn != nil { + return p.ValidateFn(c) + } + return p.ValidateReturnWarns, p.ValidateReturnErrors +} + +func (p *MockResourceProvisioner) Apply( + output UIOutput, + state *InstanceState, + c *ResourceConfig) error { + p.Lock() + + p.ApplyCalled = true + p.ApplyOutput = output + p.ApplyState = state + p.ApplyConfig = c + if p.ApplyFn != nil { + fn := p.ApplyFn + p.Unlock() + return fn(state, c) + } + + defer p.Unlock() + return p.ApplyReturnError +} + +func (p *MockResourceProvisioner) Stop() error { + p.Lock() + defer p.Unlock() + + p.StopCalled = true + if p.StopFn != nil { + return p.StopFn() + } + + return p.StopReturnError +} diff --git a/internal/legacy/terraform/resource_test.go b/internal/legacy/terraform/resource_test.go new file mode 100644 index 000000000..835163c4a --- /dev/null +++ b/internal/legacy/terraform/resource_test.go @@ -0,0 +1,674 @@ +package terraform + +import ( + "fmt" + "reflect" + "testing" + + "github.com/hashicorp/terraform/configs/configschema" + "github.com/zclconf/go-cty/cty" + + "github.com/hashicorp/terraform/configs/hcl2shim" + "github.com/mitchellh/reflectwalk" +) + +func TestResourceConfigGet(t *testing.T) { + fooStringSchema := &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "foo": {Type: cty.String, Optional: true}, + }, + } + fooListSchema := &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "foo": {Type: cty.List(cty.Number), Optional: true}, + }, + } + + cases := []struct { + Config cty.Value + Schema *configschema.Block + Key string + Value interface{} + }{ + { + Config: cty.ObjectVal(map[string]cty.Value{ + "foo": cty.StringVal("bar"), + }), + Schema: fooStringSchema, + Key: "foo", + Value: "bar", + }, + + { + Config: cty.ObjectVal(map[string]cty.Value{ + "foo": cty.UnknownVal(cty.String), + }), + Schema: fooStringSchema, + Key: "foo", + Value: hcl2shim.UnknownVariableValue, + }, + + { + Config: cty.ObjectVal(map[string]cty.Value{ + "foo": cty.ListVal([]cty.Value{ + cty.NumberIntVal(1), + cty.NumberIntVal(2), + cty.NumberIntVal(5), + }), + }), + Schema: fooListSchema, + Key: "foo.0", + Value: 1, + }, + + { + Config: cty.ObjectVal(map[string]cty.Value{ + "foo": cty.ListVal([]cty.Value{ + cty.NumberIntVal(1), + cty.NumberIntVal(2), + cty.NumberIntVal(5), + }), + }), + Schema: fooListSchema, + Key: "foo.5", + Value: nil, + }, + + { + Config: cty.ObjectVal(map[string]cty.Value{ + "foo": cty.ListVal([]cty.Value{ + cty.NumberIntVal(1), + cty.NumberIntVal(2), + cty.NumberIntVal(5), + }), + }), + Schema: fooListSchema, + Key: "foo.-1", + Value: nil, + }, + + // get from map + { + Config: cty.ObjectVal(map[string]cty.Value{ + "mapname": cty.ListVal([]cty.Value{ + cty.MapVal(map[string]cty.Value{ + "key": cty.NumberIntVal(1), + }), + }), + }), + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true}, + }, + }, + Key: "mapname.0.key", + Value: 1, + }, + + // get from map with dot in key + { + Config: cty.ObjectVal(map[string]cty.Value{ + "mapname": cty.ListVal([]cty.Value{ + cty.MapVal(map[string]cty.Value{ + "key.name": cty.NumberIntVal(1), + }), + }), + }), + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true}, + }, + }, + Key: "mapname.0.key.name", + Value: 1, + }, + + // get from map with overlapping key names + { + Config: cty.ObjectVal(map[string]cty.Value{ + "mapname": cty.ListVal([]cty.Value{ + cty.MapVal(map[string]cty.Value{ + "key.name": cty.NumberIntVal(1), + "key.name.2": cty.NumberIntVal(2), + }), + }), + }), + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true}, + }, + }, + Key: "mapname.0.key.name.2", + Value: 2, + }, + { + Config: cty.ObjectVal(map[string]cty.Value{ + "mapname": cty.ListVal([]cty.Value{ + cty.MapVal(map[string]cty.Value{ + "key.name": cty.NumberIntVal(1), + "key.name.foo": cty.NumberIntVal(2), + }), + }), + }), + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "mapname": {Type: cty.List(cty.Map(cty.Number)), Optional: true}, + }, + }, + Key: "mapname.0.key.name", + Value: 1, + }, + { + Config: cty.ObjectVal(map[string]cty.Value{ + "mapname": cty.ListVal([]cty.Value{ + cty.MapVal(map[string]cty.Value{ + "listkey": cty.ListVal([]cty.Value{ + cty.MapVal(map[string]cty.Value{ + "key": cty.NumberIntVal(3), + }), + }), + }), + }), + }), + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "mapname": {Type: cty.List(cty.Map(cty.List(cty.Map(cty.Number)))), Optional: true}, + }, + }, + Key: "mapname.0.listkey.0.key", + Value: 3, + }, + } + + for i, tc := range cases { + rc := NewResourceConfigShimmed(tc.Config, tc.Schema) + + // Test getting a key + t.Run(fmt.Sprintf("get-%d", i), func(t *testing.T) { + v, ok := rc.Get(tc.Key) + if ok && v == nil { + t.Fatal("(nil, true) returned from Get") + } + + if !reflect.DeepEqual(v, tc.Value) { + t.Fatalf("%d bad: %#v", i, v) + } + }) + + // Test copying and equality + t.Run(fmt.Sprintf("copy-and-equal-%d", i), func(t *testing.T) { + copy := rc.DeepCopy() + if !reflect.DeepEqual(copy, rc) { + t.Fatalf("bad:\n\n%#v\n\n%#v", copy, rc) + } + + if !copy.Equal(rc) { + t.Fatalf("copy != rc:\n\n%#v\n\n%#v", copy, rc) + } + if !rc.Equal(copy) { + t.Fatalf("rc != copy:\n\n%#v\n\n%#v", copy, rc) + } + }) + } +} + +func TestResourceConfigDeepCopy_nil(t *testing.T) { + var nilRc *ResourceConfig + actual := nilRc.DeepCopy() + if actual != nil { + t.Fatalf("bad: %#v", actual) + } +} + +func TestResourceConfigDeepCopy_nilComputed(t *testing.T) { + rc := &ResourceConfig{} + actual := rc.DeepCopy() + if actual.ComputedKeys != nil { + t.Fatalf("bad: %#v", actual) + } +} + +func TestResourceConfigEqual_nil(t *testing.T) { + var nilRc *ResourceConfig + notNil := NewResourceConfigShimmed(cty.EmptyObjectVal, &configschema.Block{}) + + if nilRc.Equal(notNil) { + t.Fatal("should not be equal") + } + + if notNil.Equal(nilRc) { + t.Fatal("should not be equal") + } +} + +func TestResourceConfigEqual_computedKeyOrder(t *testing.T) { + v := cty.ObjectVal(map[string]cty.Value{ + "foo": cty.UnknownVal(cty.String), + }) + schema := &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "foo": {Type: cty.String, Optional: true}, + }, + } + rc := NewResourceConfigShimmed(v, schema) + rc2 := NewResourceConfigShimmed(v, schema) + + // Set the computed keys manually to force ordering to differ + rc.ComputedKeys = []string{"foo", "bar"} + rc2.ComputedKeys = []string{"bar", "foo"} + + if !rc.Equal(rc2) { + t.Fatal("should be equal") + } +} + +func TestUnknownCheckWalker(t *testing.T) { + cases := []struct { + Name string + Input interface{} + Result bool + }{ + { + "primitive", + 42, + false, + }, + + { + "primitive computed", + hcl2shim.UnknownVariableValue, + true, + }, + + { + "list", + []interface{}{"foo", hcl2shim.UnknownVariableValue}, + true, + }, + + { + "nested list", + []interface{}{ + "foo", + []interface{}{hcl2shim.UnknownVariableValue}, + }, + true, + }, + } + + for i, tc := range cases { + t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { + var w unknownCheckWalker + if err := reflectwalk.Walk(tc.Input, &w); err != nil { + t.Fatalf("err: %s", err) + } + + if w.Unknown != tc.Result { + t.Fatalf("bad: %v", w.Unknown) + } + }) + } +} + +func TestNewResourceConfigShimmed(t *testing.T) { + for _, tc := range []struct { + Name string + Val cty.Value + Schema *configschema.Block + Expected *ResourceConfig + }{ + { + Name: "empty object", + Val: cty.NullVal(cty.EmptyObject), + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "foo": { + Type: cty.String, + Optional: true, + }, + }, + }, + Expected: &ResourceConfig{ + Raw: map[string]interface{}{}, + Config: map[string]interface{}{}, + }, + }, + { + Name: "basic", + Val: cty.ObjectVal(map[string]cty.Value{ + "foo": cty.StringVal("bar"), + }), + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "foo": { + Type: cty.String, + Optional: true, + }, + }, + }, + Expected: &ResourceConfig{ + Raw: map[string]interface{}{ + "foo": "bar", + }, + Config: map[string]interface{}{ + "foo": "bar", + }, + }, + }, + { + Name: "null string", + Val: cty.ObjectVal(map[string]cty.Value{ + "foo": cty.NullVal(cty.String), + }), + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "foo": { + Type: cty.String, + Optional: true, + }, + }, + }, + Expected: &ResourceConfig{ + Raw: map[string]interface{}{}, + Config: map[string]interface{}{}, + }, + }, + { + Name: "unknown string", + Val: cty.ObjectVal(map[string]cty.Value{ + "foo": cty.UnknownVal(cty.String), + }), + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "foo": { + Type: cty.String, + Optional: true, + }, + }, + }, + Expected: &ResourceConfig{ + ComputedKeys: []string{"foo"}, + Raw: map[string]interface{}{ + "foo": hcl2shim.UnknownVariableValue, + }, + Config: map[string]interface{}{ + "foo": hcl2shim.UnknownVariableValue, + }, + }, + }, + { + Name: "unknown collections", + Val: cty.ObjectVal(map[string]cty.Value{ + "bar": cty.UnknownVal(cty.Map(cty.String)), + "baz": cty.UnknownVal(cty.List(cty.String)), + }), + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "bar": { + Type: cty.Map(cty.String), + Required: true, + }, + "baz": { + Type: cty.List(cty.String), + Optional: true, + }, + }, + }, + Expected: &ResourceConfig{ + ComputedKeys: []string{"bar", "baz"}, + Raw: map[string]interface{}{ + "bar": hcl2shim.UnknownVariableValue, + "baz": hcl2shim.UnknownVariableValue, + }, + Config: map[string]interface{}{ + "bar": hcl2shim.UnknownVariableValue, + "baz": hcl2shim.UnknownVariableValue, + }, + }, + }, + { + Name: "null collections", + Val: cty.ObjectVal(map[string]cty.Value{ + "bar": cty.NullVal(cty.Map(cty.String)), + "baz": cty.NullVal(cty.List(cty.String)), + }), + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "bar": { + Type: cty.Map(cty.String), + Required: true, + }, + "baz": { + Type: cty.List(cty.String), + Optional: true, + }, + }, + }, + Expected: &ResourceConfig{ + Raw: map[string]interface{}{}, + Config: map[string]interface{}{}, + }, + }, + { + Name: "unknown blocks", + Val: cty.ObjectVal(map[string]cty.Value{ + "bar": cty.UnknownVal(cty.Map(cty.String)), + "baz": cty.UnknownVal(cty.List(cty.String)), + }), + Schema: &configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "bar": { + Block: configschema.Block{}, + Nesting: configschema.NestingList, + }, + "baz": { + Block: configschema.Block{}, + Nesting: configschema.NestingSet, + }, + }, + }, + Expected: &ResourceConfig{ + ComputedKeys: []string{"bar", "baz"}, + Raw: map[string]interface{}{ + "bar": hcl2shim.UnknownVariableValue, + "baz": hcl2shim.UnknownVariableValue, + }, + Config: map[string]interface{}{ + "bar": hcl2shim.UnknownVariableValue, + "baz": hcl2shim.UnknownVariableValue, + }, + }, + }, + { + Name: "unknown in nested blocks", + Val: cty.ObjectVal(map[string]cty.Value{ + "bar": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "baz": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "list": cty.UnknownVal(cty.List(cty.String)), + }), + }), + }), + }), + }), + Schema: &configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "bar": { + Block: configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "baz": { + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "list": {Type: cty.List(cty.String), + Optional: true, + }, + }, + }, + Nesting: configschema.NestingList, + }, + }, + }, + Nesting: configschema.NestingList, + }, + }, + }, + Expected: &ResourceConfig{ + ComputedKeys: []string{"bar.0.baz.0.list"}, + Raw: map[string]interface{}{ + "bar": []interface{}{map[string]interface{}{ + "baz": []interface{}{map[string]interface{}{ + "list": "74D93920-ED26-11E3-AC10-0800200C9A66", + }}, + }}, + }, + Config: map[string]interface{}{ + "bar": []interface{}{map[string]interface{}{ + "baz": []interface{}{map[string]interface{}{ + "list": "74D93920-ED26-11E3-AC10-0800200C9A66", + }}, + }}, + }, + }, + }, + { + Name: "unknown in set", + Val: cty.ObjectVal(map[string]cty.Value{ + "bar": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "val": cty.UnknownVal(cty.String), + }), + }), + }), + Schema: &configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "bar": { + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "val": { + Type: cty.String, + Optional: true, + }, + }, + }, + Nesting: configschema.NestingSet, + }, + }, + }, + Expected: &ResourceConfig{ + ComputedKeys: []string{"bar.0.val"}, + Raw: map[string]interface{}{ + "bar": []interface{}{map[string]interface{}{ + "val": "74D93920-ED26-11E3-AC10-0800200C9A66", + }}, + }, + Config: map[string]interface{}{ + "bar": []interface{}{map[string]interface{}{ + "val": "74D93920-ED26-11E3-AC10-0800200C9A66", + }}, + }, + }, + }, + { + Name: "unknown in attribute sets", + Val: cty.ObjectVal(map[string]cty.Value{ + "bar": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "val": cty.UnknownVal(cty.String), + }), + }), + "baz": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "obj": cty.UnknownVal(cty.Object(map[string]cty.Type{ + "attr": cty.List(cty.String), + })), + }), + cty.ObjectVal(map[string]cty.Value{ + "obj": cty.ObjectVal(map[string]cty.Value{ + "attr": cty.UnknownVal(cty.List(cty.String)), + }), + }), + }), + }), + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "bar": &configschema.Attribute{ + Type: cty.Set(cty.Object(map[string]cty.Type{ + "val": cty.String, + })), + }, + "baz": &configschema.Attribute{ + Type: cty.Set(cty.Object(map[string]cty.Type{ + "obj": cty.Object(map[string]cty.Type{ + "attr": cty.List(cty.String), + }), + })), + }, + }, + }, + Expected: &ResourceConfig{ + ComputedKeys: []string{"bar.0.val", "baz.0.obj.attr", "baz.1.obj"}, + Raw: map[string]interface{}{ + "bar": []interface{}{map[string]interface{}{ + "val": "74D93920-ED26-11E3-AC10-0800200C9A66", + }}, + "baz": []interface{}{ + map[string]interface{}{ + "obj": map[string]interface{}{ + "attr": "74D93920-ED26-11E3-AC10-0800200C9A66", + }, + }, + map[string]interface{}{ + "obj": "74D93920-ED26-11E3-AC10-0800200C9A66", + }, + }, + }, + Config: map[string]interface{}{ + "bar": []interface{}{map[string]interface{}{ + "val": "74D93920-ED26-11E3-AC10-0800200C9A66", + }}, + "baz": []interface{}{ + map[string]interface{}{ + "obj": map[string]interface{}{ + "attr": "74D93920-ED26-11E3-AC10-0800200C9A66", + }, + }, + map[string]interface{}{ + "obj": "74D93920-ED26-11E3-AC10-0800200C9A66", + }, + }, + }, + }, + }, + { + Name: "null blocks", + Val: cty.ObjectVal(map[string]cty.Value{ + "bar": cty.NullVal(cty.Map(cty.String)), + "baz": cty.NullVal(cty.List(cty.String)), + }), + Schema: &configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "bar": { + Block: configschema.Block{}, + Nesting: configschema.NestingMap, + }, + "baz": { + Block: configschema.Block{}, + Nesting: configschema.NestingSingle, + }, + }, + }, + Expected: &ResourceConfig{ + Raw: map[string]interface{}{}, + Config: map[string]interface{}{}, + }, + }, + } { + t.Run(tc.Name, func(*testing.T) { + cfg := NewResourceConfigShimmed(tc.Val, tc.Schema) + if !tc.Expected.Equal(cfg) { + t.Fatalf("expected:\n%#v\ngot:\n%#v", tc.Expected, cfg) + } + }) + } +} diff --git a/internal/legacy/terraform/schemas.go b/internal/legacy/terraform/schemas.go new file mode 100644 index 000000000..15f6d5e7b --- /dev/null +++ b/internal/legacy/terraform/schemas.go @@ -0,0 +1,285 @@ +package terraform + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform/addrs" + "github.com/hashicorp/terraform/configs" + "github.com/hashicorp/terraform/configs/configschema" + "github.com/hashicorp/terraform/providers" + "github.com/hashicorp/terraform/states" + "github.com/hashicorp/terraform/tfdiags" +) + +// Schemas is a container for various kinds of schema that Terraform needs +// during processing. +type Schemas struct { + Providers map[addrs.Provider]*ProviderSchema + Provisioners map[string]*configschema.Block +} + +// ProviderSchema returns the entire ProviderSchema object that was produced +// by the plugin for the given provider, or nil if no such schema is available. +// +// It's usually better to go use the more precise methods offered by type +// Schemas to handle this detail automatically. +func (ss *Schemas) ProviderSchema(provider addrs.Provider) *ProviderSchema { + if ss.Providers == nil { + return nil + } + return ss.Providers[provider] +} + +// ProviderConfig returns the schema for the provider configuration of the +// given provider type, or nil if no such schema is available. +func (ss *Schemas) ProviderConfig(provider addrs.Provider) *configschema.Block { + ps := ss.ProviderSchema(provider) + if ps == nil { + return nil + } + return ps.Provider +} + +// ResourceTypeConfig returns the schema for the configuration of a given +// resource type belonging to a given provider type, or nil of no such +// schema is available. +// +// In many cases the provider type is inferrable from the resource type name, +// but this is not always true because users can override the provider for +// a resource using the "provider" meta-argument. Therefore it's important to +// always pass the correct provider name, even though it many cases it feels +// redundant. +func (ss *Schemas) ResourceTypeConfig(provider addrs.Provider, resourceMode addrs.ResourceMode, resourceType string) (block *configschema.Block, schemaVersion uint64) { + ps := ss.ProviderSchema(provider) + if ps == nil || ps.ResourceTypes == nil { + return nil, 0 + } + return ps.SchemaForResourceType(resourceMode, resourceType) +} + +// ProvisionerConfig returns the schema for the configuration of a given +// provisioner, or nil of no such schema is available. +func (ss *Schemas) ProvisionerConfig(name string) *configschema.Block { + return ss.Provisioners[name] +} + +// LoadSchemas searches the given configuration, state and plan (any of which +// may be nil) for constructs that have an associated schema, requests the +// necessary schemas from the given component factory (which must _not_ be nil), +// and returns a single object representing all of the necessary schemas. +// +// If an error is returned, it may be a wrapped tfdiags.Diagnostics describing +// errors across multiple separate objects. Errors here will usually indicate +// either misbehavior on the part of one of the providers or of the provider +// protocol itself. When returned with errors, the returned schemas object is +// still valid but may be incomplete. +func LoadSchemas(config *configs.Config, state *states.State, components contextComponentFactory) (*Schemas, error) { + schemas := &Schemas{ + Providers: map[addrs.Provider]*ProviderSchema{}, + Provisioners: map[string]*configschema.Block{}, + } + var diags tfdiags.Diagnostics + + newDiags := loadProviderSchemas(schemas.Providers, config, state, components) + diags = diags.Append(newDiags) + newDiags = loadProvisionerSchemas(schemas.Provisioners, config, components) + diags = diags.Append(newDiags) + + return schemas, diags.Err() +} + +func loadProviderSchemas(schemas map[addrs.Provider]*ProviderSchema, config *configs.Config, state *states.State, components contextComponentFactory) tfdiags.Diagnostics { + var diags tfdiags.Diagnostics + + ensure := func(fqn addrs.Provider) { + name := fqn.String() + + if _, exists := schemas[fqn]; exists { + return + } + + log.Printf("[TRACE] LoadSchemas: retrieving schema for provider type %q", name) + provider, err := components.ResourceProvider(fqn) + if err != nil { + // We'll put a stub in the map so we won't re-attempt this on + // future calls. + schemas[fqn] = &ProviderSchema{} + diags = diags.Append( + fmt.Errorf("Failed to instantiate provider %q to obtain schema: %s", name, err), + ) + return + } + defer func() { + provider.Close() + }() + + resp := provider.GetSchema() + if resp.Diagnostics.HasErrors() { + // We'll put a stub in the map so we won't re-attempt this on + // future calls. + schemas[fqn] = &ProviderSchema{} + diags = diags.Append( + fmt.Errorf("Failed to retrieve schema from provider %q: %s", name, resp.Diagnostics.Err()), + ) + return + } + + s := &ProviderSchema{ + Provider: resp.Provider.Block, + ResourceTypes: make(map[string]*configschema.Block), + DataSources: make(map[string]*configschema.Block), + + ResourceTypeSchemaVersions: make(map[string]uint64), + } + + if resp.Provider.Version < 0 { + // We're not using the version numbers here yet, but we'll check + // for validity anyway in case we start using them in future. + diags = diags.Append( + fmt.Errorf("invalid negative schema version provider configuration for provider %q", name), + ) + } + + for t, r := range resp.ResourceTypes { + s.ResourceTypes[t] = r.Block + s.ResourceTypeSchemaVersions[t] = uint64(r.Version) + if r.Version < 0 { + diags = diags.Append( + fmt.Errorf("invalid negative schema version for resource type %s in provider %q", t, name), + ) + } + } + + for t, d := range resp.DataSources { + s.DataSources[t] = d.Block + if d.Version < 0 { + // We're not using the version numbers here yet, but we'll check + // for validity anyway in case we start using them in future. + diags = diags.Append( + fmt.Errorf("invalid negative schema version for data source %s in provider %q", t, name), + ) + } + } + + schemas[fqn] = s + + if resp.ProviderMeta.Block != nil { + s.ProviderMeta = resp.ProviderMeta.Block + } + } + + if config != nil { + for _, fqn := range config.ProviderTypes() { + ensure(fqn) + } + } + + if state != nil { + needed := providers.AddressedTypesAbs(state.ProviderAddrs()) + for _, typeAddr := range needed { + ensure(typeAddr) + } + } + + return diags +} + +func loadProvisionerSchemas(schemas map[string]*configschema.Block, config *configs.Config, components contextComponentFactory) tfdiags.Diagnostics { + var diags tfdiags.Diagnostics + + ensure := func(name string) { + if _, exists := schemas[name]; exists { + return + } + + log.Printf("[TRACE] LoadSchemas: retrieving schema for provisioner %q", name) + provisioner, err := components.ResourceProvisioner(name) + if err != nil { + // We'll put a stub in the map so we won't re-attempt this on + // future calls. + schemas[name] = &configschema.Block{} + diags = diags.Append( + fmt.Errorf("Failed to instantiate provisioner %q to obtain schema: %s", name, err), + ) + return + } + defer func() { + if closer, ok := provisioner.(ResourceProvisionerCloser); ok { + closer.Close() + } + }() + + resp := provisioner.GetSchema() + if resp.Diagnostics.HasErrors() { + // We'll put a stub in the map so we won't re-attempt this on + // future calls. + schemas[name] = &configschema.Block{} + diags = diags.Append( + fmt.Errorf("Failed to retrieve schema from provisioner %q: %s", name, resp.Diagnostics.Err()), + ) + return + } + + schemas[name] = resp.Provisioner + } + + if config != nil { + for _, rc := range config.Module.ManagedResources { + for _, pc := range rc.Managed.Provisioners { + ensure(pc.Type) + } + } + + // Must also visit our child modules, recursively. + for _, cc := range config.Children { + childDiags := loadProvisionerSchemas(schemas, cc, components) + diags = diags.Append(childDiags) + } + } + + return diags +} + +// ProviderSchema represents the schema for a provider's own configuration +// and the configuration for some or all of its resources and data sources. +// +// The completeness of this structure depends on how it was constructed. +// When constructed for a configuration, it will generally include only +// resource types and data sources used by that configuration. +type ProviderSchema struct { + Provider *configschema.Block + ProviderMeta *configschema.Block + ResourceTypes map[string]*configschema.Block + DataSources map[string]*configschema.Block + + ResourceTypeSchemaVersions map[string]uint64 +} + +// SchemaForResourceType attempts to find a schema for the given mode and type. +// Returns nil if no such schema is available. +func (ps *ProviderSchema) SchemaForResourceType(mode addrs.ResourceMode, typeName string) (schema *configschema.Block, version uint64) { + switch mode { + case addrs.ManagedResourceMode: + return ps.ResourceTypes[typeName], ps.ResourceTypeSchemaVersions[typeName] + case addrs.DataResourceMode: + // Data resources don't have schema versions right now, since state is discarded for each refresh + return ps.DataSources[typeName], 0 + default: + // Shouldn't happen, because the above cases are comprehensive. + return nil, 0 + } +} + +// SchemaForResourceAddr attempts to find a schema for the mode and type from +// the given resource address. Returns nil if no such schema is available. +func (ps *ProviderSchema) SchemaForResourceAddr(addr addrs.Resource) (schema *configschema.Block, version uint64) { + return ps.SchemaForResourceType(addr.Mode, addr.Type) +} + +// ProviderSchemaRequest is used to describe to a ResourceProvider which +// aspects of schema are required, when calling the GetSchema method. +type ProviderSchemaRequest struct { + ResourceTypes []string + DataSources []string +} diff --git a/internal/legacy/terraform/state.go b/internal/legacy/terraform/state.go new file mode 100644 index 000000000..95c1e8513 --- /dev/null +++ b/internal/legacy/terraform/state.go @@ -0,0 +1,2255 @@ +package terraform + +import ( + "bufio" + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "reflect" + "sort" + "strconv" + "strings" + "sync" + + "github.com/hashicorp/errwrap" + multierror "github.com/hashicorp/go-multierror" + uuid "github.com/hashicorp/go-uuid" + version "github.com/hashicorp/go-version" + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/hashicorp/terraform/addrs" + "github.com/hashicorp/terraform/configs" + "github.com/hashicorp/terraform/configs/configschema" + "github.com/hashicorp/terraform/configs/hcl2shim" + "github.com/hashicorp/terraform/plans" + "github.com/hashicorp/terraform/tfdiags" + tfversion "github.com/hashicorp/terraform/version" + "github.com/mitchellh/copystructure" + "github.com/zclconf/go-cty/cty" + ctyjson "github.com/zclconf/go-cty/cty/json" +) + +const ( + // StateVersion is the current version for our state file + StateVersion = 3 +) + +// rootModulePath is the path of the root module +var rootModulePath = []string{"root"} + +// normalizeModulePath transforms a legacy module path (which may or may not +// have a redundant "root" label at the start of it) into an +// addrs.ModuleInstance representing the same module. +// +// For legacy reasons, different parts of Terraform disagree about whether the +// root module has the path []string{} or []string{"root"}, and so this +// function accepts both and trims off the "root". An implication of this is +// that it's not possible to actually have a module call in the root module +// that is itself named "root", since that would be ambiguous. +// +// normalizeModulePath takes a raw module path and returns a path that +// has the rootModulePath prepended to it. If I could go back in time I +// would've never had a rootModulePath (empty path would be root). We can +// still fix this but thats a big refactor that my branch doesn't make sense +// for. Instead, this function normalizes paths. +func normalizeModulePath(p []string) addrs.ModuleInstance { + // FIXME: Remove this once everyone is using addrs.ModuleInstance. + + if len(p) > 0 && p[0] == "root" { + p = p[1:] + } + + ret := make(addrs.ModuleInstance, len(p)) + for i, name := range p { + // For now we don't actually support modules with multiple instances + // identified by keys, so we just treat every path element as a + // step with no key. + ret[i] = addrs.ModuleInstanceStep{ + Name: name, + } + } + return ret +} + +// State keeps track of a snapshot state-of-the-world that Terraform +// can use to keep track of what real world resources it is actually +// managing. +type State struct { + // Version is the state file protocol version. + Version int `json:"version"` + + // TFVersion is the version of Terraform that wrote this state. + TFVersion string `json:"terraform_version,omitempty"` + + // Serial is incremented on any operation that modifies + // the State file. It is used to detect potentially conflicting + // updates. + Serial int64 `json:"serial"` + + // Lineage is set when a new, blank state is created and then + // never updated. This allows us to determine whether the serials + // of two states can be meaningfully compared. + // Apart from the guarantee that collisions between two lineages + // are very unlikely, this value is opaque and external callers + // should only compare lineage strings byte-for-byte for equality. + Lineage string `json:"lineage"` + + // Remote is used to track the metadata required to + // pull and push state files from a remote storage endpoint. + Remote *RemoteState `json:"remote,omitempty"` + + // Backend tracks the configuration for the backend in use with + // this state. This is used to track any changes in the backend + // configuration. + Backend *BackendState `json:"backend,omitempty"` + + // Modules contains all the modules in a breadth-first order + Modules []*ModuleState `json:"modules"` + + mu sync.Mutex +} + +func (s *State) Lock() { s.mu.Lock() } +func (s *State) Unlock() { s.mu.Unlock() } + +// NewState is used to initialize a blank state +func NewState() *State { + s := &State{} + s.init() + return s +} + +// Children returns the ModuleStates that are direct children of +// the given path. If the path is "root", for example, then children +// returned might be "root.child", but not "root.child.grandchild". +func (s *State) Children(path []string) []*ModuleState { + s.Lock() + defer s.Unlock() + // TODO: test + + return s.children(path) +} + +func (s *State) children(path []string) []*ModuleState { + result := make([]*ModuleState, 0) + for _, m := range s.Modules { + if m == nil { + continue + } + + if len(m.Path) != len(path)+1 { + continue + } + if !reflect.DeepEqual(path, m.Path[:len(path)]) { + continue + } + + result = append(result, m) + } + + return result +} + +// AddModule adds the module with the given path to the state. +// +// This should be the preferred method to add module states since it +// allows us to optimize lookups later as well as control sorting. +func (s *State) AddModule(path addrs.ModuleInstance) *ModuleState { + s.Lock() + defer s.Unlock() + + return s.addModule(path) +} + +func (s *State) addModule(path addrs.ModuleInstance) *ModuleState { + // check if the module exists first + m := s.moduleByPath(path) + if m != nil { + return m + } + + // Lower the new-style address into a legacy-style address. + // This requires that none of the steps have instance keys, which is + // true for all addresses at the time of implementing this because + // "count" and "for_each" are not yet implemented for modules. + // For the purposes of state, the legacy address format also includes + // a redundant extra prefix element "root". It is important to include + // this because the "prune" method will remove any module that has a + // path length less than one, and other parts of the state code will + // trim off the first element indiscriminately. + legacyPath := make([]string, len(path)+1) + legacyPath[0] = "root" + for i, step := range path { + if step.InstanceKey != addrs.NoKey { + // FIXME: Once the rest of Terraform is ready to use count and + // for_each, remove all of this and just write the addrs.ModuleInstance + // value itself into the ModuleState. + panic("state cannot represent modules with count or for_each keys") + } + + legacyPath[i+1] = step.Name + } + + m = &ModuleState{Path: legacyPath} + m.init() + s.Modules = append(s.Modules, m) + s.sort() + return m +} + +// ModuleByPath is used to lookup the module state for the given path. +// This should be the preferred lookup mechanism as it allows for future +// lookup optimizations. +func (s *State) ModuleByPath(path addrs.ModuleInstance) *ModuleState { + if s == nil { + return nil + } + s.Lock() + defer s.Unlock() + + return s.moduleByPath(path) +} + +func (s *State) moduleByPath(path addrs.ModuleInstance) *ModuleState { + for _, mod := range s.Modules { + if mod == nil { + continue + } + if mod.Path == nil { + panic("missing module path") + } + modPath := normalizeModulePath(mod.Path) + if modPath.String() == path.String() { + return mod + } + } + return nil +} + +// Empty returns true if the state is empty. +func (s *State) Empty() bool { + if s == nil { + return true + } + s.Lock() + defer s.Unlock() + + return len(s.Modules) == 0 +} + +// HasResources returns true if the state contains any resources. +// +// This is similar to !s.Empty, but returns true also in the case where the +// state has modules but all of them are devoid of resources. +func (s *State) HasResources() bool { + if s.Empty() { + return false + } + + for _, mod := range s.Modules { + if len(mod.Resources) > 0 { + return true + } + } + + return false +} + +// IsRemote returns true if State represents a state that exists and is +// remote. +func (s *State) IsRemote() bool { + if s == nil { + return false + } + s.Lock() + defer s.Unlock() + + if s.Remote == nil { + return false + } + if s.Remote.Type == "" { + return false + } + + return true +} + +// Validate validates the integrity of this state file. +// +// Certain properties of the statefile are expected by Terraform in order +// to behave properly. The core of Terraform will assume that once it +// receives a State structure that it has been validated. This validation +// check should be called to ensure that. +// +// If this returns an error, then the user should be notified. The error +// response will include detailed information on the nature of the error. +func (s *State) Validate() error { + s.Lock() + defer s.Unlock() + + var result error + + // !!!! FOR DEVELOPERS !!!! + // + // Any errors returned from this Validate function will BLOCK TERRAFORM + // from loading a state file. Therefore, this should only contain checks + // that are only resolvable through manual intervention. + // + // !!!! FOR DEVELOPERS !!!! + + // Make sure there are no duplicate module states. We open a new + // block here so we can use basic variable names and future validations + // can do the same. + { + found := make(map[string]struct{}) + for _, ms := range s.Modules { + if ms == nil { + continue + } + + key := strings.Join(ms.Path, ".") + if _, ok := found[key]; ok { + result = multierror.Append(result, fmt.Errorf( + strings.TrimSpace(stateValidateErrMultiModule), key)) + continue + } + + found[key] = struct{}{} + } + } + + return result +} + +// Remove removes the item in the state at the given address, returning +// any errors that may have occurred. +// +// If the address references a module state or resource, it will delete +// all children as well. To check what will be deleted, use a StateFilter +// first. +func (s *State) Remove(addr ...string) error { + s.Lock() + defer s.Unlock() + + // Filter out what we need to delete + filter := &StateFilter{State: s} + results, err := filter.Filter(addr...) + if err != nil { + return err + } + + // If we have no results, just exit early, we're not going to do anything. + // While what happens below is fairly fast, this is an important early + // exit since the prune below might modify the state more and we don't + // want to modify the state if we don't have to. + if len(results) == 0 { + return nil + } + + // Go through each result and grab what we need + removed := make(map[interface{}]struct{}) + for _, r := range results { + // Convert the path to our own type + path := append([]string{"root"}, r.Path...) + + // If we removed this already, then ignore + if _, ok := removed[r.Value]; ok { + continue + } + + // If we removed the parent already, then ignore + if r.Parent != nil { + if _, ok := removed[r.Parent.Value]; ok { + continue + } + } + + // Add this to the removed list + removed[r.Value] = struct{}{} + + switch v := r.Value.(type) { + case *ModuleState: + s.removeModule(path, v) + case *ResourceState: + s.removeResource(path, v) + case *InstanceState: + s.removeInstance(path, r.Parent.Value.(*ResourceState), v) + default: + return fmt.Errorf("unknown type to delete: %T", r.Value) + } + } + + // Prune since the removal functions often do the bare minimum to + // remove a thing and may leave around dangling empty modules, resources, + // etc. Prune will clean that all up. + s.prune() + + return nil +} + +func (s *State) removeModule(path []string, v *ModuleState) { + for i, m := range s.Modules { + if m == v { + s.Modules, s.Modules[len(s.Modules)-1] = append(s.Modules[:i], s.Modules[i+1:]...), nil + return + } + } +} + +func (s *State) removeResource(path []string, v *ResourceState) { + // Get the module this resource lives in. If it doesn't exist, we're done. + mod := s.moduleByPath(normalizeModulePath(path)) + if mod == nil { + return + } + + // Find this resource. This is a O(N) lookup when if we had the key + // it could be O(1) but even with thousands of resources this shouldn't + // matter right now. We can easily up performance here when the time comes. + for k, r := range mod.Resources { + if r == v { + // Found it + delete(mod.Resources, k) + return + } + } +} + +func (s *State) removeInstance(path []string, r *ResourceState, v *InstanceState) { + // Go through the resource and find the instance that matches this + // (if any) and remove it. + + // Check primary + if r.Primary == v { + r.Primary = nil + return + } + + // Check lists + lists := [][]*InstanceState{r.Deposed} + for _, is := range lists { + for i, instance := range is { + if instance == v { + // Found it, remove it + is, is[len(is)-1] = append(is[:i], is[i+1:]...), nil + + // Done + return + } + } + } +} + +// RootModule returns the ModuleState for the root module +func (s *State) RootModule() *ModuleState { + root := s.ModuleByPath(addrs.RootModuleInstance) + if root == nil { + panic("missing root module") + } + return root +} + +// Equal tests if one state is equal to another. +func (s *State) Equal(other *State) bool { + // If one is nil, we do a direct check + if s == nil || other == nil { + return s == other + } + + s.Lock() + defer s.Unlock() + return s.equal(other) +} + +func (s *State) equal(other *State) bool { + if s == nil || other == nil { + return s == other + } + + // If the versions are different, they're certainly not equal + if s.Version != other.Version { + return false + } + + // If any of the modules are not equal, then this state isn't equal + if len(s.Modules) != len(other.Modules) { + return false + } + for _, m := range s.Modules { + // This isn't very optimal currently but works. + otherM := other.moduleByPath(normalizeModulePath(m.Path)) + if otherM == nil { + return false + } + + // If they're not equal, then we're not equal! + if !m.Equal(otherM) { + return false + } + } + + return true +} + +// MarshalEqual is similar to Equal but provides a stronger definition of +// "equal", where two states are equal if and only if their serialized form +// is byte-for-byte identical. +// +// This is primarily useful for callers that are trying to save snapshots +// of state to persistent storage, allowing them to detect when a new +// snapshot must be taken. +// +// Note that the serial number and lineage are included in the serialized form, +// so it's the caller's responsibility to properly manage these attributes +// so that this method is only called on two states that have the same +// serial and lineage, unless detecting such differences is desired. +func (s *State) MarshalEqual(other *State) bool { + if s == nil && other == nil { + return true + } else if s == nil || other == nil { + return false + } + + recvBuf := &bytes.Buffer{} + otherBuf := &bytes.Buffer{} + + err := WriteState(s, recvBuf) + if err != nil { + // should never happen, since we're writing to a buffer + panic(err) + } + + err = WriteState(other, otherBuf) + if err != nil { + // should never happen, since we're writing to a buffer + panic(err) + } + + return bytes.Equal(recvBuf.Bytes(), otherBuf.Bytes()) +} + +type StateAgeComparison int + +const ( + StateAgeEqual StateAgeComparison = 0 + StateAgeReceiverNewer StateAgeComparison = 1 + StateAgeReceiverOlder StateAgeComparison = -1 +) + +// CompareAges compares one state with another for which is "older". +// +// This is a simple check using the state's serial, and is thus only as +// reliable as the serial itself. In the normal case, only one state +// exists for a given combination of lineage/serial, but Terraform +// does not guarantee this and so the result of this method should be +// used with care. +// +// Returns an integer that is negative if the receiver is older than +// the argument, positive if the converse, and zero if they are equal. +// An error is returned if the two states are not of the same lineage, +// in which case the integer returned has no meaning. +func (s *State) CompareAges(other *State) (StateAgeComparison, error) { + // nil states are "older" than actual states + switch { + case s != nil && other == nil: + return StateAgeReceiverNewer, nil + case s == nil && other != nil: + return StateAgeReceiverOlder, nil + case s == nil && other == nil: + return StateAgeEqual, nil + } + + if !s.SameLineage(other) { + return StateAgeEqual, fmt.Errorf( + "can't compare two states of differing lineage", + ) + } + + s.Lock() + defer s.Unlock() + + switch { + case s.Serial < other.Serial: + return StateAgeReceiverOlder, nil + case s.Serial > other.Serial: + return StateAgeReceiverNewer, nil + default: + return StateAgeEqual, nil + } +} + +// SameLineage returns true only if the state given in argument belongs +// to the same "lineage" of states as the receiver. +func (s *State) SameLineage(other *State) bool { + s.Lock() + defer s.Unlock() + + // If one of the states has no lineage then it is assumed to predate + // this concept, and so we'll accept it as belonging to any lineage + // so that a lineage string can be assigned to newer versions + // without breaking compatibility with older versions. + if s.Lineage == "" || other.Lineage == "" { + return true + } + + return s.Lineage == other.Lineage +} + +// DeepCopy performs a deep copy of the state structure and returns +// a new structure. +func (s *State) DeepCopy() *State { + if s == nil { + return nil + } + + copy, err := copystructure.Config{Lock: true}.Copy(s) + if err != nil { + panic(err) + } + + return copy.(*State) +} + +// FromFutureTerraform checks if this state was written by a Terraform +// version from the future. +func (s *State) FromFutureTerraform() bool { + s.Lock() + defer s.Unlock() + + // No TF version means it is certainly from the past + if s.TFVersion == "" { + return false + } + + v := version.Must(version.NewVersion(s.TFVersion)) + return tfversion.SemVer.LessThan(v) +} + +func (s *State) Init() { + s.Lock() + defer s.Unlock() + s.init() +} + +func (s *State) init() { + if s.Version == 0 { + s.Version = StateVersion + } + + if s.moduleByPath(addrs.RootModuleInstance) == nil { + s.addModule(addrs.RootModuleInstance) + } + s.ensureHasLineage() + + for _, mod := range s.Modules { + if mod != nil { + mod.init() + } + } + + if s.Remote != nil { + s.Remote.init() + } + +} + +func (s *State) EnsureHasLineage() { + s.Lock() + defer s.Unlock() + + s.ensureHasLineage() +} + +func (s *State) ensureHasLineage() { + if s.Lineage == "" { + lineage, err := uuid.GenerateUUID() + if err != nil { + panic(fmt.Errorf("Failed to generate lineage: %v", err)) + } + s.Lineage = lineage + log.Printf("[DEBUG] New state was assigned lineage %q\n", s.Lineage) + } else { + log.Printf("[TRACE] Preserving existing state lineage %q\n", s.Lineage) + } +} + +// AddModuleState insert this module state and override any existing ModuleState +func (s *State) AddModuleState(mod *ModuleState) { + mod.init() + s.Lock() + defer s.Unlock() + + s.addModuleState(mod) +} + +func (s *State) addModuleState(mod *ModuleState) { + for i, m := range s.Modules { + if reflect.DeepEqual(m.Path, mod.Path) { + s.Modules[i] = mod + return + } + } + + s.Modules = append(s.Modules, mod) + s.sort() +} + +// prune is used to remove any resources that are no longer required +func (s *State) prune() { + if s == nil { + return + } + + // Filter out empty modules. + // A module is always assumed to have a path, and it's length isn't always + // bounds checked later on. Modules may be "emptied" during destroy, but we + // never want to store those in the state. + for i := 0; i < len(s.Modules); i++ { + if s.Modules[i] == nil || len(s.Modules[i].Path) == 0 { + s.Modules = append(s.Modules[:i], s.Modules[i+1:]...) + i-- + } + } + + for _, mod := range s.Modules { + mod.prune() + } + if s.Remote != nil && s.Remote.Empty() { + s.Remote = nil + } +} + +// sort sorts the modules +func (s *State) sort() { + sort.Sort(moduleStateSort(s.Modules)) + + // Allow modules to be sorted + for _, m := range s.Modules { + if m != nil { + m.sort() + } + } +} + +func (s *State) String() string { + if s == nil { + return "" + } + s.Lock() + defer s.Unlock() + + var buf bytes.Buffer + for _, m := range s.Modules { + mStr := m.String() + + // If we're the root module, we just write the output directly. + if reflect.DeepEqual(m.Path, rootModulePath) { + buf.WriteString(mStr + "\n") + continue + } + + buf.WriteString(fmt.Sprintf("module.%s:\n", strings.Join(m.Path[1:], "."))) + + s := bufio.NewScanner(strings.NewReader(mStr)) + for s.Scan() { + text := s.Text() + if text != "" { + text = " " + text + } + + buf.WriteString(fmt.Sprintf("%s\n", text)) + } + } + + return strings.TrimSpace(buf.String()) +} + +// BackendState stores the configuration to connect to a remote backend. +type BackendState struct { + Type string `json:"type"` // Backend type + ConfigRaw json.RawMessage `json:"config"` // Backend raw config + Hash uint64 `json:"hash"` // Hash of portion of configuration from config files +} + +// Empty returns true if BackendState has no state. +func (s *BackendState) Empty() bool { + return s == nil || s.Type == "" +} + +// Config decodes the type-specific configuration object using the provided +// schema and returns the result as a cty.Value. +// +// An error is returned if the stored configuration does not conform to the +// given schema. +func (s *BackendState) Config(schema *configschema.Block) (cty.Value, error) { + ty := schema.ImpliedType() + if s == nil { + return cty.NullVal(ty), nil + } + return ctyjson.Unmarshal(s.ConfigRaw, ty) +} + +// SetConfig replaces (in-place) the type-specific configuration object using +// the provided value and associated schema. +// +// An error is returned if the given value does not conform to the implied +// type of the schema. +func (s *BackendState) SetConfig(val cty.Value, schema *configschema.Block) error { + ty := schema.ImpliedType() + buf, err := ctyjson.Marshal(val, ty) + if err != nil { + return err + } + s.ConfigRaw = buf + return nil +} + +// ForPlan produces an alternative representation of the reciever that is +// suitable for storing in a plan. The current workspace must additionally +// be provided, to be stored alongside the backend configuration. +// +// The backend configuration schema is required in order to properly +// encode the backend-specific configuration settings. +func (s *BackendState) ForPlan(schema *configschema.Block, workspaceName string) (*plans.Backend, error) { + if s == nil { + return nil, nil + } + + configVal, err := s.Config(schema) + if err != nil { + return nil, errwrap.Wrapf("failed to decode backend config: {{err}}", err) + } + return plans.NewBackend(s.Type, configVal, schema, workspaceName) +} + +// RemoteState is used to track the information about a remote +// state store that we push/pull state to. +type RemoteState struct { + // Type controls the client we use for the remote state + Type string `json:"type"` + + // Config is used to store arbitrary configuration that + // is type specific + Config map[string]string `json:"config"` + + mu sync.Mutex +} + +func (s *RemoteState) Lock() { s.mu.Lock() } +func (s *RemoteState) Unlock() { s.mu.Unlock() } + +func (r *RemoteState) init() { + r.Lock() + defer r.Unlock() + + if r.Config == nil { + r.Config = make(map[string]string) + } +} + +func (r *RemoteState) deepcopy() *RemoteState { + r.Lock() + defer r.Unlock() + + confCopy := make(map[string]string, len(r.Config)) + for k, v := range r.Config { + confCopy[k] = v + } + return &RemoteState{ + Type: r.Type, + Config: confCopy, + } +} + +func (r *RemoteState) Empty() bool { + if r == nil { + return true + } + r.Lock() + defer r.Unlock() + + return r.Type == "" +} + +func (r *RemoteState) Equals(other *RemoteState) bool { + r.Lock() + defer r.Unlock() + + if r.Type != other.Type { + return false + } + if len(r.Config) != len(other.Config) { + return false + } + for k, v := range r.Config { + if other.Config[k] != v { + return false + } + } + return true +} + +// OutputState is used to track the state relevant to a single output. +type OutputState struct { + // Sensitive describes whether the output is considered sensitive, + // which may lead to masking the value on screen in some cases. + Sensitive bool `json:"sensitive"` + // Type describes the structure of Value. Valid values are "string", + // "map" and "list" + Type string `json:"type"` + // Value contains the value of the output, in the structure described + // by the Type field. + Value interface{} `json:"value"` + + mu sync.Mutex +} + +func (s *OutputState) Lock() { s.mu.Lock() } +func (s *OutputState) Unlock() { s.mu.Unlock() } + +func (s *OutputState) String() string { + return fmt.Sprintf("%#v", s.Value) +} + +// Equal compares two OutputState structures for equality. nil values are +// considered equal. +func (s *OutputState) Equal(other *OutputState) bool { + if s == nil && other == nil { + return true + } + + if s == nil || other == nil { + return false + } + s.Lock() + defer s.Unlock() + + if s.Type != other.Type { + return false + } + + if s.Sensitive != other.Sensitive { + return false + } + + if !reflect.DeepEqual(s.Value, other.Value) { + return false + } + + return true +} + +func (s *OutputState) deepcopy() *OutputState { + if s == nil { + return nil + } + + stateCopy, err := copystructure.Config{Lock: true}.Copy(s) + if err != nil { + panic(fmt.Errorf("Error copying output value: %s", err)) + } + + return stateCopy.(*OutputState) +} + +// ModuleState is used to track all the state relevant to a single +// module. Previous to Terraform 0.3, all state belonged to the "root" +// module. +type ModuleState struct { + // Path is the import path from the root module. Modules imports are + // always disjoint, so the path represents amodule tree + Path []string `json:"path"` + + // Locals are kept only transiently in-memory, because we can always + // re-compute them. + Locals map[string]interface{} `json:"-"` + + // Outputs declared by the module and maintained for each module + // even though only the root module technically needs to be kept. + // This allows operators to inspect values at the boundaries. + Outputs map[string]*OutputState `json:"outputs"` + + // Resources is a mapping of the logically named resource to + // the state of the resource. Each resource may actually have + // N instances underneath, although a user only needs to think + // about the 1:1 case. + Resources map[string]*ResourceState `json:"resources"` + + // Dependencies are a list of things that this module relies on + // existing to remain intact. For example: an module may depend + // on a VPC ID given by an aws_vpc resource. + // + // Terraform uses this information to build valid destruction + // orders and to warn the user if they're destroying a module that + // another resource depends on. + // + // Things can be put into this list that may not be managed by + // Terraform. If Terraform doesn't find a matching ID in the + // overall state, then it assumes it isn't managed and doesn't + // worry about it. + Dependencies []string `json:"depends_on"` + + mu sync.Mutex +} + +func (s *ModuleState) Lock() { s.mu.Lock() } +func (s *ModuleState) Unlock() { s.mu.Unlock() } + +// Equal tests whether one module state is equal to another. +func (m *ModuleState) Equal(other *ModuleState) bool { + m.Lock() + defer m.Unlock() + + // Paths must be equal + if !reflect.DeepEqual(m.Path, other.Path) { + return false + } + + // Outputs must be equal + if len(m.Outputs) != len(other.Outputs) { + return false + } + for k, v := range m.Outputs { + if !other.Outputs[k].Equal(v) { + return false + } + } + + // Dependencies must be equal. This sorts these in place but + // this shouldn't cause any problems. + sort.Strings(m.Dependencies) + sort.Strings(other.Dependencies) + if len(m.Dependencies) != len(other.Dependencies) { + return false + } + for i, d := range m.Dependencies { + if other.Dependencies[i] != d { + return false + } + } + + // Resources must be equal + if len(m.Resources) != len(other.Resources) { + return false + } + for k, r := range m.Resources { + otherR, ok := other.Resources[k] + if !ok { + return false + } + + if !r.Equal(otherR) { + return false + } + } + + return true +} + +// IsRoot says whether or not this module diff is for the root module. +func (m *ModuleState) IsRoot() bool { + m.Lock() + defer m.Unlock() + return reflect.DeepEqual(m.Path, rootModulePath) +} + +// IsDescendent returns true if other is a descendent of this module. +func (m *ModuleState) IsDescendent(other *ModuleState) bool { + m.Lock() + defer m.Unlock() + + i := len(m.Path) + return len(other.Path) > i && reflect.DeepEqual(other.Path[:i], m.Path) +} + +// Orphans returns a list of keys of resources that are in the State +// but aren't present in the configuration itself. Hence, these keys +// represent the state of resources that are orphans. +func (m *ModuleState) Orphans(c *configs.Module) []addrs.ResourceInstance { + m.Lock() + defer m.Unlock() + + inConfig := make(map[string]struct{}) + if c != nil { + for _, r := range c.ManagedResources { + inConfig[r.Addr().String()] = struct{}{} + } + for _, r := range c.DataResources { + inConfig[r.Addr().String()] = struct{}{} + } + } + + var result []addrs.ResourceInstance + for k := range m.Resources { + // Since we've not yet updated state to use our new address format, + // we need to do some shimming here. + legacyAddr, err := parseResourceAddressInternal(k) + if err != nil { + // Suggests that the user tampered with the state, since we always + // generate valid internal addresses. + log.Printf("ModuleState has invalid resource key %q. Ignoring.", k) + continue + } + + addr := legacyAddr.AbsResourceInstanceAddr().Resource + compareKey := addr.Resource.String() // compare by resource address, ignoring instance key + if _, exists := inConfig[compareKey]; !exists { + result = append(result, addr) + } + } + return result +} + +// RemovedOutputs returns a list of outputs that are in the State but aren't +// present in the configuration itself. +func (s *ModuleState) RemovedOutputs(outputs map[string]*configs.Output) []addrs.OutputValue { + if outputs == nil { + // If we got no output map at all then we'll just treat our set of + // configured outputs as empty, since that suggests that they've all + // been removed by removing their containing module. + outputs = make(map[string]*configs.Output) + } + + s.Lock() + defer s.Unlock() + + var ret []addrs.OutputValue + for n := range s.Outputs { + if _, declared := outputs[n]; !declared { + ret = append(ret, addrs.OutputValue{ + Name: n, + }) + } + } + + return ret +} + +// View returns a view with the given resource prefix. +func (m *ModuleState) View(id string) *ModuleState { + if m == nil { + return m + } + + r := m.deepcopy() + for k, _ := range r.Resources { + if id == k || strings.HasPrefix(k, id+".") { + continue + } + + delete(r.Resources, k) + } + + return r +} + +func (m *ModuleState) init() { + m.Lock() + defer m.Unlock() + + if m.Path == nil { + m.Path = []string{} + } + if m.Outputs == nil { + m.Outputs = make(map[string]*OutputState) + } + if m.Resources == nil { + m.Resources = make(map[string]*ResourceState) + } + + if m.Dependencies == nil { + m.Dependencies = make([]string, 0) + } + + for _, rs := range m.Resources { + rs.init() + } +} + +func (m *ModuleState) deepcopy() *ModuleState { + if m == nil { + return nil + } + + stateCopy, err := copystructure.Config{Lock: true}.Copy(m) + if err != nil { + panic(err) + } + + return stateCopy.(*ModuleState) +} + +// prune is used to remove any resources that are no longer required +func (m *ModuleState) prune() { + m.Lock() + defer m.Unlock() + + for k, v := range m.Resources { + if v == nil || (v.Primary == nil || v.Primary.ID == "") && len(v.Deposed) == 0 { + delete(m.Resources, k) + continue + } + + v.prune() + } + + for k, v := range m.Outputs { + if v.Value == hcl2shim.UnknownVariableValue { + delete(m.Outputs, k) + } + } + + m.Dependencies = uniqueStrings(m.Dependencies) +} + +func (m *ModuleState) sort() { + for _, v := range m.Resources { + v.sort() + } +} + +func (m *ModuleState) String() string { + m.Lock() + defer m.Unlock() + + var buf bytes.Buffer + + if len(m.Resources) == 0 { + buf.WriteString("") + } + + names := make([]string, 0, len(m.Resources)) + for name, _ := range m.Resources { + names = append(names, name) + } + + sort.Sort(resourceNameSort(names)) + + for _, k := range names { + rs := m.Resources[k] + var id string + if rs.Primary != nil { + id = rs.Primary.ID + } + if id == "" { + id = "" + } + + taintStr := "" + if rs.Primary.Tainted { + taintStr = " (tainted)" + } + + deposedStr := "" + if len(rs.Deposed) > 0 { + deposedStr = fmt.Sprintf(" (%d deposed)", len(rs.Deposed)) + } + + buf.WriteString(fmt.Sprintf("%s:%s%s\n", k, taintStr, deposedStr)) + buf.WriteString(fmt.Sprintf(" ID = %s\n", id)) + if rs.Provider != "" { + buf.WriteString(fmt.Sprintf(" provider = %s\n", rs.Provider)) + } + + var attributes map[string]string + if rs.Primary != nil { + attributes = rs.Primary.Attributes + } + attrKeys := make([]string, 0, len(attributes)) + for ak, _ := range attributes { + if ak == "id" { + continue + } + + attrKeys = append(attrKeys, ak) + } + + sort.Strings(attrKeys) + + for _, ak := range attrKeys { + av := attributes[ak] + buf.WriteString(fmt.Sprintf(" %s = %s\n", ak, av)) + } + + for idx, t := range rs.Deposed { + taintStr := "" + if t.Tainted { + taintStr = " (tainted)" + } + buf.WriteString(fmt.Sprintf(" Deposed ID %d = %s%s\n", idx+1, t.ID, taintStr)) + } + + if len(rs.Dependencies) > 0 { + buf.WriteString(fmt.Sprintf("\n Dependencies:\n")) + for _, dep := range rs.Dependencies { + buf.WriteString(fmt.Sprintf(" %s\n", dep)) + } + } + } + + if len(m.Outputs) > 0 { + buf.WriteString("\nOutputs:\n\n") + + ks := make([]string, 0, len(m.Outputs)) + for k, _ := range m.Outputs { + ks = append(ks, k) + } + + sort.Strings(ks) + + for _, k := range ks { + v := m.Outputs[k] + switch vTyped := v.Value.(type) { + case string: + buf.WriteString(fmt.Sprintf("%s = %s\n", k, vTyped)) + case []interface{}: + buf.WriteString(fmt.Sprintf("%s = %s\n", k, vTyped)) + case map[string]interface{}: + var mapKeys []string + for key, _ := range vTyped { + mapKeys = append(mapKeys, key) + } + sort.Strings(mapKeys) + + var mapBuf bytes.Buffer + mapBuf.WriteString("{") + for _, key := range mapKeys { + mapBuf.WriteString(fmt.Sprintf("%s:%s ", key, vTyped[key])) + } + mapBuf.WriteString("}") + + buf.WriteString(fmt.Sprintf("%s = %s\n", k, mapBuf.String())) + } + } + } + + return buf.String() +} + +func (m *ModuleState) Empty() bool { + return len(m.Locals) == 0 && len(m.Outputs) == 0 && len(m.Resources) == 0 +} + +// ResourceStateKey is a structured representation of the key used for the +// ModuleState.Resources mapping +type ResourceStateKey struct { + Name string + Type string + Mode ResourceMode + Index int +} + +// Equal determines whether two ResourceStateKeys are the same +func (rsk *ResourceStateKey) Equal(other *ResourceStateKey) bool { + if rsk == nil || other == nil { + return false + } + if rsk.Mode != other.Mode { + return false + } + if rsk.Type != other.Type { + return false + } + if rsk.Name != other.Name { + return false + } + if rsk.Index != other.Index { + return false + } + return true +} + +func (rsk *ResourceStateKey) String() string { + if rsk == nil { + return "" + } + var prefix string + switch rsk.Mode { + case ManagedResourceMode: + prefix = "" + case DataResourceMode: + prefix = "data." + default: + panic(fmt.Errorf("unknown resource mode %s", rsk.Mode)) + } + if rsk.Index == -1 { + return fmt.Sprintf("%s%s.%s", prefix, rsk.Type, rsk.Name) + } + return fmt.Sprintf("%s%s.%s.%d", prefix, rsk.Type, rsk.Name, rsk.Index) +} + +// ParseResourceStateKey accepts a key in the format used by +// ModuleState.Resources and returns a resource name and resource index. In the +// state, a resource has the format "type.name.index" or "type.name". In the +// latter case, the index is returned as -1. +func ParseResourceStateKey(k string) (*ResourceStateKey, error) { + parts := strings.Split(k, ".") + mode := ManagedResourceMode + if len(parts) > 0 && parts[0] == "data" { + mode = DataResourceMode + // Don't need the constant "data" prefix for parsing + // now that we've figured out the mode. + parts = parts[1:] + } + if len(parts) < 2 || len(parts) > 3 { + return nil, fmt.Errorf("Malformed resource state key: %s", k) + } + rsk := &ResourceStateKey{ + Mode: mode, + Type: parts[0], + Name: parts[1], + Index: -1, + } + if len(parts) == 3 { + index, err := strconv.Atoi(parts[2]) + if err != nil { + return nil, fmt.Errorf("Malformed resource state key index: %s", k) + } + rsk.Index = index + } + return rsk, nil +} + +// ResourceState holds the state of a resource that is used so that +// a provider can find and manage an existing resource as well as for +// storing attributes that are used to populate variables of child +// resources. +// +// Attributes has attributes about the created resource that are +// queryable in interpolation: "${type.id.attr}" +// +// Extra is just extra data that a provider can return that we store +// for later, but is not exposed in any way to the user. +// +type ResourceState struct { + // This is filled in and managed by Terraform, and is the resource + // type itself such as "mycloud_instance". If a resource provider sets + // this value, it won't be persisted. + Type string `json:"type"` + + // Dependencies are a list of things that this resource relies on + // existing to remain intact. For example: an AWS instance might + // depend on a subnet (which itself might depend on a VPC, and so + // on). + // + // Terraform uses this information to build valid destruction + // orders and to warn the user if they're destroying a resource that + // another resource depends on. + // + // Things can be put into this list that may not be managed by + // Terraform. If Terraform doesn't find a matching ID in the + // overall state, then it assumes it isn't managed and doesn't + // worry about it. + Dependencies []string `json:"depends_on"` + + // Primary is the current active instance for this resource. + // It can be replaced but only after a successful creation. + // This is the instances on which providers will act. + Primary *InstanceState `json:"primary"` + + // Deposed is used in the mechanics of CreateBeforeDestroy: the existing + // Primary is Deposed to get it out of the way for the replacement Primary to + // be created by Apply. If the replacement Primary creates successfully, the + // Deposed instance is cleaned up. + // + // If there were problems creating the replacement Primary, the Deposed + // instance and the (now tainted) replacement Primary will be swapped so the + // tainted replacement will be cleaned up instead. + // + // An instance will remain in the Deposed list until it is successfully + // destroyed and purged. + Deposed []*InstanceState `json:"deposed"` + + // Provider is used when a resource is connected to a provider with an alias. + // If this string is empty, the resource is connected to the default provider, + // e.g. "aws_instance" goes with the "aws" provider. + // If the resource block contained a "provider" key, that value will be set here. + Provider string `json:"provider"` + + mu sync.Mutex +} + +func (s *ResourceState) Lock() { s.mu.Lock() } +func (s *ResourceState) Unlock() { s.mu.Unlock() } + +// Equal tests whether two ResourceStates are equal. +func (s *ResourceState) Equal(other *ResourceState) bool { + s.Lock() + defer s.Unlock() + + if s.Type != other.Type { + return false + } + + if s.Provider != other.Provider { + return false + } + + // Dependencies must be equal + sort.Strings(s.Dependencies) + sort.Strings(other.Dependencies) + if len(s.Dependencies) != len(other.Dependencies) { + return false + } + for i, d := range s.Dependencies { + if other.Dependencies[i] != d { + return false + } + } + + // States must be equal + if !s.Primary.Equal(other.Primary) { + return false + } + + return true +} + +// Taint marks a resource as tainted. +func (s *ResourceState) Taint() { + s.Lock() + defer s.Unlock() + + if s.Primary != nil { + s.Primary.Tainted = true + } +} + +// Untaint unmarks a resource as tainted. +func (s *ResourceState) Untaint() { + s.Lock() + defer s.Unlock() + + if s.Primary != nil { + s.Primary.Tainted = false + } +} + +// ProviderAddr returns the provider address for the receiver, by parsing the +// string representation saved in state. An error can be returned if the +// value in state is corrupt. +func (s *ResourceState) ProviderAddr() (addrs.AbsProviderConfig, error) { + var diags tfdiags.Diagnostics + + str := s.Provider + traversal, travDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1}) + diags = diags.Append(travDiags) + if travDiags.HasErrors() { + return addrs.AbsProviderConfig{}, diags.Err() + } + + addr, addrDiags := addrs.ParseAbsProviderConfig(traversal) + diags = diags.Append(addrDiags) + return addr, diags.Err() +} + +func (s *ResourceState) init() { + s.Lock() + defer s.Unlock() + + if s.Primary == nil { + s.Primary = &InstanceState{} + } + s.Primary.init() + + if s.Dependencies == nil { + s.Dependencies = []string{} + } + + if s.Deposed == nil { + s.Deposed = make([]*InstanceState, 0) + } +} + +func (s *ResourceState) deepcopy() *ResourceState { + copy, err := copystructure.Config{Lock: true}.Copy(s) + if err != nil { + panic(err) + } + + return copy.(*ResourceState) +} + +// prune is used to remove any instances that are no longer required +func (s *ResourceState) prune() { + s.Lock() + defer s.Unlock() + + n := len(s.Deposed) + for i := 0; i < n; i++ { + inst := s.Deposed[i] + if inst == nil || inst.ID == "" { + copy(s.Deposed[i:], s.Deposed[i+1:]) + s.Deposed[n-1] = nil + n-- + i-- + } + } + s.Deposed = s.Deposed[:n] + + s.Dependencies = uniqueStrings(s.Dependencies) +} + +func (s *ResourceState) sort() { + s.Lock() + defer s.Unlock() + + sort.Strings(s.Dependencies) +} + +func (s *ResourceState) String() string { + s.Lock() + defer s.Unlock() + + var buf bytes.Buffer + buf.WriteString(fmt.Sprintf("Type = %s", s.Type)) + return buf.String() +} + +// InstanceState is used to track the unique state information belonging +// to a given instance. +type InstanceState struct { + // A unique ID for this resource. This is opaque to Terraform + // and is only meant as a lookup mechanism for the providers. + ID string `json:"id"` + + // Attributes are basic information about the resource. Any keys here + // are accessible in variable format within Terraform configurations: + // ${resourcetype.name.attribute}. + Attributes map[string]string `json:"attributes"` + + // Ephemeral is used to store any state associated with this instance + // that is necessary for the Terraform run to complete, but is not + // persisted to a state file. + Ephemeral EphemeralState `json:"-"` + + // Meta is a simple K/V map that is persisted to the State but otherwise + // ignored by Terraform core. It's meant to be used for accounting by + // external client code. The value here must only contain Go primitives + // and collections. + Meta map[string]interface{} `json:"meta"` + + ProviderMeta cty.Value + + // Tainted is used to mark a resource for recreation. + Tainted bool `json:"tainted"` + + mu sync.Mutex +} + +func (s *InstanceState) Lock() { s.mu.Lock() } +func (s *InstanceState) Unlock() { s.mu.Unlock() } + +func (s *InstanceState) init() { + s.Lock() + defer s.Unlock() + + if s.Attributes == nil { + s.Attributes = make(map[string]string) + } + if s.Meta == nil { + s.Meta = make(map[string]interface{}) + } + s.Ephemeral.init() +} + +// NewInstanceStateShimmedFromValue is a shim method to lower a new-style +// object value representing the attributes of an instance object into the +// legacy InstanceState representation. +// +// This is for shimming to old components only and should not be used in new code. +func NewInstanceStateShimmedFromValue(state cty.Value, schemaVersion int) *InstanceState { + attrs := hcl2shim.FlatmapValueFromHCL2(state) + return &InstanceState{ + ID: attrs["id"], + Attributes: attrs, + Meta: map[string]interface{}{ + "schema_version": schemaVersion, + }, + } +} + +// AttrsAsObjectValue shims from the legacy InstanceState representation to +// a new-style cty object value representation of the state attributes, using +// the given type for guidance. +// +// The given type must be the implied type of the schema of the resource type +// of the object whose state is being converted, or the result is undefined. +// +// This is for shimming from old components only and should not be used in +// new code. +func (s *InstanceState) AttrsAsObjectValue(ty cty.Type) (cty.Value, error) { + if s == nil { + // if the state is nil, we need to construct a complete cty.Value with + // null attributes, rather than a single cty.NullVal(ty) + s = &InstanceState{} + } + + if s.Attributes == nil { + s.Attributes = map[string]string{} + } + + // make sure ID is included in the attributes. The InstanceState.ID value + // takes precedence. + if s.ID != "" { + s.Attributes["id"] = s.ID + } + + return hcl2shim.HCL2ValueFromFlatmap(s.Attributes, ty) +} + +// Copy all the Fields from another InstanceState +func (s *InstanceState) Set(from *InstanceState) { + s.Lock() + defer s.Unlock() + + from.Lock() + defer from.Unlock() + + s.ID = from.ID + s.Attributes = from.Attributes + s.Ephemeral = from.Ephemeral + s.Meta = from.Meta + s.Tainted = from.Tainted +} + +func (s *InstanceState) DeepCopy() *InstanceState { + copy, err := copystructure.Config{Lock: true}.Copy(s) + if err != nil { + panic(err) + } + + return copy.(*InstanceState) +} + +func (s *InstanceState) Empty() bool { + if s == nil { + return true + } + s.Lock() + defer s.Unlock() + + return s.ID == "" +} + +func (s *InstanceState) Equal(other *InstanceState) bool { + // Short circuit some nil checks + if s == nil || other == nil { + return s == other + } + s.Lock() + defer s.Unlock() + + // IDs must be equal + if s.ID != other.ID { + return false + } + + // Attributes must be equal + if len(s.Attributes) != len(other.Attributes) { + return false + } + for k, v := range s.Attributes { + otherV, ok := other.Attributes[k] + if !ok { + return false + } + + if v != otherV { + return false + } + } + + // Meta must be equal + if len(s.Meta) != len(other.Meta) { + return false + } + if s.Meta != nil && other.Meta != nil { + // We only do the deep check if both are non-nil. If one is nil + // we treat it as equal since their lengths are both zero (check + // above). + // + // Since this can contain numeric values that may change types during + // serialization, let's compare the serialized values. + sMeta, err := json.Marshal(s.Meta) + if err != nil { + // marshaling primitives shouldn't ever error out + panic(err) + } + otherMeta, err := json.Marshal(other.Meta) + if err != nil { + panic(err) + } + + if !bytes.Equal(sMeta, otherMeta) { + return false + } + } + + if s.Tainted != other.Tainted { + return false + } + + return true +} + +// MergeDiff takes a ResourceDiff and merges the attributes into +// this resource state in order to generate a new state. This new +// state can be used to provide updated attribute lookups for +// variable interpolation. +// +// If the diff attribute requires computing the value, and hence +// won't be available until apply, the value is replaced with the +// computeID. +func (s *InstanceState) MergeDiff(d *InstanceDiff) *InstanceState { + result := s.DeepCopy() + if result == nil { + result = new(InstanceState) + } + result.init() + + if s != nil { + s.Lock() + defer s.Unlock() + for k, v := range s.Attributes { + result.Attributes[k] = v + } + } + if d != nil { + for k, diff := range d.CopyAttributes() { + if diff.NewRemoved { + delete(result.Attributes, k) + continue + } + if diff.NewComputed { + result.Attributes[k] = hcl2shim.UnknownVariableValue + continue + } + + result.Attributes[k] = diff.New + } + } + + return result +} + +func (s *InstanceState) String() string { + notCreated := "" + + if s == nil { + return notCreated + } + + s.Lock() + defer s.Unlock() + + var buf bytes.Buffer + + if s.ID == "" { + return notCreated + } + + buf.WriteString(fmt.Sprintf("ID = %s\n", s.ID)) + + attributes := s.Attributes + attrKeys := make([]string, 0, len(attributes)) + for ak, _ := range attributes { + if ak == "id" { + continue + } + + attrKeys = append(attrKeys, ak) + } + sort.Strings(attrKeys) + + for _, ak := range attrKeys { + av := attributes[ak] + buf.WriteString(fmt.Sprintf("%s = %s\n", ak, av)) + } + + buf.WriteString(fmt.Sprintf("Tainted = %t\n", s.Tainted)) + + return buf.String() +} + +// EphemeralState is used for transient state that is only kept in-memory +type EphemeralState struct { + // ConnInfo is used for the providers to export information which is + // used to connect to the resource for provisioning. For example, + // this could contain SSH or WinRM credentials. + ConnInfo map[string]string `json:"-"` + + // Type is used to specify the resource type for this instance. This is only + // required for import operations (as documented). If the documentation + // doesn't state that you need to set this, then don't worry about + // setting it. + Type string `json:"-"` +} + +func (e *EphemeralState) init() { + if e.ConnInfo == nil { + e.ConnInfo = make(map[string]string) + } +} + +func (e *EphemeralState) DeepCopy() *EphemeralState { + copy, err := copystructure.Config{Lock: true}.Copy(e) + if err != nil { + panic(err) + } + + return copy.(*EphemeralState) +} + +type jsonStateVersionIdentifier struct { + Version int `json:"version"` +} + +// Check if this is a V0 format - the magic bytes at the start of the file +// should be "tfstate" if so. We no longer support upgrading this type of +// state but return an error message explaining to a user how they can +// upgrade via the 0.6.x series. +func testForV0State(buf *bufio.Reader) error { + start, err := buf.Peek(len("tfstate")) + if err != nil { + return fmt.Errorf("Failed to check for magic bytes: %v", err) + } + if string(start) == "tfstate" { + return fmt.Errorf("Terraform 0.7 no longer supports upgrading the binary state\n" + + "format which was used prior to Terraform 0.3. Please upgrade\n" + + "this state file using Terraform 0.6.16 prior to using it with\n" + + "Terraform 0.7.") + } + + return nil +} + +// ErrNoState is returned by ReadState when the io.Reader contains no data +var ErrNoState = errors.New("no state") + +// ReadState reads a state structure out of a reader in the format that +// was written by WriteState. +func ReadState(src io.Reader) (*State, error) { + // check for a nil file specifically, since that produces a platform + // specific error if we try to use it in a bufio.Reader. + if f, ok := src.(*os.File); ok && f == nil { + return nil, ErrNoState + } + + buf := bufio.NewReader(src) + + if _, err := buf.Peek(1); err != nil { + if err == io.EOF { + return nil, ErrNoState + } + return nil, err + } + + if err := testForV0State(buf); err != nil { + return nil, err + } + + // If we are JSON we buffer the whole thing in memory so we can read it twice. + // This is suboptimal, but will work for now. + jsonBytes, err := ioutil.ReadAll(buf) + if err != nil { + return nil, fmt.Errorf("Reading state file failed: %v", err) + } + + versionIdentifier := &jsonStateVersionIdentifier{} + if err := json.Unmarshal(jsonBytes, versionIdentifier); err != nil { + return nil, fmt.Errorf("Decoding state file version failed: %v", err) + } + + var result *State + switch versionIdentifier.Version { + case 0: + return nil, fmt.Errorf("State version 0 is not supported as JSON.") + case 1: + v1State, err := ReadStateV1(jsonBytes) + if err != nil { + return nil, err + } + + v2State, err := upgradeStateV1ToV2(v1State) + if err != nil { + return nil, err + } + + v3State, err := upgradeStateV2ToV3(v2State) + if err != nil { + return nil, err + } + + // increment the Serial whenever we upgrade state + v3State.Serial++ + result = v3State + case 2: + v2State, err := ReadStateV2(jsonBytes) + if err != nil { + return nil, err + } + v3State, err := upgradeStateV2ToV3(v2State) + if err != nil { + return nil, err + } + + v3State.Serial++ + result = v3State + case 3: + v3State, err := ReadStateV3(jsonBytes) + if err != nil { + return nil, err + } + + result = v3State + default: + return nil, fmt.Errorf("Terraform %s does not support state version %d, please update.", + tfversion.SemVer.String(), versionIdentifier.Version) + } + + // If we reached this place we must have a result set + if result == nil { + panic("resulting state in load not set, assertion failed") + } + + // Prune the state when read it. Its possible to write unpruned states or + // for a user to make a state unpruned (nil-ing a module state for example). + result.prune() + + // Validate the state file is valid + if err := result.Validate(); err != nil { + return nil, err + } + + return result, nil +} + +func ReadStateV1(jsonBytes []byte) (*stateV1, error) { + v1State := &stateV1{} + if err := json.Unmarshal(jsonBytes, v1State); err != nil { + return nil, fmt.Errorf("Decoding state file failed: %v", err) + } + + if v1State.Version != 1 { + return nil, fmt.Errorf("Decoded state version did not match the decoder selection: "+ + "read %d, expected 1", v1State.Version) + } + + return v1State, nil +} + +func ReadStateV2(jsonBytes []byte) (*State, error) { + state := &State{} + if err := json.Unmarshal(jsonBytes, state); err != nil { + return nil, fmt.Errorf("Decoding state file failed: %v", err) + } + + // Check the version, this to ensure we don't read a future + // version that we don't understand + if state.Version > StateVersion { + return nil, fmt.Errorf("Terraform %s does not support state version %d, please update.", + tfversion.SemVer.String(), state.Version) + } + + // Make sure the version is semantic + if state.TFVersion != "" { + if _, err := version.NewVersion(state.TFVersion); err != nil { + return nil, fmt.Errorf( + "State contains invalid version: %s\n\n"+ + "Terraform validates the version format prior to writing it. This\n"+ + "means that this is invalid of the state becoming corrupted through\n"+ + "some external means. Please manually modify the Terraform version\n"+ + "field to be a proper semantic version.", + state.TFVersion) + } + } + + // catch any unitialized fields in the state + state.init() + + // Sort it + state.sort() + + return state, nil +} + +func ReadStateV3(jsonBytes []byte) (*State, error) { + state := &State{} + if err := json.Unmarshal(jsonBytes, state); err != nil { + return nil, fmt.Errorf("Decoding state file failed: %v", err) + } + + // Check the version, this to ensure we don't read a future + // version that we don't understand + if state.Version > StateVersion { + return nil, fmt.Errorf("Terraform %s does not support state version %d, please update.", + tfversion.SemVer.String(), state.Version) + } + + // Make sure the version is semantic + if state.TFVersion != "" { + if _, err := version.NewVersion(state.TFVersion); err != nil { + return nil, fmt.Errorf( + "State contains invalid version: %s\n\n"+ + "Terraform validates the version format prior to writing it. This\n"+ + "means that this is invalid of the state becoming corrupted through\n"+ + "some external means. Please manually modify the Terraform version\n"+ + "field to be a proper semantic version.", + state.TFVersion) + } + } + + // catch any unitialized fields in the state + state.init() + + // Sort it + state.sort() + + // Now we write the state back out to detect any changes in normaliztion. + // If our state is now written out differently, bump the serial number to + // prevent conflicts. + var buf bytes.Buffer + err := WriteState(state, &buf) + if err != nil { + return nil, err + } + + if !bytes.Equal(jsonBytes, buf.Bytes()) { + log.Println("[INFO] state modified during read or write. incrementing serial number") + state.Serial++ + } + + return state, nil +} + +// WriteState writes a state somewhere in a binary format. +func WriteState(d *State, dst io.Writer) error { + // writing a nil state is a noop. + if d == nil { + return nil + } + + // make sure we have no uninitialized fields + d.init() + + // Make sure it is sorted + d.sort() + + // Ensure the version is set + d.Version = StateVersion + + // If the TFVersion is set, verify it. We used to just set the version + // here, but this isn't safe since it changes the MD5 sum on some remote + // state storage backends such as Atlas. We now leave it be if needed. + if d.TFVersion != "" { + if _, err := version.NewVersion(d.TFVersion); err != nil { + return fmt.Errorf( + "Error writing state, invalid version: %s\n\n"+ + "The Terraform version when writing the state must be a semantic\n"+ + "version.", + d.TFVersion) + } + } + + // Encode the data in a human-friendly way + data, err := json.MarshalIndent(d, "", " ") + if err != nil { + return fmt.Errorf("Failed to encode state: %s", err) + } + + // We append a newline to the data because MarshalIndent doesn't + data = append(data, '\n') + + // Write the data out to the dst + if _, err := io.Copy(dst, bytes.NewReader(data)); err != nil { + return fmt.Errorf("Failed to write state: %v", err) + } + + return nil +} + +// resourceNameSort implements the sort.Interface to sort name parts lexically for +// strings and numerically for integer indexes. +type resourceNameSort []string + +func (r resourceNameSort) Len() int { return len(r) } +func (r resourceNameSort) Swap(i, j int) { r[i], r[j] = r[j], r[i] } + +func (r resourceNameSort) Less(i, j int) bool { + iParts := strings.Split(r[i], ".") + jParts := strings.Split(r[j], ".") + + end := len(iParts) + if len(jParts) < end { + end = len(jParts) + } + + for idx := 0; idx < end; idx++ { + if iParts[idx] == jParts[idx] { + continue + } + + // sort on the first non-matching part + iInt, iIntErr := strconv.Atoi(iParts[idx]) + jInt, jIntErr := strconv.Atoi(jParts[idx]) + + switch { + case iIntErr == nil && jIntErr == nil: + // sort numerically if both parts are integers + return iInt < jInt + case iIntErr == nil: + // numbers sort before strings + return true + case jIntErr == nil: + return false + default: + return iParts[idx] < jParts[idx] + } + } + + return r[i] < r[j] +} + +// moduleStateSort implements sort.Interface to sort module states +type moduleStateSort []*ModuleState + +func (s moduleStateSort) Len() int { + return len(s) +} + +func (s moduleStateSort) Less(i, j int) bool { + a := s[i] + b := s[j] + + // If either is nil, then the nil one is "less" than + if a == nil || b == nil { + return a == nil + } + + // If the lengths are different, then the shorter one always wins + if len(a.Path) != len(b.Path) { + return len(a.Path) < len(b.Path) + } + + // Otherwise, compare lexically + return strings.Join(a.Path, ".") < strings.Join(b.Path, ".") +} + +func (s moduleStateSort) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +const stateValidateErrMultiModule = ` +Multiple modules with the same path: %s + +This means that there are multiple entries in the "modules" field +in your state file that point to the same module. This will cause Terraform +to behave in unexpected and error prone ways and is invalid. Please back up +and modify your state file manually to resolve this. +` diff --git a/internal/legacy/terraform/state_filter.go b/internal/legacy/terraform/state_filter.go new file mode 100644 index 000000000..2dcb11b76 --- /dev/null +++ b/internal/legacy/terraform/state_filter.go @@ -0,0 +1,267 @@ +package terraform + +import ( + "fmt" + "sort" +) + +// StateFilter is responsible for filtering and searching a state. +// +// This is a separate struct from State rather than a method on State +// because StateFilter might create sidecar data structures to optimize +// filtering on the state. +// +// If you change the State, the filter created is invalid and either +// Reset should be called or a new one should be allocated. StateFilter +// will not watch State for changes and do this for you. If you filter after +// changing the State without calling Reset, the behavior is not defined. +type StateFilter struct { + State *State +} + +// Filter takes the addresses specified by fs and finds all the matches. +// The values of fs are resource addressing syntax that can be parsed by +// ParseResourceAddress. +func (f *StateFilter) Filter(fs ...string) ([]*StateFilterResult, error) { + // Parse all the addresses + as := make([]*ResourceAddress, len(fs)) + for i, v := range fs { + a, err := ParseResourceAddress(v) + if err != nil { + return nil, fmt.Errorf("Error parsing address '%s': %s", v, err) + } + + as[i] = a + } + + // If we weren't given any filters, then we list all + if len(fs) == 0 { + as = append(as, &ResourceAddress{Index: -1}) + } + + // Filter each of the address. We keep track of this in a map to + // strip duplicates. + resultSet := make(map[string]*StateFilterResult) + for _, a := range as { + for _, r := range f.filterSingle(a) { + resultSet[r.String()] = r + } + } + + // Make the result list + results := make([]*StateFilterResult, 0, len(resultSet)) + for _, v := range resultSet { + results = append(results, v) + } + + // Sort them and return + sort.Sort(StateFilterResultSlice(results)) + return results, nil +} + +func (f *StateFilter) filterSingle(a *ResourceAddress) []*StateFilterResult { + // The slice to keep track of results + var results []*StateFilterResult + + // Go through modules first. + modules := make([]*ModuleState, 0, len(f.State.Modules)) + for _, m := range f.State.Modules { + if f.relevant(a, m) { + modules = append(modules, m) + + // Only add the module to the results if we haven't specified a type. + // We also ignore the root module. + if a.Type == "" && len(m.Path) > 1 { + results = append(results, &StateFilterResult{ + Path: m.Path[1:], + Address: (&ResourceAddress{Path: m.Path[1:]}).String(), + Value: m, + }) + } + } + } + + // With the modules set, go through all the resources within + // the modules to find relevant resources. + for _, m := range modules { + for n, r := range m.Resources { + // The name in the state contains valuable information. Parse. + key, err := ParseResourceStateKey(n) + if err != nil { + // If we get an error parsing, then just ignore it + // out of the state. + continue + } + + // Older states and test fixtures often don't contain the + // type directly on the ResourceState. We add this so StateFilter + // is a bit more robust. + if r.Type == "" { + r.Type = key.Type + } + + if f.relevant(a, r) { + if a.Name != "" && a.Name != key.Name { + // Name doesn't match + continue + } + + if a.Index >= 0 && key.Index != a.Index { + // Index doesn't match + continue + } + + if a.Name != "" && a.Name != key.Name { + continue + } + + // Build the address for this resource + addr := &ResourceAddress{ + Path: m.Path[1:], + Name: key.Name, + Type: key.Type, + Index: key.Index, + } + + // Add the resource level result + resourceResult := &StateFilterResult{ + Path: addr.Path, + Address: addr.String(), + Value: r, + } + if !a.InstanceTypeSet { + results = append(results, resourceResult) + } + + // Add the instances + if r.Primary != nil { + addr.InstanceType = TypePrimary + addr.InstanceTypeSet = false + results = append(results, &StateFilterResult{ + Path: addr.Path, + Address: addr.String(), + Parent: resourceResult, + Value: r.Primary, + }) + } + + for _, instance := range r.Deposed { + if f.relevant(a, instance) { + addr.InstanceType = TypeDeposed + addr.InstanceTypeSet = true + results = append(results, &StateFilterResult{ + Path: addr.Path, + Address: addr.String(), + Parent: resourceResult, + Value: instance, + }) + } + } + } + } + } + + return results +} + +// relevant checks for relevance of this address against the given value. +func (f *StateFilter) relevant(addr *ResourceAddress, raw interface{}) bool { + switch v := raw.(type) { + case *ModuleState: + path := v.Path[1:] + + if len(addr.Path) > len(path) { + // Longer path in address means there is no way we match. + return false + } + + // Check for a prefix match + for i, p := range addr.Path { + if path[i] != p { + // Any mismatches don't match. + return false + } + } + + return true + case *ResourceState: + if addr.Type == "" { + // If we have no resource type, then we're interested in all! + return true + } + + // If the type doesn't match we fail immediately + if v.Type != addr.Type { + return false + } + + return true + default: + // If we don't know about it, let's just say no + return false + } +} + +// StateFilterResult is a single result from a filter operation. Filter +// can match multiple things within a state (module, resource, instance, etc.) +// and this unifies that. +type StateFilterResult struct { + // Module path of the result + Path []string + + // Address is the address that can be used to reference this exact result. + Address string + + // Parent, if non-nil, is a parent of this result. For instances, the + // parent would be a resource. For resources, the parent would be + // a module. For modules, this is currently nil. + Parent *StateFilterResult + + // Value is the actual value. This must be type switched on. It can be + // any data structures that `State` can hold: `ModuleState`, + // `ResourceState`, `InstanceState`. + Value interface{} +} + +func (r *StateFilterResult) String() string { + return fmt.Sprintf("%T: %s", r.Value, r.Address) +} + +func (r *StateFilterResult) sortedType() int { + switch r.Value.(type) { + case *ModuleState: + return 0 + case *ResourceState: + return 1 + case *InstanceState: + return 2 + default: + return 50 + } +} + +// StateFilterResultSlice is a slice of results that implements +// sort.Interface. The sorting goal is what is most appealing to +// human output. +type StateFilterResultSlice []*StateFilterResult + +func (s StateFilterResultSlice) Len() int { return len(s) } +func (s StateFilterResultSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s StateFilterResultSlice) Less(i, j int) bool { + a, b := s[i], s[j] + + // if these address contain an index, we want to sort by index rather than name + addrA, errA := ParseResourceAddress(a.Address) + addrB, errB := ParseResourceAddress(b.Address) + if errA == nil && errB == nil && addrA.Name == addrB.Name && addrA.Index != addrB.Index { + return addrA.Index < addrB.Index + } + + // If the addresses are different it is just lexographic sorting + if a.Address != b.Address { + return a.Address < b.Address + } + + // Addresses are the same, which means it matters on the type + return a.sortedType() < b.sortedType() +} diff --git a/internal/legacy/terraform/state_test.go b/internal/legacy/terraform/state_test.go new file mode 100644 index 000000000..beac79705 --- /dev/null +++ b/internal/legacy/terraform/state_test.go @@ -0,0 +1,1894 @@ +package terraform + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + "reflect" + "sort" + "strings" + "testing" + + "github.com/davecgh/go-spew/spew" + "github.com/hashicorp/terraform/addrs" + "github.com/hashicorp/terraform/configs/hcl2shim" +) + +func TestStateValidate(t *testing.T) { + cases := map[string]struct { + In *State + Err bool + }{ + "empty state": { + &State{}, + false, + }, + + "multiple modules": { + &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: []string{"root", "foo"}, + }, + &ModuleState{ + Path: []string{"root", "foo"}, + }, + }, + }, + true, + }, + } + + for name, tc := range cases { + // Init the state + tc.In.init() + + err := tc.In.Validate() + if (err != nil) != tc.Err { + t.Fatalf("%s: err: %s", name, err) + } + } +} + +func TestStateAddModule(t *testing.T) { + cases := []struct { + In []addrs.ModuleInstance + Out [][]string + }{ + { + []addrs.ModuleInstance{ + addrs.RootModuleInstance, + addrs.RootModuleInstance.Child("child", addrs.NoKey), + }, + [][]string{ + []string{"root"}, + []string{"root", "child"}, + }, + }, + + { + []addrs.ModuleInstance{ + addrs.RootModuleInstance.Child("foo", addrs.NoKey).Child("bar", addrs.NoKey), + addrs.RootModuleInstance.Child("foo", addrs.NoKey), + addrs.RootModuleInstance, + addrs.RootModuleInstance.Child("bar", addrs.NoKey), + }, + [][]string{ + []string{"root"}, + []string{"root", "bar"}, + []string{"root", "foo"}, + []string{"root", "foo", "bar"}, + }, + }, + // Same last element, different middle element + { + []addrs.ModuleInstance{ + addrs.RootModuleInstance.Child("foo", addrs.NoKey).Child("bar", addrs.NoKey), // This one should sort after... + addrs.RootModuleInstance.Child("foo", addrs.NoKey), + addrs.RootModuleInstance, + addrs.RootModuleInstance.Child("bar", addrs.NoKey).Child("bar", addrs.NoKey), // ...this one. + addrs.RootModuleInstance.Child("bar", addrs.NoKey), + }, + [][]string{ + []string{"root"}, + []string{"root", "bar"}, + []string{"root", "foo"}, + []string{"root", "bar", "bar"}, + []string{"root", "foo", "bar"}, + }, + }, + } + + for _, tc := range cases { + s := new(State) + for _, p := range tc.In { + s.AddModule(p) + } + + actual := make([][]string, 0, len(tc.In)) + for _, m := range s.Modules { + actual = append(actual, m.Path) + } + + if !reflect.DeepEqual(actual, tc.Out) { + t.Fatalf("wrong result\ninput: %sgot: %#v\nwant: %#v", spew.Sdump(tc.In), actual, tc.Out) + } + } +} + +func TestStateOutputTypeRoundTrip(t *testing.T) { + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: []string{"root"}, + Outputs: map[string]*OutputState{ + "string_output": &OutputState{ + Value: "String Value", + Type: "string", + }, + }, + }, + }, + } + state.init() + + buf := new(bytes.Buffer) + if err := WriteState(state, buf); err != nil { + t.Fatalf("err: %s", err) + } + + roundTripped, err := ReadState(buf) + if err != nil { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(state, roundTripped) { + t.Logf("expected:\n%#v", state) + t.Fatalf("got:\n%#v", roundTripped) + } +} + +func TestStateDeepCopy(t *testing.T) { + cases := []struct { + State *State + }{ + // Nil + {nil}, + + // Version + { + &State{Version: 5}, + }, + // TFVersion + { + &State{TFVersion: "5"}, + }, + // Modules + { + &State{ + Version: 6, + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "test_instance.foo": &ResourceState{ + Primary: &InstanceState{ + Meta: map[string]interface{}{}, + }, + }, + }, + }, + }, + }, + }, + // Deposed + // The nil values shouldn't be there if the State was properly init'ed, + // but the Copy should still work anyway. + { + &State{ + Version: 6, + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "test_instance.foo": &ResourceState{ + Primary: &InstanceState{ + Meta: map[string]interface{}{}, + }, + Deposed: []*InstanceState{ + {ID: "test"}, + nil, + }, + }, + }, + }, + }, + }, + }, + } + + for i, tc := range cases { + t.Run(fmt.Sprintf("copy-%d", i), func(t *testing.T) { + actual := tc.State.DeepCopy() + expected := tc.State + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("Expected: %#v\nRecevied: %#v\n", expected, actual) + } + }) + } +} + +func TestStateEqual(t *testing.T) { + cases := []struct { + Name string + Result bool + One, Two *State + }{ + // Nils + { + "one nil", + false, + nil, + &State{Version: 2}, + }, + + { + "both nil", + true, + nil, + nil, + }, + + // Different versions + { + "different state versions", + false, + &State{Version: 5}, + &State{Version: 2}, + }, + + // Different modules + { + "different module states", + false, + &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: []string{"root"}, + }, + }, + }, + &State{}, + }, + + { + "same module states", + true, + &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: []string{"root"}, + }, + }, + }, + &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: []string{"root"}, + }, + }, + }, + }, + + // Meta differs + { + "differing meta values with primitives", + false, + &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "test_instance.foo": &ResourceState{ + Primary: &InstanceState{ + Meta: map[string]interface{}{ + "schema_version": "1", + }, + }, + }, + }, + }, + }, + }, + &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "test_instance.foo": &ResourceState{ + Primary: &InstanceState{ + Meta: map[string]interface{}{ + "schema_version": "2", + }, + }, + }, + }, + }, + }, + }, + }, + + // Meta with complex types + { + "same meta with complex types", + true, + &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "test_instance.foo": &ResourceState{ + Primary: &InstanceState{ + Meta: map[string]interface{}{ + "timeouts": map[string]interface{}{ + "create": 42, + "read": "27", + }, + }, + }, + }, + }, + }, + }, + }, + &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "test_instance.foo": &ResourceState{ + Primary: &InstanceState{ + Meta: map[string]interface{}{ + "timeouts": map[string]interface{}{ + "create": 42, + "read": "27", + }, + }, + }, + }, + }, + }, + }, + }, + }, + + // Meta with complex types that have been altered during serialization + { + "same meta with complex types that have been json-ified", + true, + &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "test_instance.foo": &ResourceState{ + Primary: &InstanceState{ + Meta: map[string]interface{}{ + "timeouts": map[string]interface{}{ + "create": int(42), + "read": "27", + }, + }, + }, + }, + }, + }, + }, + }, + &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "test_instance.foo": &ResourceState{ + Primary: &InstanceState{ + Meta: map[string]interface{}{ + "timeouts": map[string]interface{}{ + "create": float64(42), + "read": "27", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + for i, tc := range cases { + t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { + if tc.One.Equal(tc.Two) != tc.Result { + t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) + } + if tc.Two.Equal(tc.One) != tc.Result { + t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) + } + }) + } +} + +func TestStateCompareAges(t *testing.T) { + cases := []struct { + Result StateAgeComparison + Err bool + One, Two *State + }{ + { + StateAgeEqual, false, + &State{ + Lineage: "1", + Serial: 2, + }, + &State{ + Lineage: "1", + Serial: 2, + }, + }, + { + StateAgeReceiverOlder, false, + &State{ + Lineage: "1", + Serial: 2, + }, + &State{ + Lineage: "1", + Serial: 3, + }, + }, + { + StateAgeReceiverNewer, false, + &State{ + Lineage: "1", + Serial: 3, + }, + &State{ + Lineage: "1", + Serial: 2, + }, + }, + { + StateAgeEqual, true, + &State{ + Lineage: "1", + Serial: 2, + }, + &State{ + Lineage: "2", + Serial: 2, + }, + }, + { + StateAgeEqual, true, + &State{ + Lineage: "1", + Serial: 3, + }, + &State{ + Lineage: "2", + Serial: 2, + }, + }, + } + + for i, tc := range cases { + result, err := tc.One.CompareAges(tc.Two) + + if err != nil && !tc.Err { + t.Errorf( + "%d: got error, but want success\n\n%s\n\n%s", + i, tc.One, tc.Two, + ) + continue + } + + if err == nil && tc.Err { + t.Errorf( + "%d: got success, but want error\n\n%s\n\n%s", + i, tc.One, tc.Two, + ) + continue + } + + if result != tc.Result { + t.Errorf( + "%d: got result %d, but want %d\n\n%s\n\n%s", + i, result, tc.Result, tc.One, tc.Two, + ) + continue + } + } +} + +func TestStateSameLineage(t *testing.T) { + cases := []struct { + Result bool + One, Two *State + }{ + { + true, + &State{ + Lineage: "1", + }, + &State{ + Lineage: "1", + }, + }, + { + // Empty lineage is compatible with all + true, + &State{ + Lineage: "", + }, + &State{ + Lineage: "1", + }, + }, + { + // Empty lineage is compatible with all + true, + &State{ + Lineage: "1", + }, + &State{ + Lineage: "", + }, + }, + { + false, + &State{ + Lineage: "1", + }, + &State{ + Lineage: "2", + }, + }, + } + + for i, tc := range cases { + result := tc.One.SameLineage(tc.Two) + + if result != tc.Result { + t.Errorf( + "%d: got %v, but want %v\n\n%s\n\n%s", + i, result, tc.Result, tc.One, tc.Two, + ) + continue + } + } +} + +func TestStateMarshalEqual(t *testing.T) { + tests := map[string]struct { + S1, S2 *State + Want bool + }{ + "both nil": { + nil, + nil, + true, + }, + "first zero, second nil": { + &State{}, + nil, + false, + }, + "first nil, second zero": { + nil, + &State{}, + false, + }, + "both zero": { + // These are not equal because they both implicitly init with + // different lineage. + &State{}, + &State{}, + false, + }, + "both set, same lineage": { + &State{ + Lineage: "abc123", + }, + &State{ + Lineage: "abc123", + }, + true, + }, + "both set, same lineage, different serial": { + &State{ + Lineage: "abc123", + Serial: 1, + }, + &State{ + Lineage: "abc123", + Serial: 2, + }, + false, + }, + "both set, same lineage, same serial, same resources": { + &State{ + Lineage: "abc123", + Serial: 1, + Modules: []*ModuleState{ + { + Path: []string{"root"}, + Resources: map[string]*ResourceState{ + "foo_bar.baz": {}, + }, + }, + }, + }, + &State{ + Lineage: "abc123", + Serial: 1, + Modules: []*ModuleState{ + { + Path: []string{"root"}, + Resources: map[string]*ResourceState{ + "foo_bar.baz": {}, + }, + }, + }, + }, + true, + }, + "both set, same lineage, same serial, different resources": { + &State{ + Lineage: "abc123", + Serial: 1, + Modules: []*ModuleState{ + { + Path: []string{"root"}, + Resources: map[string]*ResourceState{ + "foo_bar.baz": {}, + }, + }, + }, + }, + &State{ + Lineage: "abc123", + Serial: 1, + Modules: []*ModuleState{ + { + Path: []string{"root"}, + Resources: map[string]*ResourceState{ + "pizza_crust.tasty": {}, + }, + }, + }, + }, + false, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + got := test.S1.MarshalEqual(test.S2) + if got != test.Want { + t.Errorf("wrong result %#v; want %#v", got, test.Want) + s1Buf := &bytes.Buffer{} + s2Buf := &bytes.Buffer{} + _ = WriteState(test.S1, s1Buf) + _ = WriteState(test.S2, s2Buf) + t.Logf("\nState 1: %s\nState 2: %s", s1Buf.Bytes(), s2Buf.Bytes()) + } + }) + } +} + +func TestStateRemove(t *testing.T) { + cases := map[string]struct { + Address string + One, Two *State + }{ + "simple resource": { + "test_instance.foo", + &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "test_instance.foo": &ResourceState{ + Type: "test_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + + "test_instance.bar": &ResourceState{ + Type: "test_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + }, + }, + }, + }, + &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "test_instance.bar": &ResourceState{ + Type: "test_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + }, + }, + }, + }, + }, + + "single instance": { + "test_instance.foo.primary", + &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "test_instance.foo": &ResourceState{ + Type: "test_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + }, + }, + }, + }, + &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{}, + }, + }, + }, + }, + + "single instance in multi-count": { + "test_instance.foo[0]", + &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "test_instance.foo.0": &ResourceState{ + Type: "test_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + + "test_instance.foo.1": &ResourceState{ + Type: "test_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + }, + }, + }, + }, + &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "test_instance.foo.1": &ResourceState{ + Type: "test_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + }, + }, + }, + }, + }, + + "single resource, multi-count": { + "test_instance.foo", + &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "test_instance.foo.0": &ResourceState{ + Type: "test_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + + "test_instance.foo.1": &ResourceState{ + Type: "test_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + }, + }, + }, + }, + &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{}, + }, + }, + }, + }, + + "full module": { + "module.foo", + &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "test_instance.foo": &ResourceState{ + Type: "test_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + }, + }, + + &ModuleState{ + Path: []string{"root", "foo"}, + Resources: map[string]*ResourceState{ + "test_instance.foo": &ResourceState{ + Type: "test_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + + "test_instance.bar": &ResourceState{ + Type: "test_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + }, + }, + }, + }, + &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "test_instance.foo": &ResourceState{ + Type: "test_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + }, + }, + }, + }, + }, + + "module and children": { + "module.foo", + &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "test_instance.foo": &ResourceState{ + Type: "test_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + }, + }, + + &ModuleState{ + Path: []string{"root", "foo"}, + Resources: map[string]*ResourceState{ + "test_instance.foo": &ResourceState{ + Type: "test_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + + "test_instance.bar": &ResourceState{ + Type: "test_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + }, + }, + + &ModuleState{ + Path: []string{"root", "foo", "bar"}, + Resources: map[string]*ResourceState{ + "test_instance.foo": &ResourceState{ + Type: "test_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + + "test_instance.bar": &ResourceState{ + Type: "test_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + }, + }, + }, + }, + &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "test_instance.foo": &ResourceState{ + Type: "test_instance", + Primary: &InstanceState{ + ID: "foo", + }, + }, + }, + }, + }, + }, + }, + } + + for k, tc := range cases { + if err := tc.One.Remove(tc.Address); err != nil { + t.Fatalf("bad: %s\n\n%s", k, err) + } + + if !tc.One.Equal(tc.Two) { + t.Fatalf("Bad: %s\n\n%s\n\n%s", k, tc.One.String(), tc.Two.String()) + } + } +} + +func TestResourceStateEqual(t *testing.T) { + cases := []struct { + Result bool + One, Two *ResourceState + }{ + // Different types + { + false, + &ResourceState{Type: "foo"}, + &ResourceState{Type: "bar"}, + }, + + // Different dependencies + { + false, + &ResourceState{Dependencies: []string{"foo"}}, + &ResourceState{Dependencies: []string{"bar"}}, + }, + + { + false, + &ResourceState{Dependencies: []string{"foo", "bar"}}, + &ResourceState{Dependencies: []string{"foo"}}, + }, + + { + true, + &ResourceState{Dependencies: []string{"bar", "foo"}}, + &ResourceState{Dependencies: []string{"foo", "bar"}}, + }, + + // Different primaries + { + false, + &ResourceState{Primary: nil}, + &ResourceState{Primary: &InstanceState{ID: "foo"}}, + }, + + { + true, + &ResourceState{Primary: &InstanceState{ID: "foo"}}, + &ResourceState{Primary: &InstanceState{ID: "foo"}}, + }, + + // Different tainted + { + false, + &ResourceState{ + Primary: &InstanceState{ + ID: "foo", + }, + }, + &ResourceState{ + Primary: &InstanceState{ + ID: "foo", + Tainted: true, + }, + }, + }, + + { + true, + &ResourceState{ + Primary: &InstanceState{ + ID: "foo", + Tainted: true, + }, + }, + &ResourceState{ + Primary: &InstanceState{ + ID: "foo", + Tainted: true, + }, + }, + }, + } + + for i, tc := range cases { + if tc.One.Equal(tc.Two) != tc.Result { + t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) + } + if tc.Two.Equal(tc.One) != tc.Result { + t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) + } + } +} + +func TestResourceStateTaint(t *testing.T) { + cases := map[string]struct { + Input *ResourceState + Output *ResourceState + }{ + "no primary": { + &ResourceState{}, + &ResourceState{}, + }, + + "primary, not tainted": { + &ResourceState{ + Primary: &InstanceState{ID: "foo"}, + }, + &ResourceState{ + Primary: &InstanceState{ + ID: "foo", + Tainted: true, + }, + }, + }, + + "primary, tainted": { + &ResourceState{ + Primary: &InstanceState{ + ID: "foo", + Tainted: true, + }, + }, + &ResourceState{ + Primary: &InstanceState{ + ID: "foo", + Tainted: true, + }, + }, + }, + } + + for k, tc := range cases { + tc.Input.Taint() + if !reflect.DeepEqual(tc.Input, tc.Output) { + t.Fatalf( + "Failure: %s\n\nExpected: %#v\n\nGot: %#v", + k, tc.Output, tc.Input) + } + } +} + +func TestResourceStateUntaint(t *testing.T) { + cases := map[string]struct { + Input *ResourceState + ExpectedOutput *ResourceState + }{ + "no primary, err": { + Input: &ResourceState{}, + ExpectedOutput: &ResourceState{}, + }, + + "primary, not tainted": { + Input: &ResourceState{ + Primary: &InstanceState{ID: "foo"}, + }, + ExpectedOutput: &ResourceState{ + Primary: &InstanceState{ID: "foo"}, + }, + }, + "primary, tainted": { + Input: &ResourceState{ + Primary: &InstanceState{ + ID: "foo", + Tainted: true, + }, + }, + ExpectedOutput: &ResourceState{ + Primary: &InstanceState{ID: "foo"}, + }, + }, + } + + for k, tc := range cases { + tc.Input.Untaint() + if !reflect.DeepEqual(tc.Input, tc.ExpectedOutput) { + t.Fatalf( + "Failure: %s\n\nExpected: %#v\n\nGot: %#v", + k, tc.ExpectedOutput, tc.Input) + } + } +} + +func TestInstanceStateEmpty(t *testing.T) { + cases := map[string]struct { + In *InstanceState + Result bool + }{ + "nil is empty": { + nil, + true, + }, + "non-nil but without ID is empty": { + &InstanceState{}, + true, + }, + "with ID is not empty": { + &InstanceState{ + ID: "i-abc123", + }, + false, + }, + } + + for tn, tc := range cases { + if tc.In.Empty() != tc.Result { + t.Fatalf("%q expected %#v to be empty: %#v", tn, tc.In, tc.Result) + } + } +} + +func TestInstanceStateEqual(t *testing.T) { + cases := []struct { + Result bool + One, Two *InstanceState + }{ + // Nils + { + false, + nil, + &InstanceState{}, + }, + + { + false, + &InstanceState{}, + nil, + }, + + // Different IDs + { + false, + &InstanceState{ID: "foo"}, + &InstanceState{ID: "bar"}, + }, + + // Different Attributes + { + false, + &InstanceState{Attributes: map[string]string{"foo": "bar"}}, + &InstanceState{Attributes: map[string]string{"foo": "baz"}}, + }, + + // Different Attribute keys + { + false, + &InstanceState{Attributes: map[string]string{"foo": "bar"}}, + &InstanceState{Attributes: map[string]string{"bar": "baz"}}, + }, + + { + false, + &InstanceState{Attributes: map[string]string{"bar": "baz"}}, + &InstanceState{Attributes: map[string]string{"foo": "bar"}}, + }, + } + + for i, tc := range cases { + if tc.One.Equal(tc.Two) != tc.Result { + t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) + } + } +} + +func TestStateEmpty(t *testing.T) { + cases := []struct { + In *State + Result bool + }{ + { + nil, + true, + }, + { + &State{}, + true, + }, + { + &State{ + Remote: &RemoteState{Type: "foo"}, + }, + true, + }, + { + &State{ + Modules: []*ModuleState{ + &ModuleState{}, + }, + }, + false, + }, + } + + for i, tc := range cases { + if tc.In.Empty() != tc.Result { + t.Fatalf("bad %d %#v:\n\n%#v", i, tc.Result, tc.In) + } + } +} + +func TestStateHasResources(t *testing.T) { + cases := []struct { + In *State + Result bool + }{ + { + nil, + false, + }, + { + &State{}, + false, + }, + { + &State{ + Remote: &RemoteState{Type: "foo"}, + }, + false, + }, + { + &State{ + Modules: []*ModuleState{ + &ModuleState{}, + }, + }, + false, + }, + { + &State{ + Modules: []*ModuleState{ + &ModuleState{}, + &ModuleState{}, + }, + }, + false, + }, + { + &State{ + Modules: []*ModuleState{ + &ModuleState{}, + &ModuleState{ + Resources: map[string]*ResourceState{ + "foo.foo": &ResourceState{}, + }, + }, + }, + }, + true, + }, + } + + for i, tc := range cases { + if tc.In.HasResources() != tc.Result { + t.Fatalf("bad %d %#v:\n\n%#v", i, tc.Result, tc.In) + } + } +} + +func TestStateFromFutureTerraform(t *testing.T) { + cases := []struct { + In string + Result bool + }{ + { + "", + false, + }, + { + "0.1", + false, + }, + { + "999.15.1", + true, + }, + } + + for _, tc := range cases { + state := &State{TFVersion: tc.In} + actual := state.FromFutureTerraform() + if actual != tc.Result { + t.Fatalf("%s: bad: %v", tc.In, actual) + } + } +} + +func TestStateIsRemote(t *testing.T) { + cases := []struct { + In *State + Result bool + }{ + { + nil, + false, + }, + { + &State{}, + false, + }, + { + &State{ + Remote: &RemoteState{Type: "foo"}, + }, + true, + }, + } + + for i, tc := range cases { + if tc.In.IsRemote() != tc.Result { + t.Fatalf("bad %d %#v:\n\n%#v", i, tc.Result, tc.In) + } + } +} + +func TestInstanceState_MergeDiff(t *testing.T) { + is := InstanceState{ + ID: "foo", + Attributes: map[string]string{ + "foo": "bar", + "port": "8000", + }, + } + + diff := &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{ + Old: "bar", + New: "baz", + }, + "bar": &ResourceAttrDiff{ + Old: "", + New: "foo", + }, + "baz": &ResourceAttrDiff{ + Old: "", + New: "foo", + NewComputed: true, + }, + "port": &ResourceAttrDiff{ + NewRemoved: true, + }, + }, + } + + is2 := is.MergeDiff(diff) + + expected := map[string]string{ + "foo": "baz", + "bar": "foo", + "baz": hcl2shim.UnknownVariableValue, + } + + if !reflect.DeepEqual(expected, is2.Attributes) { + t.Fatalf("bad: %#v", is2.Attributes) + } +} + +// GH-12183. This tests that a list with a computed set generates the +// right partial state. This never failed but is put here for completion +// of the test case for GH-12183. +func TestInstanceState_MergeDiff_computedSet(t *testing.T) { + is := InstanceState{} + + diff := &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "config.#": &ResourceAttrDiff{ + Old: "0", + New: "1", + RequiresNew: true, + }, + + "config.0.name": &ResourceAttrDiff{ + Old: "", + New: "hello", + }, + + "config.0.rules.#": &ResourceAttrDiff{ + Old: "", + NewComputed: true, + }, + }, + } + + is2 := is.MergeDiff(diff) + + expected := map[string]string{ + "config.#": "1", + "config.0.name": "hello", + "config.0.rules.#": hcl2shim.UnknownVariableValue, + } + + if !reflect.DeepEqual(expected, is2.Attributes) { + t.Fatalf("bad: %#v", is2.Attributes) + } +} + +func TestInstanceState_MergeDiff_nil(t *testing.T) { + var is *InstanceState + + diff := &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo": &ResourceAttrDiff{ + Old: "", + New: "baz", + }, + }, + } + + is2 := is.MergeDiff(diff) + + expected := map[string]string{ + "foo": "baz", + } + + if !reflect.DeepEqual(expected, is2.Attributes) { + t.Fatalf("bad: %#v", is2.Attributes) + } +} + +func TestInstanceState_MergeDiff_nilDiff(t *testing.T) { + is := InstanceState{ + ID: "foo", + Attributes: map[string]string{ + "foo": "bar", + }, + } + + is2 := is.MergeDiff(nil) + + expected := map[string]string{ + "foo": "bar", + } + + if !reflect.DeepEqual(expected, is2.Attributes) { + t.Fatalf("bad: %#v", is2.Attributes) + } +} + +func TestReadWriteState(t *testing.T) { + state := &State{ + Serial: 9, + Lineage: "5d1ad1a1-4027-4665-a908-dbe6adff11d8", + Remote: &RemoteState{ + Type: "http", + Config: map[string]string{ + "url": "http://my-cool-server.com/", + }, + }, + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Dependencies: []string{ + "aws_instance.bar", + }, + Resources: map[string]*ResourceState{ + "foo": &ResourceState{ + Primary: &InstanceState{ + ID: "bar", + Ephemeral: EphemeralState{ + ConnInfo: map[string]string{ + "type": "ssh", + "user": "root", + "password": "supersecret", + }, + }, + }, + }, + }, + }, + }, + } + state.init() + + buf := new(bytes.Buffer) + if err := WriteState(state, buf); err != nil { + t.Fatalf("err: %s", err) + } + + // Verify that the version and serial are set + if state.Version != StateVersion { + t.Fatalf("bad version number: %d", state.Version) + } + + actual, err := ReadState(buf) + if err != nil { + t.Fatalf("err: %s", err) + } + + // ReadState should not restore sensitive information! + mod := state.RootModule() + mod.Resources["foo"].Primary.Ephemeral = EphemeralState{} + mod.Resources["foo"].Primary.Ephemeral.init() + + if !reflect.DeepEqual(actual, state) { + t.Logf("expected:\n%#v", state) + t.Fatalf("got:\n%#v", actual) + } +} + +func TestReadStateNewVersion(t *testing.T) { + type out struct { + Version int + } + + buf, err := json.Marshal(&out{StateVersion + 1}) + if err != nil { + t.Fatalf("err: %v", err) + } + + s, err := ReadState(bytes.NewReader(buf)) + if s != nil { + t.Fatalf("unexpected: %#v", s) + } + if !strings.Contains(err.Error(), "does not support state version") { + t.Fatalf("err: %v", err) + } +} + +func TestReadStateEmptyOrNilFile(t *testing.T) { + var emptyState bytes.Buffer + _, err := ReadState(&emptyState) + if err != ErrNoState { + t.Fatal("expected ErrNostate, got", err) + } + + var nilFile *os.File + _, err = ReadState(nilFile) + if err != ErrNoState { + t.Fatal("expected ErrNostate, got", err) + } +} + +func TestReadStateTFVersion(t *testing.T) { + type tfVersion struct { + Version int `json:"version"` + TFVersion string `json:"terraform_version"` + } + + cases := []struct { + Written string + Read string + Err bool + }{ + { + "0.0.0", + "0.0.0", + false, + }, + { + "", + "", + false, + }, + { + "bad", + "", + true, + }, + } + + for _, tc := range cases { + buf, err := json.Marshal(&tfVersion{ + Version: 2, + TFVersion: tc.Written, + }) + if err != nil { + t.Fatalf("err: %v", err) + } + + s, err := ReadState(bytes.NewReader(buf)) + if (err != nil) != tc.Err { + t.Fatalf("%s: err: %s", tc.Written, err) + } + if err != nil { + continue + } + + if s.TFVersion != tc.Read { + t.Fatalf("%s: bad: %s", tc.Written, s.TFVersion) + } + } +} + +func TestWriteStateTFVersion(t *testing.T) { + cases := []struct { + Write string + Read string + Err bool + }{ + { + "0.0.0", + "0.0.0", + false, + }, + { + "", + "", + false, + }, + { + "bad", + "", + true, + }, + } + + for _, tc := range cases { + var buf bytes.Buffer + err := WriteState(&State{TFVersion: tc.Write}, &buf) + if (err != nil) != tc.Err { + t.Fatalf("%s: err: %s", tc.Write, err) + } + if err != nil { + continue + } + + s, err := ReadState(&buf) + if err != nil { + t.Fatalf("%s: err: %s", tc.Write, err) + } + + if s.TFVersion != tc.Read { + t.Fatalf("%s: bad: %s", tc.Write, s.TFVersion) + } + } +} + +func TestParseResourceStateKey(t *testing.T) { + cases := []struct { + Input string + Expected *ResourceStateKey + ExpectedErr bool + }{ + { + Input: "aws_instance.foo.3", + Expected: &ResourceStateKey{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + Index: 3, + }, + }, + { + Input: "aws_instance.foo.0", + Expected: &ResourceStateKey{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + Index: 0, + }, + }, + { + Input: "aws_instance.foo", + Expected: &ResourceStateKey{ + Mode: ManagedResourceMode, + Type: "aws_instance", + Name: "foo", + Index: -1, + }, + }, + { + Input: "data.aws_ami.foo", + Expected: &ResourceStateKey{ + Mode: DataResourceMode, + Type: "aws_ami", + Name: "foo", + Index: -1, + }, + }, + { + Input: "aws_instance.foo.malformed", + ExpectedErr: true, + }, + { + Input: "aws_instance.foo.malformedwithnumber.123", + ExpectedErr: true, + }, + { + Input: "malformed", + ExpectedErr: true, + }, + } + for _, tc := range cases { + rsk, err := ParseResourceStateKey(tc.Input) + if rsk != nil && tc.Expected != nil && !rsk.Equal(tc.Expected) { + t.Fatalf("%s: expected %s, got %s", tc.Input, tc.Expected, rsk) + } + if (err != nil) != tc.ExpectedErr { + t.Fatalf("%s: expected err: %t, got %s", tc.Input, tc.ExpectedErr, err) + } + } +} + +func TestReadState_prune(t *testing.T) { + state := &State{ + Modules: []*ModuleState{ + &ModuleState{Path: rootModulePath}, + nil, + }, + } + state.init() + + buf := new(bytes.Buffer) + if err := WriteState(state, buf); err != nil { + t.Fatalf("err: %s", err) + } + + actual, err := ReadState(buf) + if err != nil { + t.Fatalf("err: %s", err) + } + + expected := &State{ + Version: state.Version, + Lineage: state.Lineage, + } + expected.init() + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("got:\n%#v", actual) + } +} + +func TestReadState_pruneDependencies(t *testing.T) { + state := &State{ + Serial: 9, + Lineage: "5d1ad1a1-4027-4665-a908-dbe6adff11d8", + Remote: &RemoteState{ + Type: "http", + Config: map[string]string{ + "url": "http://my-cool-server.com/", + }, + }, + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Dependencies: []string{ + "aws_instance.bar", + "aws_instance.bar", + }, + Resources: map[string]*ResourceState{ + "foo": &ResourceState{ + Dependencies: []string{ + "aws_instance.baz", + "aws_instance.baz", + }, + Primary: &InstanceState{ + ID: "bar", + }, + }, + }, + }, + }, + } + state.init() + + buf := new(bytes.Buffer) + if err := WriteState(state, buf); err != nil { + t.Fatalf("err: %s", err) + } + + actual, err := ReadState(buf) + if err != nil { + t.Fatalf("err: %s", err) + } + + // make sure the duplicate Dependencies are filtered + modDeps := actual.Modules[0].Dependencies + resourceDeps := actual.Modules[0].Resources["foo"].Dependencies + + if len(modDeps) > 1 || modDeps[0] != "aws_instance.bar" { + t.Fatalf("expected 1 module depends_on entry, got %q", modDeps) + } + + if len(resourceDeps) > 1 || resourceDeps[0] != "aws_instance.baz" { + t.Fatalf("expected 1 resource depends_on entry, got %q", resourceDeps) + } +} + +func TestReadState_bigHash(t *testing.T) { + expected := uint64(14885267135666261723) + s := strings.NewReader(`{"version": 3, "backend":{"hash":14885267135666261723}}`) + + actual, err := ReadState(s) + if err != nil { + t.Fatal(err) + } + + if actual.Backend.Hash != expected { + t.Fatalf("expected backend hash %d, got %d", expected, actual.Backend.Hash) + } +} + +func TestResourceNameSort(t *testing.T) { + names := []string{ + "a", + "b", + "a.0", + "a.c", + "a.d", + "c", + "a.b.0", + "a.b.1", + "a.b.10", + "a.b.2", + } + + sort.Sort(resourceNameSort(names)) + + expected := []string{ + "a", + "a.0", + "a.b.0", + "a.b.1", + "a.b.2", + "a.b.10", + "a.c", + "a.d", + "b", + "c", + } + + if !reflect.DeepEqual(names, expected) { + t.Fatalf("got: %q\nexpected: %q\n", names, expected) + } +} diff --git a/internal/legacy/terraform/state_upgrade_v1_to_v2.go b/internal/legacy/terraform/state_upgrade_v1_to_v2.go new file mode 100644 index 000000000..aa13cce80 --- /dev/null +++ b/internal/legacy/terraform/state_upgrade_v1_to_v2.go @@ -0,0 +1,189 @@ +package terraform + +import ( + "fmt" + + "github.com/mitchellh/copystructure" +) + +// upgradeStateV1ToV2 is used to upgrade a V1 state representation +// into a V2 state representation +func upgradeStateV1ToV2(old *stateV1) (*State, error) { + if old == nil { + return nil, nil + } + + remote, err := old.Remote.upgradeToV2() + if err != nil { + return nil, fmt.Errorf("Error upgrading State V1: %v", err) + } + + modules := make([]*ModuleState, len(old.Modules)) + for i, module := range old.Modules { + upgraded, err := module.upgradeToV2() + if err != nil { + return nil, fmt.Errorf("Error upgrading State V1: %v", err) + } + modules[i] = upgraded + } + if len(modules) == 0 { + modules = nil + } + + newState := &State{ + Version: 2, + Serial: old.Serial, + Remote: remote, + Modules: modules, + } + + newState.sort() + newState.init() + + return newState, nil +} + +func (old *remoteStateV1) upgradeToV2() (*RemoteState, error) { + if old == nil { + return nil, nil + } + + config, err := copystructure.Copy(old.Config) + if err != nil { + return nil, fmt.Errorf("Error upgrading RemoteState V1: %v", err) + } + + return &RemoteState{ + Type: old.Type, + Config: config.(map[string]string), + }, nil +} + +func (old *moduleStateV1) upgradeToV2() (*ModuleState, error) { + if old == nil { + return nil, nil + } + + pathRaw, err := copystructure.Copy(old.Path) + if err != nil { + return nil, fmt.Errorf("Error upgrading ModuleState V1: %v", err) + } + path, ok := pathRaw.([]string) + if !ok { + return nil, fmt.Errorf("Error upgrading ModuleState V1: path is not a list of strings") + } + if len(path) == 0 { + // We found some V1 states with a nil path. Assume root and catch + // duplicate path errors later (as part of Validate). + path = rootModulePath + } + + // Outputs needs upgrading to use the new structure + outputs := make(map[string]*OutputState) + for key, output := range old.Outputs { + outputs[key] = &OutputState{ + Type: "string", + Value: output, + Sensitive: false, + } + } + + resources := make(map[string]*ResourceState) + for key, oldResource := range old.Resources { + upgraded, err := oldResource.upgradeToV2() + if err != nil { + return nil, fmt.Errorf("Error upgrading ModuleState V1: %v", err) + } + resources[key] = upgraded + } + + dependencies, err := copystructure.Copy(old.Dependencies) + if err != nil { + return nil, fmt.Errorf("Error upgrading ModuleState V1: %v", err) + } + + return &ModuleState{ + Path: path, + Outputs: outputs, + Resources: resources, + Dependencies: dependencies.([]string), + }, nil +} + +func (old *resourceStateV1) upgradeToV2() (*ResourceState, error) { + if old == nil { + return nil, nil + } + + dependencies, err := copystructure.Copy(old.Dependencies) + if err != nil { + return nil, fmt.Errorf("Error upgrading ResourceState V1: %v", err) + } + + primary, err := old.Primary.upgradeToV2() + if err != nil { + return nil, fmt.Errorf("Error upgrading ResourceState V1: %v", err) + } + + deposed := make([]*InstanceState, len(old.Deposed)) + for i, v := range old.Deposed { + upgraded, err := v.upgradeToV2() + if err != nil { + return nil, fmt.Errorf("Error upgrading ResourceState V1: %v", err) + } + deposed[i] = upgraded + } + if len(deposed) == 0 { + deposed = nil + } + + return &ResourceState{ + Type: old.Type, + Dependencies: dependencies.([]string), + Primary: primary, + Deposed: deposed, + Provider: old.Provider, + }, nil +} + +func (old *instanceStateV1) upgradeToV2() (*InstanceState, error) { + if old == nil { + return nil, nil + } + + attributes, err := copystructure.Copy(old.Attributes) + if err != nil { + return nil, fmt.Errorf("Error upgrading InstanceState V1: %v", err) + } + ephemeral, err := old.Ephemeral.upgradeToV2() + if err != nil { + return nil, fmt.Errorf("Error upgrading InstanceState V1: %v", err) + } + + meta, err := copystructure.Copy(old.Meta) + if err != nil { + return nil, fmt.Errorf("Error upgrading InstanceState V1: %v", err) + } + + newMeta := make(map[string]interface{}) + for k, v := range meta.(map[string]string) { + newMeta[k] = v + } + + return &InstanceState{ + ID: old.ID, + Attributes: attributes.(map[string]string), + Ephemeral: *ephemeral, + Meta: newMeta, + }, nil +} + +func (old *ephemeralStateV1) upgradeToV2() (*EphemeralState, error) { + connInfo, err := copystructure.Copy(old.ConnInfo) + if err != nil { + return nil, fmt.Errorf("Error upgrading EphemeralState V1: %v", err) + } + return &EphemeralState{ + ConnInfo: connInfo.(map[string]string), + }, nil +} diff --git a/internal/legacy/terraform/state_upgrade_v2_to_v3.go b/internal/legacy/terraform/state_upgrade_v2_to_v3.go new file mode 100644 index 000000000..e52d35fcd --- /dev/null +++ b/internal/legacy/terraform/state_upgrade_v2_to_v3.go @@ -0,0 +1,142 @@ +package terraform + +import ( + "fmt" + "log" + "regexp" + "sort" + "strconv" + "strings" +) + +// The upgrade process from V2 to V3 state does not affect the structure, +// so we do not need to redeclare all of the structs involved - we just +// take a deep copy of the old structure and assert the version number is +// as we expect. +func upgradeStateV2ToV3(old *State) (*State, error) { + new := old.DeepCopy() + + // Ensure the copied version is v2 before attempting to upgrade + if new.Version != 2 { + return nil, fmt.Errorf("Cannot apply v2->v3 state upgrade to " + + "a state which is not version 2.") + } + + // Set the new version number + new.Version = 3 + + // Change the counts for things which look like maps to use the % + // syntax. Remove counts for empty collections - they will be added + // back in later. + for _, module := range new.Modules { + for _, resource := range module.Resources { + // Upgrade Primary + if resource.Primary != nil { + upgradeAttributesV2ToV3(resource.Primary) + } + + // Upgrade Deposed + if resource.Deposed != nil { + for _, deposed := range resource.Deposed { + upgradeAttributesV2ToV3(deposed) + } + } + } + } + + return new, nil +} + +func upgradeAttributesV2ToV3(instanceState *InstanceState) error { + collectionKeyRegexp := regexp.MustCompile(`^(.*\.)#$`) + collectionSubkeyRegexp := regexp.MustCompile(`^([^\.]+)\..*`) + + // Identify the key prefix of anything which is a collection + var collectionKeyPrefixes []string + for key := range instanceState.Attributes { + if submatches := collectionKeyRegexp.FindAllStringSubmatch(key, -1); len(submatches) > 0 { + collectionKeyPrefixes = append(collectionKeyPrefixes, submatches[0][1]) + } + } + sort.Strings(collectionKeyPrefixes) + + log.Printf("[STATE UPGRADE] Detected the following collections in state: %v", collectionKeyPrefixes) + + // This could be rolled into fewer loops, but it is somewhat clearer this way, and will not + // run very often. + for _, prefix := range collectionKeyPrefixes { + // First get the actual keys that belong to this prefix + var potentialKeysMatching []string + for key := range instanceState.Attributes { + if strings.HasPrefix(key, prefix) { + potentialKeysMatching = append(potentialKeysMatching, strings.TrimPrefix(key, prefix)) + } + } + sort.Strings(potentialKeysMatching) + + var actualKeysMatching []string + for _, key := range potentialKeysMatching { + if submatches := collectionSubkeyRegexp.FindAllStringSubmatch(key, -1); len(submatches) > 0 { + actualKeysMatching = append(actualKeysMatching, submatches[0][1]) + } else { + if key != "#" { + actualKeysMatching = append(actualKeysMatching, key) + } + } + } + actualKeysMatching = uniqueSortedStrings(actualKeysMatching) + + // Now inspect the keys in order to determine whether this is most likely to be + // a map, list or set. There is room for error here, so we log in each case. If + // there is no method of telling, we remove the key from the InstanceState in + // order that it will be recreated. Again, this could be rolled into fewer loops + // but we prefer clarity. + + oldCountKey := fmt.Sprintf("%s#", prefix) + + // First, detect "obvious" maps - which have non-numeric keys (mostly). + hasNonNumericKeys := false + for _, key := range actualKeysMatching { + if _, err := strconv.Atoi(key); err != nil { + hasNonNumericKeys = true + } + } + if hasNonNumericKeys { + newCountKey := fmt.Sprintf("%s%%", prefix) + + instanceState.Attributes[newCountKey] = instanceState.Attributes[oldCountKey] + delete(instanceState.Attributes, oldCountKey) + log.Printf("[STATE UPGRADE] Detected %s as a map. Replaced count = %s", + strings.TrimSuffix(prefix, "."), instanceState.Attributes[newCountKey]) + } + + // Now detect empty collections and remove them from state. + if len(actualKeysMatching) == 0 { + delete(instanceState.Attributes, oldCountKey) + log.Printf("[STATE UPGRADE] Detected %s as an empty collection. Removed from state.", + strings.TrimSuffix(prefix, ".")) + } + } + + return nil +} + +// uniqueSortedStrings removes duplicates from a slice of strings and returns +// a sorted slice of the unique strings. +func uniqueSortedStrings(input []string) []string { + uniquemap := make(map[string]struct{}) + for _, str := range input { + uniquemap[str] = struct{}{} + } + + output := make([]string, len(uniquemap)) + + i := 0 + for key := range uniquemap { + output[i] = key + i = i + 1 + } + + sort.Strings(output) + return output +} diff --git a/internal/legacy/terraform/state_v1.go b/internal/legacy/terraform/state_v1.go new file mode 100644 index 000000000..68cffb41b --- /dev/null +++ b/internal/legacy/terraform/state_v1.go @@ -0,0 +1,145 @@ +package terraform + +// stateV1 keeps track of a snapshot state-of-the-world that Terraform +// can use to keep track of what real world resources it is actually +// managing. +// +// stateV1 is _only used for the purposes of backwards compatibility +// and is no longer used in Terraform. +// +// For the upgrade process, see state_upgrade_v1_to_v2.go +type stateV1 struct { + // Version is the protocol version. "1" for a StateV1. + Version int `json:"version"` + + // Serial is incremented on any operation that modifies + // the State file. It is used to detect potentially conflicting + // updates. + Serial int64 `json:"serial"` + + // Remote is used to track the metadata required to + // pull and push state files from a remote storage endpoint. + Remote *remoteStateV1 `json:"remote,omitempty"` + + // Modules contains all the modules in a breadth-first order + Modules []*moduleStateV1 `json:"modules"` +} + +type remoteStateV1 struct { + // Type controls the client we use for the remote state + Type string `json:"type"` + + // Config is used to store arbitrary configuration that + // is type specific + Config map[string]string `json:"config"` +} + +type moduleStateV1 struct { + // Path is the import path from the root module. Modules imports are + // always disjoint, so the path represents amodule tree + Path []string `json:"path"` + + // Outputs declared by the module and maintained for each module + // even though only the root module technically needs to be kept. + // This allows operators to inspect values at the boundaries. + Outputs map[string]string `json:"outputs"` + + // Resources is a mapping of the logically named resource to + // the state of the resource. Each resource may actually have + // N instances underneath, although a user only needs to think + // about the 1:1 case. + Resources map[string]*resourceStateV1 `json:"resources"` + + // Dependencies are a list of things that this module relies on + // existing to remain intact. For example: an module may depend + // on a VPC ID given by an aws_vpc resource. + // + // Terraform uses this information to build valid destruction + // orders and to warn the user if they're destroying a module that + // another resource depends on. + // + // Things can be put into this list that may not be managed by + // Terraform. If Terraform doesn't find a matching ID in the + // overall state, then it assumes it isn't managed and doesn't + // worry about it. + Dependencies []string `json:"depends_on,omitempty"` +} + +type resourceStateV1 struct { + // This is filled in and managed by Terraform, and is the resource + // type itself such as "mycloud_instance". If a resource provider sets + // this value, it won't be persisted. + Type string `json:"type"` + + // Dependencies are a list of things that this resource relies on + // existing to remain intact. For example: an AWS instance might + // depend on a subnet (which itself might depend on a VPC, and so + // on). + // + // Terraform uses this information to build valid destruction + // orders and to warn the user if they're destroying a resource that + // another resource depends on. + // + // Things can be put into this list that may not be managed by + // Terraform. If Terraform doesn't find a matching ID in the + // overall state, then it assumes it isn't managed and doesn't + // worry about it. + Dependencies []string `json:"depends_on,omitempty"` + + // Primary is the current active instance for this resource. + // It can be replaced but only after a successful creation. + // This is the instances on which providers will act. + Primary *instanceStateV1 `json:"primary"` + + // Tainted is used to track any underlying instances that + // have been created but are in a bad or unknown state and + // need to be cleaned up subsequently. In the + // standard case, there is only at most a single instance. + // However, in pathological cases, it is possible for the number + // of instances to accumulate. + Tainted []*instanceStateV1 `json:"tainted,omitempty"` + + // Deposed is used in the mechanics of CreateBeforeDestroy: the existing + // Primary is Deposed to get it out of the way for the replacement Primary to + // be created by Apply. If the replacement Primary creates successfully, the + // Deposed instance is cleaned up. If there were problems creating the + // replacement, the instance remains in the Deposed list so it can be + // destroyed in a future run. Functionally, Deposed instances are very + // similar to Tainted instances in that Terraform is only tracking them in + // order to remember to destroy them. + Deposed []*instanceStateV1 `json:"deposed,omitempty"` + + // Provider is used when a resource is connected to a provider with an alias. + // If this string is empty, the resource is connected to the default provider, + // e.g. "aws_instance" goes with the "aws" provider. + // If the resource block contained a "provider" key, that value will be set here. + Provider string `json:"provider,omitempty"` +} + +type instanceStateV1 struct { + // A unique ID for this resource. This is opaque to Terraform + // and is only meant as a lookup mechanism for the providers. + ID string `json:"id"` + + // Attributes are basic information about the resource. Any keys here + // are accessible in variable format within Terraform configurations: + // ${resourcetype.name.attribute}. + Attributes map[string]string `json:"attributes,omitempty"` + + // Ephemeral is used to store any state associated with this instance + // that is necessary for the Terraform run to complete, but is not + // persisted to a state file. + Ephemeral ephemeralStateV1 `json:"-"` + + // Meta is a simple K/V map that is persisted to the State but otherwise + // ignored by Terraform core. It's meant to be used for accounting by + // external client code. + Meta map[string]string `json:"meta,omitempty"` +} + +type ephemeralStateV1 struct { + // ConnInfo is used for the providers to export information which is + // used to connect to the resource for provisioning. For example, + // this could contain SSH or WinRM credentials. + ConnInfo map[string]string `json:"-"` +} diff --git a/internal/legacy/terraform/testing.go b/internal/legacy/terraform/testing.go new file mode 100644 index 000000000..3f0418d92 --- /dev/null +++ b/internal/legacy/terraform/testing.go @@ -0,0 +1,19 @@ +package terraform + +import ( + "os" + "testing" +) + +// TestStateFile writes the given state to the path. +func TestStateFile(t *testing.T, path string, state *State) { + f, err := os.Create(path) + if err != nil { + t.Fatalf("err: %s", err) + } + defer f.Close() + + if err := WriteState(state, f); err != nil { + t.Fatalf("err: %s", err) + } +} diff --git a/internal/legacy/terraform/ui_input.go b/internal/legacy/terraform/ui_input.go new file mode 100644 index 000000000..688bcf71e --- /dev/null +++ b/internal/legacy/terraform/ui_input.go @@ -0,0 +1,32 @@ +package terraform + +import "context" + +// UIInput is the interface that must be implemented to ask for input +// from this user. This should forward the request to wherever the user +// inputs things to ask for values. +type UIInput interface { + Input(context.Context, *InputOpts) (string, error) +} + +// InputOpts are options for asking for input. +type InputOpts struct { + // Id is a unique ID for the question being asked that might be + // used for logging or to look up a prior answered question. + Id string + + // Query is a human-friendly question for inputting this value. + Query string + + // Description is a description about what this option is. Be wary + // that this will probably be in a terminal so split lines as you see + // necessary. + Description string + + // Default will be the value returned if no data is entered. + Default string + + // Secret should be true if we are asking for sensitive input. + // If attached to a TTY, Terraform will disable echo. + Secret bool +} diff --git a/internal/legacy/terraform/ui_input_mock.go b/internal/legacy/terraform/ui_input_mock.go new file mode 100644 index 000000000..e2d9c3848 --- /dev/null +++ b/internal/legacy/terraform/ui_input_mock.go @@ -0,0 +1,25 @@ +package terraform + +import "context" + +// MockUIInput is an implementation of UIInput that can be used for tests. +type MockUIInput struct { + InputCalled bool + InputOpts *InputOpts + InputReturnMap map[string]string + InputReturnString string + InputReturnError error + InputFn func(*InputOpts) (string, error) +} + +func (i *MockUIInput) Input(ctx context.Context, opts *InputOpts) (string, error) { + i.InputCalled = true + i.InputOpts = opts + if i.InputFn != nil { + return i.InputFn(opts) + } + if i.InputReturnMap != nil { + return i.InputReturnMap[opts.Id], i.InputReturnError + } + return i.InputReturnString, i.InputReturnError +} diff --git a/internal/legacy/terraform/ui_input_prefix.go b/internal/legacy/terraform/ui_input_prefix.go new file mode 100644 index 000000000..b5d32b1e8 --- /dev/null +++ b/internal/legacy/terraform/ui_input_prefix.go @@ -0,0 +1,20 @@ +package terraform + +import ( + "context" + "fmt" +) + +// PrefixUIInput is an implementation of UIInput that prefixes the ID +// with a string, allowing queries to be namespaced. +type PrefixUIInput struct { + IdPrefix string + QueryPrefix string + UIInput UIInput +} + +func (i *PrefixUIInput) Input(ctx context.Context, opts *InputOpts) (string, error) { + opts.Id = fmt.Sprintf("%s.%s", i.IdPrefix, opts.Id) + opts.Query = fmt.Sprintf("%s%s", i.QueryPrefix, opts.Query) + return i.UIInput.Input(ctx, opts) +} diff --git a/internal/legacy/terraform/ui_input_prefix_test.go b/internal/legacy/terraform/ui_input_prefix_test.go new file mode 100644 index 000000000..dff42c39c --- /dev/null +++ b/internal/legacy/terraform/ui_input_prefix_test.go @@ -0,0 +1,27 @@ +package terraform + +import ( + "context" + "testing" +) + +func TestPrefixUIInput_impl(t *testing.T) { + var _ UIInput = new(PrefixUIInput) +} + +func TestPrefixUIInput(t *testing.T) { + input := new(MockUIInput) + prefix := &PrefixUIInput{ + IdPrefix: "foo", + UIInput: input, + } + + _, err := prefix.Input(context.Background(), &InputOpts{Id: "bar"}) + if err != nil { + t.Fatalf("err: %s", err) + } + + if input.InputOpts.Id != "foo.bar" { + t.Fatalf("bad: %#v", input.InputOpts) + } +} diff --git a/internal/legacy/terraform/ui_output.go b/internal/legacy/terraform/ui_output.go new file mode 100644 index 000000000..84427c63d --- /dev/null +++ b/internal/legacy/terraform/ui_output.go @@ -0,0 +1,7 @@ +package terraform + +// UIOutput is the interface that must be implemented to output +// data to the end user. +type UIOutput interface { + Output(string) +} diff --git a/internal/legacy/terraform/ui_output_callback.go b/internal/legacy/terraform/ui_output_callback.go new file mode 100644 index 000000000..135a91c5f --- /dev/null +++ b/internal/legacy/terraform/ui_output_callback.go @@ -0,0 +1,9 @@ +package terraform + +type CallbackUIOutput struct { + OutputFn func(string) +} + +func (o *CallbackUIOutput) Output(v string) { + o.OutputFn(v) +} diff --git a/internal/legacy/terraform/ui_output_callback_test.go b/internal/legacy/terraform/ui_output_callback_test.go new file mode 100644 index 000000000..1dd5ccddf --- /dev/null +++ b/internal/legacy/terraform/ui_output_callback_test.go @@ -0,0 +1,9 @@ +package terraform + +import ( + "testing" +) + +func TestCallbackUIOutput_impl(t *testing.T) { + var _ UIOutput = new(CallbackUIOutput) +} diff --git a/internal/legacy/terraform/ui_output_mock.go b/internal/legacy/terraform/ui_output_mock.go new file mode 100644 index 000000000..d828c921c --- /dev/null +++ b/internal/legacy/terraform/ui_output_mock.go @@ -0,0 +1,21 @@ +package terraform + +import "sync" + +// MockUIOutput is an implementation of UIOutput that can be used for tests. +type MockUIOutput struct { + sync.Mutex + OutputCalled bool + OutputMessage string + OutputFn func(string) +} + +func (o *MockUIOutput) Output(v string) { + o.Lock() + defer o.Unlock() + o.OutputCalled = true + o.OutputMessage = v + if o.OutputFn != nil { + o.OutputFn(v) + } +} diff --git a/internal/legacy/terraform/ui_output_mock_test.go b/internal/legacy/terraform/ui_output_mock_test.go new file mode 100644 index 000000000..0a23c2e23 --- /dev/null +++ b/internal/legacy/terraform/ui_output_mock_test.go @@ -0,0 +1,9 @@ +package terraform + +import ( + "testing" +) + +func TestMockUIOutput(t *testing.T) { + var _ UIOutput = new(MockUIOutput) +} diff --git a/internal/legacy/terraform/upgrade_state_v1_test.go b/internal/legacy/terraform/upgrade_state_v1_test.go new file mode 100644 index 000000000..93e03acca --- /dev/null +++ b/internal/legacy/terraform/upgrade_state_v1_test.go @@ -0,0 +1,190 @@ +package terraform + +import ( + "bytes" + "reflect" + "strings" + "testing" + + "github.com/davecgh/go-spew/spew" +) + +// TestReadUpgradeStateV1toV3 tests the state upgrade process from the V1 state +// to the current version, and needs editing each time. This means it tests the +// entire pipeline of upgrades (which migrate version to version). +func TestReadUpgradeStateV1toV3(t *testing.T) { + // ReadState should transparently detect the old version but will upgrade + // it on Write. + actual, err := ReadState(strings.NewReader(testV1State)) + if err != nil { + t.Fatalf("err: %s", err) + } + + buf := new(bytes.Buffer) + if err := WriteState(actual, buf); err != nil { + t.Fatalf("err: %s", err) + } + + if actual.Version != 3 { + t.Fatalf("bad: State version not incremented; is %d", actual.Version) + } + + roundTripped, err := ReadState(buf) + if err != nil { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(actual, roundTripped) { + t.Logf("actual:\n%#v", actual) + t.Fatalf("roundTripped:\n%#v", roundTripped) + } +} + +func TestReadUpgradeStateV1toV3_outputs(t *testing.T) { + // ReadState should transparently detect the old version but will upgrade + // it on Write. + actual, err := ReadState(strings.NewReader(testV1StateWithOutputs)) + if err != nil { + t.Fatalf("err: %s", err) + } + + buf := new(bytes.Buffer) + if err := WriteState(actual, buf); err != nil { + t.Fatalf("err: %s", err) + } + + if actual.Version != 3 { + t.Fatalf("bad: State version not incremented; is %d", actual.Version) + } + + roundTripped, err := ReadState(buf) + if err != nil { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(actual, roundTripped) { + spew.Config.DisableMethods = true + t.Fatalf("bad:\n%s\n\nround tripped:\n%s\n", spew.Sdump(actual), spew.Sdump(roundTripped)) + spew.Config.DisableMethods = false + } +} + +// Upgrading the state should not lose empty module Outputs and Resources maps +// during upgrade. The init for a new module initializes new maps, so we may not +// be expecting to check for a nil map. +func TestReadUpgradeStateV1toV3_emptyState(t *testing.T) { + // ReadState should transparently detect the old version but will upgrade + // it on Write. + orig, err := ReadStateV1([]byte(testV1EmptyState)) + if err != nil { + t.Fatalf("err: %s", err) + } + + stateV2, err := upgradeStateV1ToV2(orig) + if err != nil { + t.Fatalf("error attempting upgradeStateV1ToV2: %s", err) + } + + for _, m := range stateV2.Modules { + if m.Resources == nil { + t.Fatal("V1 to V2 upgrade lost module.Resources") + } + if m.Outputs == nil { + t.Fatal("V1 to V2 upgrade lost module.Outputs") + } + } + + stateV3, err := upgradeStateV2ToV3(stateV2) + if err != nil { + t.Fatalf("error attempting to upgradeStateV2ToV3: %s", err) + } + for _, m := range stateV3.Modules { + if m.Resources == nil { + t.Fatal("V2 to V3 upgrade lost module.Resources") + } + if m.Outputs == nil { + t.Fatal("V2 to V3 upgrade lost module.Outputs") + } + } + +} + +const testV1EmptyState = `{ + "version": 1, + "serial": 0, + "modules": [ + { + "path": [ + "root" + ], + "outputs": {}, + "resources": {} + } + ] +} +` + +const testV1State = `{ + "version": 1, + "serial": 9, + "remote": { + "type": "http", + "config": { + "url": "http://my-cool-server.com/" + } + }, + "modules": [ + { + "path": [ + "root" + ], + "outputs": null, + "resources": { + "foo": { + "type": "", + "primary": { + "id": "bar" + } + } + }, + "depends_on": [ + "aws_instance.bar" + ] + } + ] +} +` + +const testV1StateWithOutputs = `{ + "version": 1, + "serial": 9, + "remote": { + "type": "http", + "config": { + "url": "http://my-cool-server.com/" + } + }, + "modules": [ + { + "path": [ + "root" + ], + "outputs": { + "foo": "bar", + "baz": "foo" + }, + "resources": { + "foo": { + "type": "", + "primary": { + "id": "bar" + } + } + }, + "depends_on": [ + "aws_instance.bar" + ] + } + ] +} +` diff --git a/internal/legacy/terraform/upgrade_state_v2_test.go b/internal/legacy/terraform/upgrade_state_v2_test.go new file mode 100644 index 000000000..546d74968 --- /dev/null +++ b/internal/legacy/terraform/upgrade_state_v2_test.go @@ -0,0 +1,202 @@ +package terraform + +import ( + "bytes" + "strings" + "testing" +) + +// TestReadUpgradeStateV2toV3 tests the state upgrade process from the V2 state +// to the current version, and needs editing each time. This means it tests the +// entire pipeline of upgrades (which migrate version to version). +func TestReadUpgradeStateV2toV3(t *testing.T) { + // ReadState should transparently detect the old version but will upgrade + // it on Write. + upgraded, err := ReadState(strings.NewReader(testV2State)) + if err != nil { + t.Fatalf("err: %s", err) + } + + buf := new(bytes.Buffer) + if err := WriteState(upgraded, buf); err != nil { + t.Fatalf("err: %s", err) + } + + if upgraded.Version != 3 { + t.Fatalf("bad: State version not incremented; is %d", upgraded.Version) + } + + // For this test we cannot assert that we match the round trip because an + // empty map has been removed from state. Instead we make assertions against + // some of the key fields in the _upgraded_ state. + instanceState, ok := upgraded.RootModule().Resources["test_resource.main"] + if !ok { + t.Fatalf("Instance state for test_resource.main was removed from state during upgrade") + } + + primary := instanceState.Primary + if primary == nil { + t.Fatalf("Primary instance was removed from state for test_resource.main") + } + + // Non-empty computed map is moved from .# to .% + if _, ok := primary.Attributes["computed_map.#"]; ok { + t.Fatalf("Count was not upgraded from .# to .%% for computed_map") + } + if count, ok := primary.Attributes["computed_map.%"]; !ok || count != "1" { + t.Fatalf("Count was not in .%% or was not 2 for computed_map") + } + + // list_of_map top level retains .# + if count, ok := primary.Attributes["list_of_map.#"]; !ok || count != "2" { + t.Fatal("Count for list_of_map was migrated incorrectly") + } + + // list_of_map.0 is moved from .# to .% + if _, ok := primary.Attributes["list_of_map.0.#"]; ok { + t.Fatalf("Count was not upgraded from .# to .%% for list_of_map.0") + } + if count, ok := primary.Attributes["list_of_map.0.%"]; !ok || count != "2" { + t.Fatalf("Count was not in .%% or was not 2 for list_of_map.0") + } + + // list_of_map.1 is moved from .# to .% + if _, ok := primary.Attributes["list_of_map.1.#"]; ok { + t.Fatalf("Count was not upgraded from .# to .%% for list_of_map.1") + } + if count, ok := primary.Attributes["list_of_map.1.%"]; !ok || count != "2" { + t.Fatalf("Count was not in .%% or was not 2 for list_of_map.1") + } + + // map is moved from .# to .% + if _, ok := primary.Attributes["map.#"]; ok { + t.Fatalf("Count was not upgraded from .# to .%% for map") + } + if count, ok := primary.Attributes["map.%"]; !ok || count != "2" { + t.Fatalf("Count was not in .%% or was not 2 for map") + } + + // optional_computed_map should be removed from state + if _, ok := primary.Attributes["optional_computed_map"]; ok { + t.Fatal("optional_computed_map was not removed from state") + } + + // required_map is moved from .# to .% + if _, ok := primary.Attributes["required_map.#"]; ok { + t.Fatalf("Count was not upgraded from .# to .%% for required_map") + } + if count, ok := primary.Attributes["required_map.%"]; !ok || count != "3" { + t.Fatalf("Count was not in .%% or was not 3 for map") + } + + // computed_list keeps .# + if count, ok := primary.Attributes["computed_list.#"]; !ok || count != "2" { + t.Fatal("Count was migrated incorrectly for computed_list") + } + + // computed_set keeps .# + if count, ok := primary.Attributes["computed_set.#"]; !ok || count != "2" { + t.Fatal("Count was migrated incorrectly for computed_set") + } + if val, ok := primary.Attributes["computed_set.2337322984"]; !ok || val != "setval1" { + t.Fatal("Set item for computed_set.2337322984 changed or moved") + } + if val, ok := primary.Attributes["computed_set.307881554"]; !ok || val != "setval2" { + t.Fatal("Set item for computed_set.307881554 changed or moved") + } + + // string properties are unaffected + if val, ok := primary.Attributes["id"]; !ok || val != "testId" { + t.Fatal("id was not set correctly after migration") + } +} + +const testV2State = `{ + "version": 2, + "terraform_version": "0.7.0", + "serial": 2, + "modules": [ + { + "path": [ + "root" + ], + "outputs": { + "computed_map": { + "sensitive": false, + "type": "map", + "value": { + "key1": "value1" + } + }, + "computed_set": { + "sensitive": false, + "type": "list", + "value": [ + "setval1", + "setval2" + ] + }, + "map": { + "sensitive": false, + "type": "map", + "value": { + "key": "test", + "test": "test" + } + }, + "set": { + "sensitive": false, + "type": "list", + "value": [ + "test1", + "test2" + ] + } + }, + "resources": { + "test_resource.main": { + "type": "test_resource", + "primary": { + "id": "testId", + "attributes": { + "computed_list.#": "2", + "computed_list.0": "listval1", + "computed_list.1": "listval2", + "computed_map.#": "1", + "computed_map.key1": "value1", + "computed_read_only": "value_from_api", + "computed_read_only_force_new": "value_from_api", + "computed_set.#": "2", + "computed_set.2337322984": "setval1", + "computed_set.307881554": "setval2", + "id": "testId", + "list_of_map.#": "2", + "list_of_map.0.#": "2", + "list_of_map.0.key1": "value1", + "list_of_map.0.key2": "value2", + "list_of_map.1.#": "2", + "list_of_map.1.key3": "value3", + "list_of_map.1.key4": "value4", + "map.#": "2", + "map.key": "test", + "map.test": "test", + "map_that_look_like_set.#": "2", + "map_that_look_like_set.12352223": "hello", + "map_that_look_like_set.36234341": "world", + "optional_computed_map.#": "0", + "required": "Hello World", + "required_map.#": "3", + "required_map.key1": "value1", + "required_map.key2": "value2", + "required_map.key3": "value3", + "set.#": "2", + "set.2326977762": "test1", + "set.331058520": "test2" + } + } + } + } + } + ] +} +` diff --git a/internal/legacy/terraform/util.go b/internal/legacy/terraform/util.go new file mode 100644 index 000000000..7966b58dd --- /dev/null +++ b/internal/legacy/terraform/util.go @@ -0,0 +1,75 @@ +package terraform + +import ( + "sort" +) + +// Semaphore is a wrapper around a channel to provide +// utility methods to clarify that we are treating the +// channel as a semaphore +type Semaphore chan struct{} + +// NewSemaphore creates a semaphore that allows up +// to a given limit of simultaneous acquisitions +func NewSemaphore(n int) Semaphore { + if n <= 0 { + panic("semaphore with limit <=0") + } + ch := make(chan struct{}, n) + return Semaphore(ch) +} + +// Acquire is used to acquire an available slot. +// Blocks until available. +func (s Semaphore) Acquire() { + s <- struct{}{} +} + +// TryAcquire is used to do a non-blocking acquire. +// Returns a bool indicating success +func (s Semaphore) TryAcquire() bool { + select { + case s <- struct{}{}: + return true + default: + return false + } +} + +// Release is used to return a slot. Acquire must +// be called as a pre-condition. +func (s Semaphore) Release() { + select { + case <-s: + default: + panic("release without an acquire") + } +} + +// strSliceContains checks if a given string is contained in a slice +// When anybody asks why Go needs generics, here you go. +func strSliceContains(haystack []string, needle string) bool { + for _, s := range haystack { + if s == needle { + return true + } + } + return false +} + +// deduplicate a slice of strings +func uniqueStrings(s []string) []string { + if len(s) < 2 { + return s + } + + sort.Strings(s) + result := make([]string, 1, len(s)) + result[0] = s[0] + for i := 1; i < len(s); i++ { + if s[i] != result[len(result)-1] { + result = append(result, s[i]) + } + } + return result +} diff --git a/internal/legacy/terraform/util_test.go b/internal/legacy/terraform/util_test.go new file mode 100644 index 000000000..8b3907e23 --- /dev/null +++ b/internal/legacy/terraform/util_test.go @@ -0,0 +1,91 @@ +package terraform + +import ( + "fmt" + "reflect" + "testing" + "time" +) + +func TestSemaphore(t *testing.T) { + s := NewSemaphore(2) + timer := time.AfterFunc(time.Second, func() { + panic("deadlock") + }) + defer timer.Stop() + + s.Acquire() + if !s.TryAcquire() { + t.Fatalf("should acquire") + } + if s.TryAcquire() { + t.Fatalf("should not acquire") + } + s.Release() + s.Release() + + // This release should panic + defer func() { + r := recover() + if r == nil { + t.Fatalf("should panic") + } + }() + s.Release() +} + +func TestStrSliceContains(t *testing.T) { + if strSliceContains(nil, "foo") { + t.Fatalf("Bad") + } + if strSliceContains([]string{}, "foo") { + t.Fatalf("Bad") + } + if strSliceContains([]string{"bar"}, "foo") { + t.Fatalf("Bad") + } + if !strSliceContains([]string{"bar", "foo"}, "foo") { + t.Fatalf("Bad") + } +} + +func TestUniqueStrings(t *testing.T) { + cases := []struct { + Input []string + Expected []string + }{ + { + []string{}, + []string{}, + }, + { + []string{"x"}, + []string{"x"}, + }, + { + []string{"a", "b", "c"}, + []string{"a", "b", "c"}, + }, + { + []string{"a", "a", "a"}, + []string{"a"}, + }, + { + []string{"a", "b", "a", "b", "a", "a"}, + []string{"a", "b"}, + }, + { + []string{"c", "b", "a", "c", "b"}, + []string{"a", "b", "c"}, + }, + } + + for i, tc := range cases { + t.Run(fmt.Sprintf("unique-%d", i), func(t *testing.T) { + actual := uniqueStrings(tc.Input) + if !reflect.DeepEqual(tc.Expected, actual) { + t.Fatalf("Expected: %q\nGot: %q", tc.Expected, actual) + } + }) + } +} diff --git a/internal/legacy/terraform/version.go b/internal/legacy/terraform/version.go new file mode 100644 index 000000000..0caeca0ad --- /dev/null +++ b/internal/legacy/terraform/version.go @@ -0,0 +1,10 @@ +package terraform + +import ( + "github.com/hashicorp/terraform/version" +) + +// Deprecated: Providers should use schema.Provider.TerraformVersion instead +func VersionString() string { + return version.String() +} diff --git a/internal/legacy/terraform/version_required.go b/internal/legacy/terraform/version_required.go new file mode 100644 index 000000000..4c9cb34a4 --- /dev/null +++ b/internal/legacy/terraform/version_required.go @@ -0,0 +1,62 @@ +package terraform + +import ( + "fmt" + + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/terraform/tfdiags" + + "github.com/hashicorp/terraform/configs" + + tfversion "github.com/hashicorp/terraform/version" +) + +// CheckCoreVersionRequirements visits each of the modules in the given +// configuration tree and verifies that any given Core version constraints +// match with the version of Terraform Core that is being used. +// +// The returned diagnostics will contain errors if any constraints do not match. +// The returned diagnostics might also return warnings, which should be +// displayed to the user. +func CheckCoreVersionRequirements(config *configs.Config) tfdiags.Diagnostics { + if config == nil { + return nil + } + + var diags tfdiags.Diagnostics + module := config.Module + + for _, constraint := range module.CoreVersionConstraints { + if !constraint.Required.Check(tfversion.SemVer) { + switch { + case len(config.Path) == 0: + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Unsupported Terraform Core version", + Detail: fmt.Sprintf( + "This configuration does not support Terraform version %s. To proceed, either choose another supported Terraform version or update this version constraint. Version constraints are normally set for good reason, so updating the constraint may lead to other errors or unexpected behavior.", + tfversion.String(), + ), + Subject: constraint.DeclRange.Ptr(), + }) + default: + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Unsupported Terraform Core version", + Detail: fmt.Sprintf( + "Module %s (from %s) does not support Terraform version %s. To proceed, either choose another supported Terraform version or update this version constraint. Version constraints are normally set for good reason, so updating the constraint may lead to other errors or unexpected behavior.", + config.Path, config.SourceAddr, tfversion.String(), + ), + Subject: constraint.DeclRange.Ptr(), + }) + } + } + } + + for _, c := range config.Children { + childDiags := CheckCoreVersionRequirements(c) + diags = diags.Append(childDiags) + } + + return diags +} diff --git a/plugin/plugin_test.go b/plugin/plugin_test.go index ddef40ab2..ddcde4c6e 100644 --- a/plugin/plugin_test.go +++ b/plugin/plugin_test.go @@ -1,7 +1,7 @@ package plugin import ( - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) func testProviderFixed(p terraform.ResourceProvider) ProviderFunc { diff --git a/plugin/resource_provider.go b/plugin/resource_provider.go index a9d520581..3ffe45f73 100644 --- a/plugin/resource_provider.go +++ b/plugin/resource_provider.go @@ -4,7 +4,7 @@ import ( "net/rpc" plugin "github.com/hashicorp/go-plugin" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) // ResourceProviderPlugin is the plugin.Plugin implementation. diff --git a/plugin/resource_provider_test.go b/plugin/resource_provider_test.go index 6c3cc4546..36e96f59c 100644 --- a/plugin/resource_provider_test.go +++ b/plugin/resource_provider_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/hashicorp/go-plugin" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) func TestResourceProvider_impl(t *testing.T) { diff --git a/plugin/resource_provisioner.go b/plugin/resource_provisioner.go index 5bebc4c61..2dfc8d1a6 100644 --- a/plugin/resource_provisioner.go +++ b/plugin/resource_provisioner.go @@ -5,7 +5,7 @@ import ( "github.com/hashicorp/go-plugin" "github.com/hashicorp/terraform/configs/configschema" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) // ResourceProvisionerPlugin is the plugin.Plugin implementation. diff --git a/plugin/resource_provisioner_test.go b/plugin/resource_provisioner_test.go index 0ff3ea239..bf00886e4 100644 --- a/plugin/resource_provisioner_test.go +++ b/plugin/resource_provisioner_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/hashicorp/go-plugin" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) func TestResourceProvisioner_impl(t *testing.T) { diff --git a/plugin/serve.go b/plugin/serve.go index 8d056c591..3414eaa23 100644 --- a/plugin/serve.go +++ b/plugin/serve.go @@ -2,9 +2,9 @@ package plugin import ( "github.com/hashicorp/go-plugin" - grpcplugin "github.com/hashicorp/terraform/helper/plugin" + grpcplugin "github.com/hashicorp/terraform/internal/legacy/helper/plugin" + "github.com/hashicorp/terraform/internal/legacy/terraform" proto "github.com/hashicorp/terraform/internal/tfplugin5" - "github.com/hashicorp/terraform/terraform" ) const ( diff --git a/plugin/ui_input.go b/plugin/ui_input.go index 3469e6a96..8343612aa 100644 --- a/plugin/ui_input.go +++ b/plugin/ui_input.go @@ -5,7 +5,7 @@ import ( "net/rpc" "github.com/hashicorp/go-plugin" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) // UIInput is an implementation of terraform.UIInput that communicates diff --git a/plugin/ui_input_test.go b/plugin/ui_input_test.go index c6d7036d1..3829e9e84 100644 --- a/plugin/ui_input_test.go +++ b/plugin/ui_input_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/hashicorp/go-plugin" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) func TestUIInput_impl(t *testing.T) { diff --git a/plugin/ui_output.go b/plugin/ui_output.go index c222b00cd..752c93144 100644 --- a/plugin/ui_output.go +++ b/plugin/ui_output.go @@ -3,7 +3,7 @@ package plugin import ( "net/rpc" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) // UIOutput is an implementatin of terraform.UIOutput that communicates diff --git a/plugin/ui_output_test.go b/plugin/ui_output_test.go index 50eadaa02..43c4ad6fc 100644 --- a/plugin/ui_output_test.go +++ b/plugin/ui_output_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/hashicorp/go-plugin" - "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/terraform/internal/legacy/terraform" ) func TestUIOutput_impl(t *testing.T) {