From 23a8bdd52253163e0bc0405ea9af40511bcfc314 Mon Sep 17 00:00:00 2001 From: Kristin Laemmert Date: Wed, 26 Aug 2020 14:39:18 -0400 Subject: [PATCH] configs: finish deprecation of the config package by removing the remaining used functions into configs (#25996) --- config/append.go | 92 -- config/append_test.go | 173 --- config/config.go | 1171 --------------- config/config_string.go | 378 ----- config/config_terraform.go | 117 -- config/config_terraform_test.go | 55 - config/config_test.go | 783 ---------- config/config_tree.go | 43 - config/import_tree.go | 151 -- config/import_tree_test.go | 81 - config/loader.go | 212 --- config/loader_hcl.go | 1270 ---------------- config/loader_hcl2.go | 473 ------ config/loader_hcl2_test.go | 510 ------- config/loader_hcl_test.go | 9 - config/loader_test.go | 1316 ----------------- config/merge.go | 204 --- config/merge_test.go | 499 ------- config/providers.go | 61 - config/provisioner_enums.go | 40 - config/resource_mode.go | 9 - config/resource_mode_string.go | 24 - config/testdata/.gitattributes | 1 - config/testdata/attributes.tf | 15 - config/testdata/attributes.tf.json | 27 - config/testdata/backend-hash-basic/main.tf | 7 - config/testdata/backend-hash-empty/main.tf | 1 - .../backend-hash-no-terraform/main.tf | 1 - .../testdata/backend-hash-type-only/main.tf | 4 - config/testdata/bad-variable-type.tf | 3 - config/testdata/bad_type.tf.nope | 1 - config/testdata/basic-hcl2.tf | 125 -- config/testdata/basic.tf | 95 -- config/testdata/basic.tf.json | 103 -- config/testdata/connection.tf | 23 - config/testdata/copy-basic/main.tf | 19 - config/testdata/count-int/main.tf | 3 - config/testdata/count-list/main.tf | 3 - config/testdata/count-string/main.tf | 3 - config/testdata/count-var/main.tf | 3 - config/testdata/create-before-destroy.tf | 14 - config/testdata/data-count/main.tf | 3 - config/testdata/data-source-arity-mistake.tf | 3 - config/testdata/dir-basic/README.md | 2 - config/testdata/dir-basic/nested/nested.tf | 3 - config/testdata/dir-basic/one.tf | 21 - config/testdata/dir-basic/two.tf | 24 - config/testdata/dir-empty/.gitkeep | 0 config/testdata/dir-merge/one.tf | 8 - config/testdata/dir-merge/two.tf | 2 - .../dir-only-override/main_override.tf | 4 - config/testdata/dir-override-var/main.tf | 4 - .../dir-override-var/main_override.tf | 3 - .../dir-override/foo_override.tf.json | 16 - config/testdata/dir-override/one.tf | 22 - config/testdata/dir-override/override.tf.json | 10 - config/testdata/dir-override/two.tf | 24 - .../dir-temporary-files/#emacs-two.tf# | 20 - .../dir-temporary-files/.#emacs-one.tf | 17 - .../testdata/dir-temporary-files/.hidden.tf | 0 .../testdata/dir-temporary-files/vim-one.tf~ | 17 - config/testdata/empty-collections/main.tf | 11 - config/testdata/empty.tf | 0 config/testdata/escapedquotes.tf | 7 - config/testdata/git-crypt.tf | Bin 13 -> 0 bytes .../not-eligible.tf.json | 5 - .../hcl2-experiment-switch/not-opted-in.tf | 7 - .../hcl2-experiment-switch/opted-in.tf | 7 - config/testdata/heredoc.tf | 51 - config/testdata/ignore-changes.tf | 17 - config/testdata/import.tf | 10 - config/testdata/import/one.tf | 7 - config/testdata/interpolations/concat.hcl | 1 - config/testdata/lifecycle_cbd_typo.tf | 5 - config/testdata/module-providers/main.tf | 7 - config/testdata/module-unnamed.tf | 7 - config/testdata/modules.tf | 4 - config/testdata/output-depends-on.tf | 4 - config/testdata/output-no-warnings/main.tf | 14 - config/testdata/output-unnamed.tf | 7 - config/testdata/output-warnings/main.tf | 24 - config/testdata/prevent-destroy-string.tf | 10 - .../testdata/provider-version-invalid/main.tf | 5 - config/testdata/provider-version/main.tf | 6 - config/testdata/provisioners-destroy.tf | 14 - config/testdata/provisioners.tf | 11 - config/testdata/resource-arity-mistake.tf | 5 - config/testdata/resource-multi-lifecycle.tf | 4 - config/testdata/resource-no-name.tf.json | 11 - config/testdata/terraform-backend-2.tf.json | 9 - config/testdata/terraform-backend-multi.tf | 4 - config/testdata/terraform-backend.tf | 5 - config/testdata/terraform-backend.tf.json | 9 - .../validate-backend-interpolate/main.tf | 5 - .../testdata/validate-bad-depends-on/main.tf | 3 - .../validate-bad-multi-resource/main.tf | 7 - .../testdata/validate-bad-tf-version/main.tf | 3 - .../validate-basic-provisioners/main.tf | 14 - .../validate-count-bad-context/main.tf | 11 - .../validate-count-below-zero/main.tf | 3 - .../testdata/validate-count-count-var/main.tf | 3 - config/testdata/validate-count-int/main.tf | 5 - .../validate-count-module-var/main.tf | 7 - .../validate-count-not-int-const/main.tf | 5 - .../testdata/validate-count-not-int/main.tf | 5 - .../validate-count-resource-var-multi/main.tf | 5 - .../validate-count-resource-var/main.tf | 5 - .../testdata/validate-count-user-var/main.tf | 5 - .../validate-count-var-invalid/main.tf | 3 - .../validate-count-var-unknown/main.tf | 3 - config/testdata/validate-count-var/main.tf | 3 - config/testdata/validate-count-zero/main.tf | 3 - .../validate-data-provisioner/main.tf | 3 - .../validate-depends-on-bad-module/main.tf | 7 - .../validate-depends-on-module/main.tf | 7 - .../testdata/validate-depends-on-var/main.tf | 7 - config/testdata/validate-dup-module/main.tf | 7 - config/testdata/validate-dup-resource/main.tf | 7 - config/testdata/validate-good/main.tf | 37 - .../validate-ignore-changes-bad/main.tf | 21 - .../main.tf | 7 - .../testdata/validate-ignore-changes/main.tf | 21 - .../validate-local-multi-file/local-def.tf | 4 - .../validate-local-multi-file/local-use.tf | 8 - .../validate-local-value-count/main.tf | 8 - .../validate-missing-provider/main.tf | 10 - .../testdata/validate-module-name-bad/main.tf | 3 - .../validate-module-source-var/main.tf | 3 - .../testdata/validate-module-var-int/main.tf | 4 - .../testdata/validate-module-var-list/main.tf | 4 - .../testdata/validate-module-var-map/main.tf | 7 - .../testdata/validate-module-var-self/main.tf | 4 - .../validate-output-bad-field/main.tf | 7 - .../validate-output-description/main.tf | 4 - config/testdata/validate-output-dup/main.tf | 10 - .../validate-path-var-invalid/main.tf | 3 - config/testdata/validate-path-var/main.tf | 3 - .../validate-prov-conn-splat-other/main.tf | 9 - .../validate-prov-conn-splat-self/main.tf | 7 - .../validate-prov-splat-other/main.tf | 7 - .../testdata/validate-prov-splat-self/main.tf | 7 - .../validate-provider-multi-good/main.tf | 7 - .../validate-provider-multi-ref-bad/main.tf | 7 - .../validate-provider-multi-ref-good/main.tf | 7 - .../testdata/validate-provider-multi/main.tf | 7 - .../validate-provider-version-invalid/main.tf | 3 - .../validate-resource-prov-self/main.tf | 11 - .../testdata/validate-resource-self/main.tf | 3 - .../validate-tf-version-interp/main.tf | 3 - config/testdata/validate-tf-version/main.tf | 3 - .../main.tf | 6 - .../validate-unknown-resource-var/main.tf | 6 - config/testdata/validate-unknownthing/main.tf | 1 - .../validate-unknownvar-count/main.tf | 5 - config/testdata/validate-unknownvar/main.tf | 8 - .../main.tf | 5 - .../validate-var-default-interpolate/main.tf | 3 - .../validate-var-default-list-type/main.tf | 3 - config/testdata/validate-var-default/main.tf | 9 - config/testdata/validate-var-dup/main.tf | 2 - .../validate-var-module-invalid/main.tf | 3 - config/testdata/validate-var-module/main.tf | 5 - .../main.tf | 7 - .../testdata/validate-var-multi-func/main.tf | 7 - config/testdata/validate-var-nested/main.tf | 6 - config/testdata/var-invalid-key.tf | 3 - config/testdata/var_int.tf | 1 - config/testdata/var_int_bare.tf | 1 - config/testdata/variable-mismatched-type.tf | 7 - config/testdata/variable-no-name.tf | 8 - config/testdata/variables.tf | 7 - config/testdata/windows-line-endings.tf | 6 - config/testing.go | 17 - {config => configs}/interpolate.go | 15 +- {config => configs}/interpolate_test.go | 7 +- {config => configs}/interpolate_walk.go | 2 +- {config => configs}/interpolate_walk_test.go | 2 +- {config => configs}/raw_config.go | 2 +- {config => configs}/raw_config_test.go | 2 +- terraform/resource.go | 10 +- 180 files changed, 21 insertions(+), 9143 deletions(-) delete mode 100644 config/append.go delete mode 100644 config/append_test.go delete mode 100644 config/config.go delete mode 100644 config/config_string.go delete mode 100644 config/config_terraform.go delete mode 100644 config/config_terraform_test.go delete mode 100644 config/config_test.go delete mode 100644 config/config_tree.go delete mode 100644 config/import_tree.go delete mode 100644 config/import_tree_test.go delete mode 100644 config/loader.go delete mode 100644 config/loader_hcl.go delete mode 100644 config/loader_hcl2.go delete mode 100644 config/loader_hcl2_test.go delete mode 100644 config/loader_hcl_test.go delete mode 100644 config/loader_test.go delete mode 100644 config/merge.go delete mode 100644 config/merge_test.go delete mode 100644 config/providers.go delete mode 100644 config/provisioner_enums.go delete mode 100644 config/resource_mode.go delete mode 100644 config/resource_mode_string.go delete mode 100644 config/testdata/.gitattributes delete mode 100644 config/testdata/attributes.tf delete mode 100644 config/testdata/attributes.tf.json delete mode 100644 config/testdata/backend-hash-basic/main.tf delete mode 100644 config/testdata/backend-hash-empty/main.tf delete mode 100644 config/testdata/backend-hash-no-terraform/main.tf delete mode 100644 config/testdata/backend-hash-type-only/main.tf delete mode 100644 config/testdata/bad-variable-type.tf delete mode 100644 config/testdata/bad_type.tf.nope delete mode 100644 config/testdata/basic-hcl2.tf delete mode 100644 config/testdata/basic.tf delete mode 100644 config/testdata/basic.tf.json delete mode 100644 config/testdata/connection.tf delete mode 100644 config/testdata/copy-basic/main.tf delete mode 100644 config/testdata/count-int/main.tf delete mode 100644 config/testdata/count-list/main.tf delete mode 100644 config/testdata/count-string/main.tf delete mode 100644 config/testdata/count-var/main.tf delete mode 100644 config/testdata/create-before-destroy.tf delete mode 100644 config/testdata/data-count/main.tf delete mode 100644 config/testdata/data-source-arity-mistake.tf delete mode 100644 config/testdata/dir-basic/README.md delete mode 100644 config/testdata/dir-basic/nested/nested.tf delete mode 100644 config/testdata/dir-basic/one.tf delete mode 100644 config/testdata/dir-basic/two.tf delete mode 100644 config/testdata/dir-empty/.gitkeep delete mode 100644 config/testdata/dir-merge/one.tf delete mode 100644 config/testdata/dir-merge/two.tf delete mode 100644 config/testdata/dir-only-override/main_override.tf delete mode 100644 config/testdata/dir-override-var/main.tf delete mode 100644 config/testdata/dir-override-var/main_override.tf delete mode 100644 config/testdata/dir-override/foo_override.tf.json delete mode 100644 config/testdata/dir-override/one.tf delete mode 100644 config/testdata/dir-override/override.tf.json delete mode 100644 config/testdata/dir-override/two.tf delete mode 100644 config/testdata/dir-temporary-files/#emacs-two.tf# delete mode 100644 config/testdata/dir-temporary-files/.#emacs-one.tf delete mode 100644 config/testdata/dir-temporary-files/.hidden.tf delete mode 100644 config/testdata/dir-temporary-files/vim-one.tf~ delete mode 100644 config/testdata/empty-collections/main.tf delete mode 100644 config/testdata/empty.tf delete mode 100644 config/testdata/escapedquotes.tf delete mode 100644 config/testdata/git-crypt.tf delete mode 100644 config/testdata/hcl2-experiment-switch/not-eligible.tf.json delete mode 100644 config/testdata/hcl2-experiment-switch/not-opted-in.tf delete mode 100644 config/testdata/hcl2-experiment-switch/opted-in.tf delete mode 100644 config/testdata/heredoc.tf delete mode 100644 config/testdata/ignore-changes.tf delete mode 100644 config/testdata/import.tf delete mode 100644 config/testdata/import/one.tf delete mode 100644 config/testdata/interpolations/concat.hcl delete mode 100644 config/testdata/lifecycle_cbd_typo.tf delete mode 100644 config/testdata/module-providers/main.tf delete mode 100644 config/testdata/module-unnamed.tf delete mode 100644 config/testdata/modules.tf delete mode 100644 config/testdata/output-depends-on.tf delete mode 100644 config/testdata/output-no-warnings/main.tf delete mode 100644 config/testdata/output-unnamed.tf delete mode 100644 config/testdata/output-warnings/main.tf delete mode 100644 config/testdata/prevent-destroy-string.tf delete mode 100644 config/testdata/provider-version-invalid/main.tf delete mode 100644 config/testdata/provider-version/main.tf delete mode 100644 config/testdata/provisioners-destroy.tf delete mode 100644 config/testdata/provisioners.tf delete mode 100644 config/testdata/resource-arity-mistake.tf delete mode 100644 config/testdata/resource-multi-lifecycle.tf delete mode 100644 config/testdata/resource-no-name.tf.json delete mode 100644 config/testdata/terraform-backend-2.tf.json delete mode 100644 config/testdata/terraform-backend-multi.tf delete mode 100644 config/testdata/terraform-backend.tf delete mode 100644 config/testdata/terraform-backend.tf.json delete mode 100644 config/testdata/validate-backend-interpolate/main.tf delete mode 100644 config/testdata/validate-bad-depends-on/main.tf delete mode 100644 config/testdata/validate-bad-multi-resource/main.tf delete mode 100644 config/testdata/validate-bad-tf-version/main.tf delete mode 100644 config/testdata/validate-basic-provisioners/main.tf delete mode 100644 config/testdata/validate-count-bad-context/main.tf delete mode 100644 config/testdata/validate-count-below-zero/main.tf delete mode 100644 config/testdata/validate-count-count-var/main.tf delete mode 100644 config/testdata/validate-count-int/main.tf delete mode 100644 config/testdata/validate-count-module-var/main.tf delete mode 100644 config/testdata/validate-count-not-int-const/main.tf delete mode 100644 config/testdata/validate-count-not-int/main.tf delete mode 100644 config/testdata/validate-count-resource-var-multi/main.tf delete mode 100644 config/testdata/validate-count-resource-var/main.tf delete mode 100644 config/testdata/validate-count-user-var/main.tf delete mode 100644 config/testdata/validate-count-var-invalid/main.tf delete mode 100644 config/testdata/validate-count-var-unknown/main.tf delete mode 100644 config/testdata/validate-count-var/main.tf delete mode 100644 config/testdata/validate-count-zero/main.tf delete mode 100644 config/testdata/validate-data-provisioner/main.tf delete mode 100644 config/testdata/validate-depends-on-bad-module/main.tf delete mode 100644 config/testdata/validate-depends-on-module/main.tf delete mode 100644 config/testdata/validate-depends-on-var/main.tf delete mode 100644 config/testdata/validate-dup-module/main.tf delete mode 100644 config/testdata/validate-dup-resource/main.tf delete mode 100644 config/testdata/validate-good/main.tf delete mode 100644 config/testdata/validate-ignore-changes-bad/main.tf delete mode 100644 config/testdata/validate-ignore-changes-interpolate/main.tf delete mode 100644 config/testdata/validate-ignore-changes/main.tf delete mode 100644 config/testdata/validate-local-multi-file/local-def.tf delete mode 100644 config/testdata/validate-local-multi-file/local-use.tf delete mode 100644 config/testdata/validate-local-value-count/main.tf delete mode 100644 config/testdata/validate-missing-provider/main.tf delete mode 100644 config/testdata/validate-module-name-bad/main.tf delete mode 100644 config/testdata/validate-module-source-var/main.tf delete mode 100644 config/testdata/validate-module-var-int/main.tf delete mode 100644 config/testdata/validate-module-var-list/main.tf delete mode 100644 config/testdata/validate-module-var-map/main.tf delete mode 100644 config/testdata/validate-module-var-self/main.tf delete mode 100644 config/testdata/validate-output-bad-field/main.tf delete mode 100644 config/testdata/validate-output-description/main.tf delete mode 100644 config/testdata/validate-output-dup/main.tf delete mode 100644 config/testdata/validate-path-var-invalid/main.tf delete mode 100644 config/testdata/validate-path-var/main.tf delete mode 100644 config/testdata/validate-prov-conn-splat-other/main.tf delete mode 100644 config/testdata/validate-prov-conn-splat-self/main.tf delete mode 100644 config/testdata/validate-prov-splat-other/main.tf delete mode 100644 config/testdata/validate-prov-splat-self/main.tf delete mode 100644 config/testdata/validate-provider-multi-good/main.tf delete mode 100644 config/testdata/validate-provider-multi-ref-bad/main.tf delete mode 100644 config/testdata/validate-provider-multi-ref-good/main.tf delete mode 100644 config/testdata/validate-provider-multi/main.tf delete mode 100644 config/testdata/validate-provider-version-invalid/main.tf delete mode 100644 config/testdata/validate-resource-prov-self/main.tf delete mode 100644 config/testdata/validate-resource-self/main.tf delete mode 100644 config/testdata/validate-tf-version-interp/main.tf delete mode 100644 config/testdata/validate-tf-version/main.tf delete mode 100644 config/testdata/validate-unknown-resource-var-output/main.tf delete mode 100644 config/testdata/validate-unknown-resource-var/main.tf delete mode 100644 config/testdata/validate-unknownthing/main.tf delete mode 100644 config/testdata/validate-unknownvar-count/main.tf delete mode 100644 config/testdata/validate-unknownvar/main.tf delete mode 100644 config/testdata/validate-var-default-interpolate-escaped/main.tf delete mode 100644 config/testdata/validate-var-default-interpolate/main.tf delete mode 100644 config/testdata/validate-var-default-list-type/main.tf delete mode 100644 config/testdata/validate-var-default/main.tf delete mode 100644 config/testdata/validate-var-dup/main.tf delete mode 100644 config/testdata/validate-var-module-invalid/main.tf delete mode 100644 config/testdata/validate-var-module/main.tf delete mode 100644 config/testdata/validate-var-multi-exact-non-slice/main.tf delete mode 100644 config/testdata/validate-var-multi-func/main.tf delete mode 100644 config/testdata/validate-var-nested/main.tf delete mode 100644 config/testdata/var-invalid-key.tf delete mode 100644 config/testdata/var_int.tf delete mode 100644 config/testdata/var_int_bare.tf delete mode 100644 config/testdata/variable-mismatched-type.tf delete mode 100644 config/testdata/variable-no-name.tf delete mode 100644 config/testdata/variables.tf delete mode 100644 config/testdata/windows-line-endings.tf delete mode 100644 config/testing.go rename {config => configs}/interpolate.go (97%) rename {config => configs}/interpolate_test.go (97%) rename {config => configs}/interpolate_walk.go (99%) rename {config => configs}/interpolate_walk_test.go (99%) rename {config => configs}/raw_config.go (99%) rename {config => configs}/raw_config_test.go (99%) diff --git a/config/append.go b/config/append.go deleted file mode 100644 index 9d80c42b7..000000000 --- a/config/append.go +++ /dev/null @@ -1,92 +0,0 @@ -package config - -// Append appends one configuration to another. -// -// Append assumes that both configurations will not have -// conflicting variables, resources, etc. If they do, the -// problems will be caught in the validation phase. -// -// It is possible that c1, c2 on their own are not valid. For -// example, a resource in c2 may reference a variable in c1. But -// together, they would be valid. -func Append(c1, c2 *Config) (*Config, error) { - c := new(Config) - - // Append unknown keys, but keep them unique since it is a set - unknowns := make(map[string]struct{}) - for _, k := range c1.unknownKeys { - _, present := unknowns[k] - if !present { - unknowns[k] = struct{}{} - c.unknownKeys = append(c.unknownKeys, k) - } - } - - for _, k := range c2.unknownKeys { - _, present := unknowns[k] - if !present { - unknowns[k] = struct{}{} - c.unknownKeys = append(c.unknownKeys, k) - } - } - - c.Atlas = c1.Atlas - if c2.Atlas != nil { - c.Atlas = c2.Atlas - } - - // merge Terraform blocks - if c1.Terraform != nil { - c.Terraform = c1.Terraform - if c2.Terraform != nil { - c.Terraform.Merge(c2.Terraform) - } - } else { - c.Terraform = c2.Terraform - } - - if len(c1.Modules) > 0 || len(c2.Modules) > 0 { - c.Modules = make( - []*Module, 0, len(c1.Modules)+len(c2.Modules)) - c.Modules = append(c.Modules, c1.Modules...) - c.Modules = append(c.Modules, c2.Modules...) - } - - if len(c1.Outputs) > 0 || len(c2.Outputs) > 0 { - c.Outputs = make( - []*Output, 0, len(c1.Outputs)+len(c2.Outputs)) - c.Outputs = append(c.Outputs, c1.Outputs...) - c.Outputs = append(c.Outputs, c2.Outputs...) - } - - if len(c1.ProviderConfigs) > 0 || len(c2.ProviderConfigs) > 0 { - c.ProviderConfigs = make( - []*ProviderConfig, - 0, len(c1.ProviderConfigs)+len(c2.ProviderConfigs)) - c.ProviderConfigs = append(c.ProviderConfigs, c1.ProviderConfigs...) - c.ProviderConfigs = append(c.ProviderConfigs, c2.ProviderConfigs...) - } - - if len(c1.Resources) > 0 || len(c2.Resources) > 0 { - c.Resources = make( - []*Resource, - 0, len(c1.Resources)+len(c2.Resources)) - c.Resources = append(c.Resources, c1.Resources...) - c.Resources = append(c.Resources, c2.Resources...) - } - - if len(c1.Variables) > 0 || len(c2.Variables) > 0 { - c.Variables = make( - []*Variable, 0, len(c1.Variables)+len(c2.Variables)) - c.Variables = append(c.Variables, c1.Variables...) - c.Variables = append(c.Variables, c2.Variables...) - } - - if len(c1.Locals) > 0 || len(c2.Locals) > 0 { - c.Locals = make([]*Local, 0, len(c1.Locals)+len(c2.Locals)) - c.Locals = append(c.Locals, c1.Locals...) - c.Locals = append(c.Locals, c2.Locals...) - } - - return c, nil -} diff --git a/config/append_test.go b/config/append_test.go deleted file mode 100644 index 853a460b0..000000000 --- a/config/append_test.go +++ /dev/null @@ -1,173 +0,0 @@ -package config - -import ( - "fmt" - "reflect" - "testing" - - "github.com/davecgh/go-spew/spew" -) - -func TestAppend(t *testing.T) { - cases := []struct { - c1, c2, result *Config - err bool - }{ - { - &Config{ - Atlas: &AtlasConfig{ - Name: "foo", - }, - Modules: []*Module{ - &Module{Name: "foo"}, - }, - Outputs: []*Output{ - &Output{Name: "foo"}, - }, - ProviderConfigs: []*ProviderConfig{ - &ProviderConfig{Name: "foo"}, - }, - Resources: []*Resource{ - &Resource{Name: "foo"}, - }, - Variables: []*Variable{ - &Variable{Name: "foo"}, - }, - Locals: []*Local{ - &Local{Name: "foo"}, - }, - - unknownKeys: []string{"foo"}, - }, - - &Config{ - Atlas: &AtlasConfig{ - Name: "bar", - }, - Modules: []*Module{ - &Module{Name: "bar"}, - }, - Outputs: []*Output{ - &Output{Name: "bar"}, - }, - ProviderConfigs: []*ProviderConfig{ - &ProviderConfig{Name: "bar"}, - }, - Resources: []*Resource{ - &Resource{Name: "bar"}, - }, - Variables: []*Variable{ - &Variable{Name: "bar"}, - }, - Locals: []*Local{ - &Local{Name: "bar"}, - }, - - unknownKeys: []string{"bar"}, - }, - - &Config{ - Atlas: &AtlasConfig{ - Name: "bar", - }, - Modules: []*Module{ - &Module{Name: "foo"}, - &Module{Name: "bar"}, - }, - Outputs: []*Output{ - &Output{Name: "foo"}, - &Output{Name: "bar"}, - }, - ProviderConfigs: []*ProviderConfig{ - &ProviderConfig{Name: "foo"}, - &ProviderConfig{Name: "bar"}, - }, - Resources: []*Resource{ - &Resource{Name: "foo"}, - &Resource{Name: "bar"}, - }, - Variables: []*Variable{ - &Variable{Name: "foo"}, - &Variable{Name: "bar"}, - }, - Locals: []*Local{ - &Local{Name: "foo"}, - &Local{Name: "bar"}, - }, - - unknownKeys: []string{"foo", "bar"}, - }, - - false, - }, - - // Terraform block - { - &Config{ - Terraform: &Terraform{ - RequiredVersion: "A", - }, - }, - &Config{}, - &Config{ - Terraform: &Terraform{ - RequiredVersion: "A", - }, - }, - false, - }, - - { - &Config{}, - &Config{ - Terraform: &Terraform{ - RequiredVersion: "A", - }, - }, - &Config{ - Terraform: &Terraform{ - RequiredVersion: "A", - }, - }, - false, - }, - - // appending configs merges terraform blocks - { - &Config{ - Terraform: &Terraform{ - RequiredVersion: "A", - }, - }, - &Config{ - Terraform: &Terraform{ - Backend: &Backend{ - Type: "test", - }, - }, - }, - &Config{ - Terraform: &Terraform{ - RequiredVersion: "A", - Backend: &Backend{ - Type: "test", - }, - }, - }, - false, - }, - } - - for i, tc := range cases { - t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { - actual, err := Append(tc.c1, tc.c2) - if err != nil != tc.err { - t.Errorf("unexpected error: %s", err) - } - - if !reflect.DeepEqual(actual, tc.result) { - t.Errorf("wrong result\ngot: %swant: %s", spew.Sdump(actual), spew.Sdump(tc.result)) - } - }) - } -} diff --git a/config/config.go b/config/config.go deleted file mode 100644 index 497effef5..000000000 --- a/config/config.go +++ /dev/null @@ -1,1171 +0,0 @@ -// The config package is responsible for loading and validating the -// configuration. -package config - -import ( - "fmt" - "regexp" - "strconv" - "strings" - - hcl2 "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/hil/ast" - "github.com/hashicorp/terraform/helper/hilmapstructure" - "github.com/hashicorp/terraform/plugin/discovery" - "github.com/hashicorp/terraform/tfdiags" - "github.com/mitchellh/reflectwalk" -) - -// NameRegexp is the regular expression that all names (modules, providers, -// resources, etc.) must follow. -var NameRegexp = regexp.MustCompile(`(?i)\A[A-Z0-9_][A-Z0-9\-\_]*\z`) - -// Config is the configuration that comes from loading a collection -// of Terraform templates. -type Config struct { - // Dir is the path to the directory where this configuration was - // loaded from. If it is blank, this configuration wasn't loaded from - // any meaningful directory. - Dir string - - Terraform *Terraform - Atlas *AtlasConfig - Modules []*Module - ProviderConfigs []*ProviderConfig - Resources []*Resource - Variables []*Variable - Locals []*Local - Outputs []*Output - - // The fields below can be filled in by loaders for validation - // purposes. - unknownKeys []string -} - -// AtlasConfig is the configuration for building in HashiCorp's Atlas. -type AtlasConfig struct { - Name string - Include []string - Exclude []string -} - -// Module is a module used within a configuration. -// -// This does not represent a module itself, this represents a module -// call-site within an existing configuration. -type Module struct { - Name string - Source string - Version string - Providers map[string]string - RawConfig *RawConfig -} - -// ProviderConfig is the configuration for a resource provider. -// -// For example, Terraform needs to set the AWS access keys for the AWS -// resource provider. -type ProviderConfig struct { - Name string - Alias string - Version string - RawConfig *RawConfig -} - -// A resource represents a single Terraform resource in the configuration. -// A Terraform resource is something that supports some or all of the -// usual "create, read, update, delete" operations, depending on -// the given Mode. -type Resource struct { - Mode ResourceMode // which operations the resource supports - Name string - Type string - RawCount *RawConfig - RawConfig *RawConfig - Provisioners []*Provisioner - Provider string - DependsOn []string - Lifecycle ResourceLifecycle -} - -// Copy returns a copy of this Resource. Helpful for avoiding shared -// config pointers across multiple pieces of the graph that need to do -// interpolation. -func (r *Resource) Copy() *Resource { - n := &Resource{ - Mode: r.Mode, - Name: r.Name, - Type: r.Type, - RawCount: r.RawCount.Copy(), - RawConfig: r.RawConfig.Copy(), - Provisioners: make([]*Provisioner, 0, len(r.Provisioners)), - Provider: r.Provider, - DependsOn: make([]string, len(r.DependsOn)), - Lifecycle: *r.Lifecycle.Copy(), - } - for _, p := range r.Provisioners { - n.Provisioners = append(n.Provisioners, p.Copy()) - } - copy(n.DependsOn, r.DependsOn) - return n -} - -// ResourceLifecycle is used to store the lifecycle tuning parameters -// to allow customized behavior -type ResourceLifecycle struct { - CreateBeforeDestroy bool `mapstructure:"create_before_destroy"` - PreventDestroy bool `mapstructure:"prevent_destroy"` - IgnoreChanges []string `mapstructure:"ignore_changes"` -} - -// Copy returns a copy of this ResourceLifecycle -func (r *ResourceLifecycle) Copy() *ResourceLifecycle { - n := &ResourceLifecycle{ - CreateBeforeDestroy: r.CreateBeforeDestroy, - PreventDestroy: r.PreventDestroy, - IgnoreChanges: make([]string, len(r.IgnoreChanges)), - } - copy(n.IgnoreChanges, r.IgnoreChanges) - return n -} - -// Provisioner is a configured provisioner step on a resource. -type Provisioner struct { - Type string - RawConfig *RawConfig - ConnInfo *RawConfig - - When ProvisionerWhen - OnFailure ProvisionerOnFailure -} - -// Copy returns a copy of this Provisioner -func (p *Provisioner) Copy() *Provisioner { - return &Provisioner{ - Type: p.Type, - RawConfig: p.RawConfig.Copy(), - ConnInfo: p.ConnInfo.Copy(), - When: p.When, - OnFailure: p.OnFailure, - } -} - -// Variable is a module argument defined within the configuration. -type Variable struct { - Name string - DeclaredType string `mapstructure:"type"` - Default interface{} - Description string -} - -// Local is a local value defined within the configuration. -type Local struct { - Name string - RawConfig *RawConfig -} - -// Output is an output defined within the configuration. An output is -// resulting data that is highlighted by Terraform when finished. An -// output marked Sensitive will be output in a masked form following -// application, but will still be available in state. -type Output struct { - Name string - DependsOn []string - Description string - Sensitive bool - RawConfig *RawConfig -} - -// VariableType is the type of value a variable is holding, and returned -// by the Type() function on variables. -type VariableType byte - -const ( - VariableTypeUnknown VariableType = iota - VariableTypeString - VariableTypeList - VariableTypeMap -) - -func (v VariableType) Printable() string { - switch v { - case VariableTypeString: - return "string" - case VariableTypeMap: - return "map" - case VariableTypeList: - return "list" - default: - return "unknown" - } -} - -// ProviderConfigName returns the name of the provider configuration in -// the given mapping that maps to the proper provider configuration -// for this resource. -func ProviderConfigName(t string, pcs []*ProviderConfig) string { - lk := "" - for _, v := range pcs { - k := v.Name - if strings.HasPrefix(t, k) && len(k) > len(lk) { - lk = k - } - } - - return lk -} - -// A unique identifier for this module. -func (r *Module) Id() string { - return fmt.Sprintf("%s", r.Name) -} - -// Count returns the count of this resource. -func (r *Resource) Count() (int, error) { - raw := r.RawCount.Value() - count, ok := r.RawCount.Value().(string) - if !ok { - return 0, fmt.Errorf( - "expected count to be a string or int, got %T", raw) - } - - v, err := strconv.ParseInt(count, 0, 0) - if err != nil { - return 0, fmt.Errorf( - "cannot parse %q as an integer", - count, - ) - } - - return int(v), nil -} - -// A unique identifier for this resource. -func (r *Resource) Id() string { - switch r.Mode { - case ManagedResourceMode: - return fmt.Sprintf("%s.%s", r.Type, r.Name) - case DataResourceMode: - return fmt.Sprintf("data.%s.%s", r.Type, r.Name) - default: - panic(fmt.Errorf("unknown resource mode %s", r.Mode)) - } -} - -// Validate does some basic semantic checking of the configuration. -func (c *Config) Validate() tfdiags.Diagnostics { - if c == nil { - return nil - } - - var diags tfdiags.Diagnostics - - for _, k := range c.unknownKeys { - diags = diags.Append( - fmt.Errorf("Unknown root level key: %s", k), - ) - } - - // Validate the Terraform config - if tf := c.Terraform; tf != nil { - errs := c.Terraform.Validate() - for _, err := range errs { - diags = diags.Append(err) - } - } - - vars := c.InterpolatedVariables() - varMap := make(map[string]*Variable) - for _, v := range c.Variables { - if _, ok := varMap[v.Name]; ok { - diags = diags.Append(fmt.Errorf( - "Variable '%s': duplicate found. Variable names must be unique.", - v.Name, - )) - } - - varMap[v.Name] = v - } - - for k, _ := range varMap { - if !NameRegexp.MatchString(k) { - diags = diags.Append(fmt.Errorf( - "variable %q: variable name must match regular expression %s", - k, NameRegexp, - )) - } - } - - for _, v := range c.Variables { - if v.Type() == VariableTypeUnknown { - diags = diags.Append(fmt.Errorf( - "Variable '%s': must be a string or a map", - v.Name, - )) - continue - } - - interp := false - fn := func(n ast.Node) (interface{}, error) { - // LiteralNode is a literal string (outside of a ${ ... } sequence). - // interpolationWalker skips most of these. but in particular it - // visits those that have escaped sequences (like $${foo}) as a - // signal that *some* processing is required on this string. For - // our purposes here though, this is fine and not an interpolation. - if _, ok := n.(*ast.LiteralNode); !ok { - interp = true - } - return "", nil - } - - w := &interpolationWalker{F: fn} - if v.Default != nil { - if err := reflectwalk.Walk(v.Default, w); err == nil { - if interp { - diags = diags.Append(fmt.Errorf( - "variable %q: default may not contain interpolations", - v.Name, - )) - } - } - } - } - - // Check for references to user variables that do not actually - // exist and record those errors. - for source, vs := range vars { - for _, v := range vs { - uv, ok := v.(*UserVariable) - if !ok { - continue - } - - if _, ok := varMap[uv.Name]; !ok { - diags = diags.Append(fmt.Errorf( - "%s: unknown variable referenced: '%s'; define it with a 'variable' block", - source, - uv.Name, - )) - } - } - } - - // Check that all count variables are valid. - for source, vs := range vars { - for _, rawV := range vs { - switch v := rawV.(type) { - case *CountVariable: - if v.Type == CountValueInvalid { - diags = diags.Append(fmt.Errorf( - "%s: invalid count variable: %s", - source, - v.FullKey(), - )) - } - case *PathVariable: - if v.Type == PathValueInvalid { - diags = diags.Append(fmt.Errorf( - "%s: invalid path variable: %s", - source, - v.FullKey(), - )) - } - } - } - } - - // Check that providers aren't declared multiple times and that their - // version constraints, where present, are syntactically valid. - providerSet := make(map[string]bool) - for _, p := range c.ProviderConfigs { - name := p.FullName() - if _, ok := providerSet[name]; ok { - diags = diags.Append(fmt.Errorf( - "provider.%s: multiple configurations present; only one configuration is allowed per provider", - name, - )) - continue - } - - if p.Version != "" { - _, err := discovery.ConstraintStr(p.Version).Parse() - if err != nil { - diags = diags.Append(&hcl2.Diagnostic{ - Severity: hcl2.DiagError, - Summary: "Invalid provider version constraint", - Detail: fmt.Sprintf( - "The value %q given for provider.%s is not a valid version constraint.", - p.Version, name, - ), - // TODO: include a "Subject" source reference in here, - // once the config loader is able to retain source - // location information. - }) - } - } - - providerSet[name] = true - } - - // Check that all references to modules are valid - modules := make(map[string]*Module) - dupped := make(map[string]struct{}) - for _, m := range c.Modules { - // Check for duplicates - if _, ok := modules[m.Id()]; ok { - if _, ok := dupped[m.Id()]; !ok { - dupped[m.Id()] = struct{}{} - - diags = diags.Append(fmt.Errorf( - "module %q: module repeated multiple times", - m.Id(), - )) - } - - // Already seen this module, just skip it - continue - } - - modules[m.Id()] = m - - // Check that the source has no interpolations - rc, err := NewRawConfig(map[string]interface{}{ - "root": m.Source, - }) - if err != nil { - diags = diags.Append(fmt.Errorf( - "module %q: module source error: %s", - m.Id(), err, - )) - } else if len(rc.Interpolations) > 0 { - diags = diags.Append(fmt.Errorf( - "module %q: module source cannot contain interpolations", - m.Id(), - )) - } - - // Check that the name matches our regexp - if !NameRegexp.Match([]byte(m.Name)) { - diags = diags.Append(fmt.Errorf( - "module %q: module name must be a letter or underscore followed by only letters, numbers, dashes, and underscores", - m.Id(), - )) - } - - // Check that the configuration can all be strings, lists or maps - raw := make(map[string]interface{}) - for k, v := range m.RawConfig.Raw { - var strVal string - if err := hilmapstructure.WeakDecode(v, &strVal); err == nil { - raw[k] = strVal - continue - } - - var mapVal map[string]interface{} - if err := hilmapstructure.WeakDecode(v, &mapVal); err == nil { - raw[k] = mapVal - continue - } - - var sliceVal []interface{} - if err := hilmapstructure.WeakDecode(v, &sliceVal); err == nil { - raw[k] = sliceVal - continue - } - - diags = diags.Append(fmt.Errorf( - "module %q: argument %s must have a string, list, or map value", - m.Id(), k, - )) - } - - // Check for invalid count variables - for _, v := range m.RawConfig.Variables { - switch v.(type) { - case *CountVariable: - diags = diags.Append(fmt.Errorf( - "module %q: count variables are only valid within resources", - m.Name, - )) - case *SelfVariable: - diags = diags.Append(fmt.Errorf( - "module %q: self variables are only valid within resources", - m.Name, - )) - } - } - - // Update the raw configuration to only contain the string values - m.RawConfig, err = NewRawConfig(raw) - if err != nil { - diags = diags.Append(fmt.Errorf( - "%s: can't initialize configuration: %s", - m.Id(), err, - )) - } - - // check that all named providers actually exist - for _, p := range m.Providers { - if !providerSet[p] { - diags = diags.Append(fmt.Errorf( - "module %q: cannot pass non-existent provider %q", - m.Name, p, - )) - } - } - - } - dupped = nil - - // Check that all variables for modules reference modules that - // exist. - for source, vs := range vars { - for _, v := range vs { - mv, ok := v.(*ModuleVariable) - if !ok { - continue - } - - if _, ok := modules[mv.Name]; !ok { - diags = diags.Append(fmt.Errorf( - "%s: unknown module referenced: %s", - source, mv.Name, - )) - } - } - } - - // Check that all references to resources are valid - resources := make(map[string]*Resource) - dupped = make(map[string]struct{}) - for _, r := range c.Resources { - if _, ok := resources[r.Id()]; ok { - if _, ok := dupped[r.Id()]; !ok { - dupped[r.Id()] = struct{}{} - - diags = diags.Append(fmt.Errorf( - "%s: resource repeated multiple times", - r.Id(), - )) - } - } - - resources[r.Id()] = r - } - dupped = nil - - // Validate resources - for n, r := range resources { - // Verify count variables - for _, v := range r.RawCount.Variables { - switch v.(type) { - case *CountVariable: - diags = diags.Append(fmt.Errorf( - "%s: resource count can't reference count variable: %s", - n, v.FullKey(), - )) - case *SimpleVariable: - diags = diags.Append(fmt.Errorf( - "%s: resource count can't reference variable: %s", - n, v.FullKey(), - )) - - // Good - case *ModuleVariable: - case *ResourceVariable: - case *TerraformVariable: - case *UserVariable: - case *LocalVariable: - - default: - diags = diags.Append(fmt.Errorf( - "Internal error. Unknown type in count var in %s: %T", - n, v, - )) - } - } - - if !r.RawCount.couldBeInteger() { - diags = diags.Append(fmt.Errorf( - "%s: resource count must be an integer", n, - )) - } - r.RawCount.init() - - // Validate DependsOn - for _, err := range c.validateDependsOn(n, r.DependsOn, resources, modules) { - diags = diags.Append(err) - } - - // Verify provisioners - for _, p := range r.Provisioners { - // This validation checks that there are no splat variables - // referencing ourself. This currently is not allowed. - - for _, v := range p.ConnInfo.Variables { - rv, ok := v.(*ResourceVariable) - if !ok { - continue - } - - if rv.Multi && rv.Index == -1 && rv.Type == r.Type && rv.Name == r.Name { - diags = diags.Append(fmt.Errorf( - "%s: connection info cannot contain splat variable referencing itself", - n, - )) - break - } - } - - for _, v := range p.RawConfig.Variables { - rv, ok := v.(*ResourceVariable) - if !ok { - continue - } - - if rv.Multi && rv.Index == -1 && rv.Type == r.Type && rv.Name == r.Name { - diags = diags.Append(fmt.Errorf( - "%s: connection info cannot contain splat variable referencing itself", - n, - )) - break - } - } - - // Check for invalid when/onFailure values, though this should be - // picked up by the loader we check here just in case. - if p.When == ProvisionerWhenInvalid { - diags = diags.Append(fmt.Errorf( - "%s: provisioner 'when' value is invalid", n, - )) - } - if p.OnFailure == ProvisionerOnFailureInvalid { - diags = diags.Append(fmt.Errorf( - "%s: provisioner 'on_failure' value is invalid", n, - )) - } - } - - // Verify ignore_changes contains valid entries - for _, v := range r.Lifecycle.IgnoreChanges { - if strings.Contains(v, "*") && v != "*" { - diags = diags.Append(fmt.Errorf( - "%s: ignore_changes does not support using a partial string together with a wildcard: %s", - n, v, - )) - } - } - - // Verify ignore_changes has no interpolations - rc, err := NewRawConfig(map[string]interface{}{ - "root": r.Lifecycle.IgnoreChanges, - }) - if err != nil { - diags = diags.Append(fmt.Errorf( - "%s: lifecycle ignore_changes error: %s", - n, err, - )) - } else if len(rc.Interpolations) > 0 { - diags = diags.Append(fmt.Errorf( - "%s: lifecycle ignore_changes cannot contain interpolations", - n, - )) - } - - // If it is a data source then it can't have provisioners - if r.Mode == DataResourceMode { - if _, ok := r.RawConfig.Raw["provisioner"]; ok { - diags = diags.Append(fmt.Errorf( - "%s: data sources cannot have provisioners", - n, - )) - } - } - } - - for source, vs := range vars { - for _, v := range vs { - rv, ok := v.(*ResourceVariable) - if !ok { - continue - } - - id := rv.ResourceId() - if _, ok := resources[id]; !ok { - diags = diags.Append(fmt.Errorf( - "%s: unknown resource '%s' referenced in variable %s", - source, - id, - rv.FullKey(), - )) - continue - } - } - } - - // Check that all locals are valid - { - found := make(map[string]struct{}) - for _, l := range c.Locals { - if _, ok := found[l.Name]; ok { - diags = diags.Append(fmt.Errorf( - "%s: duplicate local. local value names must be unique", - l.Name, - )) - continue - } - found[l.Name] = struct{}{} - - for _, v := range l.RawConfig.Variables { - if _, ok := v.(*CountVariable); ok { - diags = diags.Append(fmt.Errorf( - "local %s: count variables are only valid within resources", l.Name, - )) - } - } - } - } - - // Check that all outputs are valid - { - found := make(map[string]struct{}) - for _, o := range c.Outputs { - // Verify the output is new - if _, ok := found[o.Name]; ok { - diags = diags.Append(fmt.Errorf( - "output %q: an output of this name was already defined", - o.Name, - )) - continue - } - found[o.Name] = struct{}{} - - var invalidKeys []string - valueKeyFound := false - for k := range o.RawConfig.Raw { - if k == "value" { - valueKeyFound = true - continue - } - if k == "sensitive" { - if sensitive, ok := o.RawConfig.config[k].(bool); ok { - if sensitive { - o.Sensitive = true - } - continue - } - - diags = diags.Append(fmt.Errorf( - "output %q: value for 'sensitive' must be boolean", - o.Name, - )) - continue - } - if k == "description" { - if desc, ok := o.RawConfig.config[k].(string); ok { - o.Description = desc - continue - } - - diags = diags.Append(fmt.Errorf( - "output %q: value for 'description' must be string", - o.Name, - )) - continue - } - invalidKeys = append(invalidKeys, k) - } - if len(invalidKeys) > 0 { - diags = diags.Append(fmt.Errorf( - "output %q: invalid keys: %s", - o.Name, strings.Join(invalidKeys, ", "), - )) - } - if !valueKeyFound { - diags = diags.Append(fmt.Errorf( - "output %q: missing required 'value' argument", o.Name, - )) - } - - for _, v := range o.RawConfig.Variables { - if _, ok := v.(*CountVariable); ok { - diags = diags.Append(fmt.Errorf( - "output %q: count variables are only valid within resources", - o.Name, - )) - } - } - - // Detect a common mistake of using a "count"ed resource in - // an output value without using the splat or index form. - // Prior to 0.11 this error was silently ignored, but outputs - // now have their errors checked like all other contexts. - // - // TODO: Remove this in 0.12. - for _, v := range o.RawConfig.Variables { - rv, ok := v.(*ResourceVariable) - if !ok { - continue - } - - // If the variable seems to be treating the referenced - // resource as a singleton (no count specified) then - // we'll check to make sure it is indeed a singleton. - // It's a warning if not. - - if rv.Multi || rv.Index != 0 { - // This reference is treating the resource as a - // multi-resource, so the warning doesn't apply. - continue - } - - for _, r := range c.Resources { - if r.Id() != rv.ResourceId() { - continue - } - - // We test specifically for the raw string "1" here - // because we _do_ want to generate this warning if - // the user has provided an expression that happens - // to return 1 right now, to catch situations where - // a count might dynamically be set to something - // other than 1 and thus splat syntax is still needed - // to be safe. - if r.RawCount != nil && r.RawCount.Raw != nil && r.RawCount.Raw["count"] != "1" && rv.Field != "count" { - diags = diags.Append(tfdiags.SimpleWarning(fmt.Sprintf( - "output %q: must use splat syntax to access %s attribute %q, because it has \"count\" set; use %s.*.%s to obtain a list of the attributes across all instances", - o.Name, - r.Id(), rv.Field, - r.Id(), rv.Field, - ))) - } - } - } - } - } - - // Validate the self variable - for source, rc := range c.rawConfigs() { - // Ignore provisioners. This is a pretty brittle way to do this, - // but better than also repeating all the resources. - if strings.Contains(source, "provision") { - continue - } - - for _, v := range rc.Variables { - if _, ok := v.(*SelfVariable); ok { - diags = diags.Append(fmt.Errorf( - "%s: cannot contain self-reference %s", - source, v.FullKey(), - )) - } - } - } - - return diags -} - -// InterpolatedVariables is a helper that returns a mapping of all the interpolated -// variables within the configuration. This is used to verify references -// are valid in the Validate step. -func (c *Config) InterpolatedVariables() map[string][]InterpolatedVariable { - result := make(map[string][]InterpolatedVariable) - for source, rc := range c.rawConfigs() { - for _, v := range rc.Variables { - result[source] = append(result[source], v) - } - } - return result -} - -// rawConfigs returns all of the RawConfigs that are available keyed by -// a human-friendly source. -func (c *Config) rawConfigs() map[string]*RawConfig { - result := make(map[string]*RawConfig) - for _, m := range c.Modules { - source := fmt.Sprintf("module '%s'", m.Name) - result[source] = m.RawConfig - } - - for _, pc := range c.ProviderConfigs { - source := fmt.Sprintf("provider config '%s'", pc.Name) - result[source] = pc.RawConfig - } - - for _, rc := range c.Resources { - source := fmt.Sprintf("resource '%s'", rc.Id()) - result[source+" count"] = rc.RawCount - result[source+" config"] = rc.RawConfig - - for i, p := range rc.Provisioners { - subsource := fmt.Sprintf( - "%s provisioner %s (#%d)", - source, p.Type, i+1) - result[subsource] = p.RawConfig - } - } - - for _, o := range c.Outputs { - source := fmt.Sprintf("output '%s'", o.Name) - result[source] = o.RawConfig - } - - return result -} - -func (c *Config) validateDependsOn( - n string, - v []string, - resources map[string]*Resource, - modules map[string]*Module) []error { - // Verify depends on points to resources that all exist - var errs []error - for _, d := range v { - // Check if we contain interpolations - rc, err := NewRawConfig(map[string]interface{}{ - "value": d, - }) - if err == nil && len(rc.Variables) > 0 { - errs = append(errs, fmt.Errorf( - "%s: depends on value cannot contain interpolations: %s", - n, d)) - continue - } - - // If it is a module, verify it is a module - if strings.HasPrefix(d, "module.") { - name := d[len("module."):] - if _, ok := modules[name]; !ok { - errs = append(errs, fmt.Errorf( - "%s: resource depends on non-existent module '%s'", - n, name)) - } - - continue - } - - // Check resources - if _, ok := resources[d]; !ok { - errs = append(errs, fmt.Errorf( - "%s: resource depends on non-existent resource '%s'", - n, d)) - } - } - - return errs -} - -func (m *Module) mergerName() string { - return m.Id() -} - -func (m *Module) mergerMerge(other merger) merger { - m2 := other.(*Module) - - result := *m - result.Name = m2.Name - result.RawConfig = result.RawConfig.merge(m2.RawConfig) - - if m2.Source != "" { - result.Source = m2.Source - } - - return &result -} - -func (o *Output) mergerName() string { - return o.Name -} - -func (o *Output) mergerMerge(m merger) merger { - o2 := m.(*Output) - - result := *o - result.Name = o2.Name - result.Description = o2.Description - result.RawConfig = result.RawConfig.merge(o2.RawConfig) - result.Sensitive = o2.Sensitive - result.DependsOn = o2.DependsOn - - return &result -} - -func (c *ProviderConfig) GoString() string { - return fmt.Sprintf("*%#v", *c) -} - -func (c *ProviderConfig) FullName() string { - if c.Alias == "" { - return c.Name - } - - return fmt.Sprintf("%s.%s", c.Name, c.Alias) -} - -func (c *ProviderConfig) mergerName() string { - return c.Name -} - -func (c *ProviderConfig) mergerMerge(m merger) merger { - c2 := m.(*ProviderConfig) - - result := *c - result.Name = c2.Name - result.RawConfig = result.RawConfig.merge(c2.RawConfig) - - if c2.Alias != "" { - result.Alias = c2.Alias - } - - return &result -} - -func (r *Resource) mergerName() string { - return r.Id() -} - -func (r *Resource) mergerMerge(m merger) merger { - r2 := m.(*Resource) - - result := *r - result.Mode = r2.Mode - result.Name = r2.Name - result.Type = r2.Type - result.RawConfig = result.RawConfig.merge(r2.RawConfig) - - if r2.RawCount.Value() != "1" { - result.RawCount = r2.RawCount - } - - if len(r2.Provisioners) > 0 { - result.Provisioners = r2.Provisioners - } - - return &result -} - -// Merge merges two variables to create a new third variable. -func (v *Variable) Merge(v2 *Variable) *Variable { - // Shallow copy the variable - result := *v - - // The names should be the same, but the second name always wins. - result.Name = v2.Name - - if v2.DeclaredType != "" { - result.DeclaredType = v2.DeclaredType - } - if v2.Default != nil { - result.Default = v2.Default - } - if v2.Description != "" { - result.Description = v2.Description - } - - return &result -} - -var typeStringMap = map[string]VariableType{ - "string": VariableTypeString, - "map": VariableTypeMap, - "list": VariableTypeList, -} - -// Type returns the type of variable this is. -func (v *Variable) Type() VariableType { - if v.DeclaredType != "" { - declaredType, ok := typeStringMap[v.DeclaredType] - if !ok { - return VariableTypeUnknown - } - - return declaredType - } - - return v.inferTypeFromDefault() -} - -// ValidateTypeAndDefault ensures that default variable value is compatible -// with the declared type (if one exists), and that the type is one which is -// known to Terraform -func (v *Variable) ValidateTypeAndDefault() error { - // If an explicit type is declared, ensure it is valid - if v.DeclaredType != "" { - if _, ok := typeStringMap[v.DeclaredType]; !ok { - validTypes := []string{} - for k := range typeStringMap { - validTypes = append(validTypes, k) - } - return fmt.Errorf( - "Variable '%s' type must be one of [%s] - '%s' is not a valid type", - v.Name, - strings.Join(validTypes, ", "), - v.DeclaredType, - ) - } - } - - if v.DeclaredType == "" || v.Default == nil { - return nil - } - - if v.inferTypeFromDefault() != v.Type() { - return fmt.Errorf("'%s' has a default value which is not of type '%s' (got '%s')", - v.Name, v.DeclaredType, v.inferTypeFromDefault().Printable()) - } - - return nil -} - -func (v *Variable) mergerName() string { - return v.Name -} - -func (v *Variable) mergerMerge(m merger) merger { - return v.Merge(m.(*Variable)) -} - -// Required tests whether a variable is required or not. -func (v *Variable) Required() bool { - return v.Default == nil -} - -// inferTypeFromDefault contains the logic for the old method of inferring -// variable types - we can also use this for validating that the declared -// type matches the type of the default value -func (v *Variable) inferTypeFromDefault() VariableType { - if v.Default == nil { - return VariableTypeString - } - - var s string - if err := hilmapstructure.WeakDecode(v.Default, &s); err == nil { - v.Default = s - return VariableTypeString - } - - var m map[string]interface{} - if err := hilmapstructure.WeakDecode(v.Default, &m); err == nil { - v.Default = m - return VariableTypeMap - } - - var l []interface{} - if err := hilmapstructure.WeakDecode(v.Default, &l); err == nil { - v.Default = l - return VariableTypeList - } - - return VariableTypeUnknown -} - -func (m ResourceMode) Taintable() bool { - switch m { - case ManagedResourceMode: - return true - case DataResourceMode: - return false - default: - panic(fmt.Errorf("unsupported ResourceMode value %s", m)) - } -} diff --git a/config/config_string.go b/config/config_string.go deleted file mode 100644 index a6933c2a5..000000000 --- a/config/config_string.go +++ /dev/null @@ -1,378 +0,0 @@ -package config - -import ( - "bytes" - "fmt" - "sort" - "strings" -) - -// TestString is a Stringer-like function that outputs a string that can -// be used to easily compare multiple Config structures in unit tests. -// -// This function has no practical use outside of unit tests and debugging. -func (c *Config) TestString() string { - if c == nil { - return "" - } - - var buf bytes.Buffer - if len(c.Modules) > 0 { - buf.WriteString("Modules:\n\n") - buf.WriteString(modulesStr(c.Modules)) - buf.WriteString("\n\n") - } - - if len(c.Variables) > 0 { - buf.WriteString("Variables:\n\n") - buf.WriteString(variablesStr(c.Variables)) - buf.WriteString("\n\n") - } - - if len(c.ProviderConfigs) > 0 { - buf.WriteString("Provider Configs:\n\n") - buf.WriteString(providerConfigsStr(c.ProviderConfigs)) - buf.WriteString("\n\n") - } - - if len(c.Resources) > 0 { - buf.WriteString("Resources:\n\n") - buf.WriteString(resourcesStr(c.Resources)) - buf.WriteString("\n\n") - } - - if len(c.Outputs) > 0 { - buf.WriteString("Outputs:\n\n") - buf.WriteString(outputsStr(c.Outputs)) - buf.WriteString("\n") - } - - return strings.TrimSpace(buf.String()) -} - -func terraformStr(t *Terraform) string { - result := "" - - if b := t.Backend; b != nil { - result += fmt.Sprintf("backend (%s)\n", b.Type) - - keys := make([]string, 0, len(b.RawConfig.Raw)) - for k, _ := range b.RawConfig.Raw { - keys = append(keys, k) - } - sort.Strings(keys) - - for _, k := range keys { - result += fmt.Sprintf(" %s\n", k) - } - } - - return strings.TrimSpace(result) -} - -func modulesStr(ms []*Module) string { - result := "" - order := make([]int, 0, len(ms)) - ks := make([]string, 0, len(ms)) - mapping := make(map[string]int) - for i, m := range ms { - k := m.Id() - ks = append(ks, k) - mapping[k] = i - } - sort.Strings(ks) - for _, k := range ks { - order = append(order, mapping[k]) - } - - for _, i := range order { - m := ms[i] - result += fmt.Sprintf("%s\n", m.Id()) - - ks := make([]string, 0, len(m.RawConfig.Raw)) - for k, _ := range m.RawConfig.Raw { - ks = append(ks, k) - } - sort.Strings(ks) - - result += fmt.Sprintf(" source = %s\n", m.Source) - - for _, k := range ks { - result += fmt.Sprintf(" %s\n", k) - } - } - - return strings.TrimSpace(result) -} - -func outputsStr(os []*Output) string { - ns := make([]string, 0, len(os)) - m := make(map[string]*Output) - for _, o := range os { - ns = append(ns, o.Name) - m[o.Name] = o - } - sort.Strings(ns) - - result := "" - for _, n := range ns { - o := m[n] - - result += fmt.Sprintf("%s\n", n) - - if len(o.DependsOn) > 0 { - result += fmt.Sprintf(" dependsOn\n") - for _, d := range o.DependsOn { - result += fmt.Sprintf(" %s\n", d) - } - } - - if len(o.RawConfig.Variables) > 0 { - result += fmt.Sprintf(" vars\n") - for _, rawV := range o.RawConfig.Variables { - kind := "unknown" - str := rawV.FullKey() - - switch rawV.(type) { - case *ResourceVariable: - kind = "resource" - case *UserVariable: - kind = "user" - } - - result += fmt.Sprintf(" %s: %s\n", kind, str) - } - } - - if o.Description != "" { - result += fmt.Sprintf(" description\n %s\n", o.Description) - } - } - - return strings.TrimSpace(result) -} - -func localsStr(ls []*Local) string { - ns := make([]string, 0, len(ls)) - m := make(map[string]*Local) - for _, l := range ls { - ns = append(ns, l.Name) - m[l.Name] = l - } - sort.Strings(ns) - - result := "" - for _, n := range ns { - l := m[n] - - result += fmt.Sprintf("%s\n", n) - - if len(l.RawConfig.Variables) > 0 { - result += fmt.Sprintf(" vars\n") - for _, rawV := range l.RawConfig.Variables { - kind := "unknown" - str := rawV.FullKey() - - switch rawV.(type) { - case *ResourceVariable: - kind = "resource" - case *UserVariable: - kind = "user" - } - - result += fmt.Sprintf(" %s: %s\n", kind, str) - } - } - } - - return strings.TrimSpace(result) -} - -// This helper turns a provider configs field into a deterministic -// string value for comparison in tests. -func providerConfigsStr(pcs []*ProviderConfig) string { - result := "" - - ns := make([]string, 0, len(pcs)) - m := make(map[string]*ProviderConfig) - for _, n := range pcs { - ns = append(ns, n.Name) - m[n.Name] = n - } - sort.Strings(ns) - - for _, n := range ns { - pc := m[n] - - result += fmt.Sprintf("%s\n", n) - - keys := make([]string, 0, len(pc.RawConfig.Raw)) - for k, _ := range pc.RawConfig.Raw { - keys = append(keys, k) - } - sort.Strings(keys) - - for _, k := range keys { - result += fmt.Sprintf(" %s\n", k) - } - - if len(pc.RawConfig.Variables) > 0 { - result += fmt.Sprintf(" vars\n") - for _, rawV := range pc.RawConfig.Variables { - kind := "unknown" - str := rawV.FullKey() - - switch rawV.(type) { - case *ResourceVariable: - kind = "resource" - case *UserVariable: - kind = "user" - } - - result += fmt.Sprintf(" %s: %s\n", kind, str) - } - } - } - - return strings.TrimSpace(result) -} - -// This helper turns a resources field into a deterministic -// string value for comparison in tests. -func resourcesStr(rs []*Resource) string { - result := "" - order := make([]int, 0, len(rs)) - ks := make([]string, 0, len(rs)) - mapping := make(map[string]int) - for i, r := range rs { - k := r.Id() - ks = append(ks, k) - mapping[k] = i - } - sort.Strings(ks) - for _, k := range ks { - order = append(order, mapping[k]) - } - - for _, i := range order { - r := rs[i] - result += fmt.Sprintf( - "%s (x%s)\n", - r.Id(), - r.RawCount.Value()) - - ks := make([]string, 0, len(r.RawConfig.Raw)) - for k, _ := range r.RawConfig.Raw { - ks = append(ks, k) - } - sort.Strings(ks) - - for _, k := range ks { - result += fmt.Sprintf(" %s\n", k) - } - - if len(r.Provisioners) > 0 { - result += fmt.Sprintf(" provisioners\n") - for _, p := range r.Provisioners { - when := "" - if p.When != ProvisionerWhenCreate { - when = fmt.Sprintf(" (%s)", p.When.String()) - } - - result += fmt.Sprintf(" %s%s\n", p.Type, when) - - if p.OnFailure != ProvisionerOnFailureFail { - result += fmt.Sprintf(" on_failure = %s\n", p.OnFailure.String()) - } - - ks := make([]string, 0, len(p.RawConfig.Raw)) - for k, _ := range p.RawConfig.Raw { - ks = append(ks, k) - } - sort.Strings(ks) - - for _, k := range ks { - result += fmt.Sprintf(" %s\n", k) - } - } - } - - if len(r.DependsOn) > 0 { - result += fmt.Sprintf(" dependsOn\n") - for _, d := range r.DependsOn { - result += fmt.Sprintf(" %s\n", d) - } - } - - if len(r.RawConfig.Variables) > 0 { - result += fmt.Sprintf(" vars\n") - - ks := make([]string, 0, len(r.RawConfig.Variables)) - for k, _ := range r.RawConfig.Variables { - ks = append(ks, k) - } - sort.Strings(ks) - - for _, k := range ks { - rawV := r.RawConfig.Variables[k] - kind := "unknown" - str := rawV.FullKey() - - switch rawV.(type) { - case *ResourceVariable: - kind = "resource" - case *UserVariable: - kind = "user" - } - - result += fmt.Sprintf(" %s: %s\n", kind, str) - } - } - } - - return strings.TrimSpace(result) -} - -// This helper turns a variables field into a deterministic -// string value for comparison in tests. -func variablesStr(vs []*Variable) string { - result := "" - ks := make([]string, 0, len(vs)) - m := make(map[string]*Variable) - for _, v := range vs { - ks = append(ks, v.Name) - m[v.Name] = v - } - sort.Strings(ks) - - for _, k := range ks { - v := m[k] - - required := "" - if v.Required() { - required = " (required)" - } - - declaredType := "" - if v.DeclaredType != "" { - declaredType = fmt.Sprintf(" (%s)", v.DeclaredType) - } - - if v.Default == nil || v.Default == "" { - v.Default = "<>" - } - if v.Description == "" { - v.Description = "<>" - } - - result += fmt.Sprintf( - "%s%s%s\n %v\n %s\n", - k, - required, - declaredType, - v.Default, - v.Description) - } - - return strings.TrimSpace(result) -} diff --git a/config/config_terraform.go b/config/config_terraform.go deleted file mode 100644 index 8535c9648..000000000 --- a/config/config_terraform.go +++ /dev/null @@ -1,117 +0,0 @@ -package config - -import ( - "fmt" - "strings" - - "github.com/hashicorp/go-version" - "github.com/mitchellh/hashstructure" -) - -// Terraform is the Terraform meta-configuration that can be present -// in configuration files for configuring Terraform itself. -type Terraform struct { - RequiredVersion string `hcl:"required_version"` // Required Terraform version (constraint) - Backend *Backend // See Backend struct docs -} - -// Validate performs the validation for just the Terraform configuration. -func (t *Terraform) Validate() []error { - var errs []error - - if raw := t.RequiredVersion; raw != "" { - // Check that the value has no interpolations - rc, err := NewRawConfig(map[string]interface{}{ - "root": raw, - }) - if err != nil { - errs = append(errs, fmt.Errorf( - "terraform.required_version: %s", err)) - } else if len(rc.Interpolations) > 0 { - errs = append(errs, fmt.Errorf( - "terraform.required_version: cannot contain interpolations")) - } else { - // Check it is valid - _, err := version.NewConstraint(raw) - if err != nil { - errs = append(errs, fmt.Errorf( - "terraform.required_version: invalid syntax: %s", err)) - } - } - } - - if t.Backend != nil { - errs = append(errs, t.Backend.Validate()...) - } - - return errs -} - -// Merge t with t2. -// Any conflicting fields are overwritten by t2. -func (t *Terraform) Merge(t2 *Terraform) { - if t2.RequiredVersion != "" { - t.RequiredVersion = t2.RequiredVersion - } - - if t2.Backend != nil { - t.Backend = t2.Backend - } -} - -// Backend is the configuration for the "backend" to use with Terraform. -// A backend is responsible for all major behavior of Terraform's core. -// The abstraction layer above the core (the "backend") allows for behavior -// such as remote operation. -type Backend struct { - Type string - RawConfig *RawConfig - - // Hash is a unique hash code representing the original configuration - // of the backend. This won't be recomputed unless Rehash is called. - Hash uint64 -} - -// Rehash returns a unique content hash for this backend's configuration -// as a uint64 value. -func (b *Backend) Rehash() uint64 { - // If we have no backend, the value is zero - if b == nil { - return 0 - } - - // Use hashstructure to hash only our type with the config. - code, err := hashstructure.Hash(map[string]interface{}{ - "type": b.Type, - "config": b.RawConfig.Raw, - }, nil) - - // This should never happen since we have just some basic primitives - // so panic if there is an error. - if err != nil { - panic(err) - } - - return code -} - -func (b *Backend) Validate() []error { - if len(b.RawConfig.Interpolations) > 0 { - return []error{fmt.Errorf(strings.TrimSpace(errBackendInterpolations))} - } - - return nil -} - -const errBackendInterpolations = ` -terraform.backend: configuration cannot contain interpolations - -The backend configuration is loaded by Terraform extremely early, before -the core of Terraform can be initialized. This is necessary because the backend -dictates the behavior of that core. The core is what handles interpolation -processing. Because of this, interpolations cannot be used in backend -configuration. - -If you'd like to parameterize backend configuration, we recommend using -partial configuration with the "-backend-config" flag to "terraform init". -` diff --git a/config/config_terraform_test.go b/config/config_terraform_test.go deleted file mode 100644 index e0da63e9d..000000000 --- a/config/config_terraform_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package config - -import ( - "fmt" - "testing" -) - -func TestBackendHash(t *testing.T) { - // WARNING: The codes below should _never_ change. If they change, it - // means that a future TF version may falsely recognize unchanged backend - // configuration as changed. Ultimately this should have no adverse - // affect but it is annoying for users and should be avoided if possible. - - cases := []struct { - Name string - Fixture string - Code uint64 - }{ - { - "no backend config", - "backend-hash-empty", - 0, - }, - - { - "backend config with only type", - "backend-hash-type-only", - 17852588448730441876, - }, - - { - "backend config with type and config", - "backend-hash-basic", - 10288498853650209002, - }, - } - - for i, tc := range cases { - t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { - c := testConfig(t, tc.Fixture) - err := c.Validate() - if err != nil { - t.Fatalf("err: %s", err) - } - - var actual uint64 - if c.Terraform != nil && c.Terraform.Backend != nil { - actual = c.Terraform.Backend.Hash - } - if actual != tc.Code { - t.Fatalf("bad: %d != %d", actual, tc.Code) - } - }) - } -} diff --git a/config/config_test.go b/config/config_test.go deleted file mode 100644 index 926c1d9b4..000000000 --- a/config/config_test.go +++ /dev/null @@ -1,783 +0,0 @@ -package config - -import ( - "flag" - "fmt" - "io/ioutil" - "log" - "os" - "path/filepath" - "reflect" - "strings" - "testing" - - "github.com/hashicorp/hil/ast" - "github.com/hashicorp/terraform/helper/logging" -) - -// This is the directory where our test fixtures are. -const fixtureDir = "./testdata" - -func TestMain(m *testing.M) { - flag.Parse() - if testing.Verbose() { - // if we're verbose, use the logging requested by TF_LOG - logging.SetOutput() - } else { - // otherwise silence all logs - log.SetOutput(ioutil.Discard) - } - - os.Exit(m.Run()) -} - -func TestConfigCopy(t *testing.T) { - c := testConfig(t, "copy-basic") - rOrig := c.Resources[0] - rCopy := rOrig.Copy() - - if rCopy.Name != rOrig.Name { - t.Fatalf("Expected names to equal: %q <=> %q", rCopy.Name, rOrig.Name) - } - - if rCopy.Type != rOrig.Type { - t.Fatalf("Expected types to equal: %q <=> %q", rCopy.Type, rOrig.Type) - } - - origCount := rOrig.RawCount.Config()["count"] - rCopy.RawCount.Config()["count"] = "5" - if rOrig.RawCount.Config()["count"] != origCount { - t.Fatalf("Expected RawCount to be copied, but it behaves like a ref!") - } - - rCopy.RawConfig.Config()["newfield"] = "hello" - if rOrig.RawConfig.Config()["newfield"] == "hello" { - t.Fatalf("Expected RawConfig to be copied, but it behaves like a ref!") - } - - rCopy.Provisioners = append(rCopy.Provisioners, &Provisioner{}) - if len(rOrig.Provisioners) == len(rCopy.Provisioners) { - t.Fatalf("Expected Provisioners to be copied, but it behaves like a ref!") - } - - if rCopy.Provider != rOrig.Provider { - t.Fatalf("Expected providers to equal: %q <=> %q", - rCopy.Provider, rOrig.Provider) - } - - rCopy.DependsOn[0] = "gotchya" - if rOrig.DependsOn[0] == rCopy.DependsOn[0] { - t.Fatalf("Expected DependsOn to be copied, but it behaves like a ref!") - } - - rCopy.Lifecycle.IgnoreChanges[0] = "gotchya" - if rOrig.Lifecycle.IgnoreChanges[0] == rCopy.Lifecycle.IgnoreChanges[0] { - t.Fatalf("Expected Lifecycle to be copied, but it behaves like a ref!") - } - -} - -func TestConfigCount(t *testing.T) { - c := testConfig(t, "count-int") - actual, err := c.Resources[0].Count() - if err != nil { - t.Fatalf("err: %s", err) - } - if actual != 5 { - t.Fatalf("bad: %#v", actual) - } -} - -func TestConfigCount_string(t *testing.T) { - c := testConfig(t, "count-string") - actual, err := c.Resources[0].Count() - if err != nil { - t.Fatalf("err: %s", err) - } - if actual != 5 { - t.Fatalf("bad: %#v", actual) - } -} - -// Terraform GH-11800 -func TestConfigCount_list(t *testing.T) { - c := testConfig(t, "count-list") - - // The key is to interpolate so it doesn't fail parsing - c.Resources[0].RawCount.Interpolate(map[string]ast.Variable{ - "var.list": ast.Variable{ - Value: []ast.Variable{}, - Type: ast.TypeList, - }, - }) - - _, err := c.Resources[0].Count() - if err == nil { - t.Fatal("should error") - } -} - -func TestConfigCount_var(t *testing.T) { - c := testConfig(t, "count-var") - _, err := c.Resources[0].Count() - if err == nil { - t.Fatalf("should error") - } -} - -func TestConfig_emptyCollections(t *testing.T) { - c := testConfig(t, "empty-collections") - if len(c.Variables) != 3 { - t.Fatalf("bad: expected 3 variables, got %d", len(c.Variables)) - } - for _, variable := range c.Variables { - switch variable.Name { - case "empty_string": - if variable.Default != "" { - t.Fatalf("bad: wrong default %q for variable empty_string", variable.Default) - } - case "empty_map": - if !reflect.DeepEqual(variable.Default, map[string]interface{}{}) { - t.Fatalf("bad: wrong default %#v for variable empty_map", variable.Default) - } - case "empty_list": - if !reflect.DeepEqual(variable.Default, []interface{}{}) { - t.Fatalf("bad: wrong default %#v for variable empty_list", variable.Default) - } - default: - t.Fatalf("Unexpected variable: %s", variable.Name) - } - } -} - -// This table test is the preferred way to test validation of configuration. -// There are dozens of functions below which do not follow this that are -// there mostly historically. They should be converted at some point. -func TestConfigValidate_table(t *testing.T) { - cases := []struct { - Name string - Fixture string - Err bool - ErrString string - }{ - { - "basic good", - "validate-good", - false, - "", - }, - - { - "depends on module", - "validate-depends-on-module", - false, - "", - }, - - { - "depends on non-existent module", - "validate-depends-on-bad-module", - true, - "non-existent module 'foo'", - }, - - { - "data source with provisioners", - "validate-data-provisioner", - true, - "data sources cannot have", - }, - - { - "basic provisioners", - "validate-basic-provisioners", - false, - "", - }, - - { - "backend config with interpolations", - "validate-backend-interpolate", - true, - "cannot contain interp", - }, - { - "nested types in variable default", - "validate-var-nested", - false, - "", - }, - { - "provider with valid version constraint", - "provider-version", - false, - "", - }, - { - "provider with invalid version constraint", - "provider-version-invalid", - true, - "not a valid version constraint", - }, - { - "invalid provider name in module block", - "validate-missing-provider", - true, - "cannot pass non-existent provider", - }, - } - - for i, tc := range cases { - t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { - c := testConfig(t, tc.Fixture) - diags := c.Validate() - if diags.HasErrors() != tc.Err { - t.Fatalf("err: %s", diags.Err().Error()) - } - if diags.HasErrors() { - gotErr := diags.Err().Error() - if tc.ErrString != "" && !strings.Contains(gotErr, tc.ErrString) { - t.Fatalf("expected err to contain: %s\n\ngot: %s", tc.ErrString, gotErr) - } - - return - } - }) - } - -} - -func TestConfigValidate_tfVersion(t *testing.T) { - c := testConfig(t, "validate-tf-version") - if err := c.Validate(); err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestConfigValidate_tfVersionBad(t *testing.T) { - c := testConfig(t, "validate-bad-tf-version") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_tfVersionInterpolations(t *testing.T) { - c := testConfig(t, "validate-tf-version-interp") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_badDependsOn(t *testing.T) { - c := testConfig(t, "validate-bad-depends-on") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_countInt(t *testing.T) { - c := testConfig(t, "validate-count-int") - if err := c.Validate(); err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestConfigValidate_countBadContext(t *testing.T) { - c := testConfig(t, "validate-count-bad-context") - - diags := c.Validate() - - expected := []string{ - "output \"no_count_in_output\": count variables are only valid within resources", - "module \"no_count_in_module\": count variables are only valid within resources", - } - for _, exp := range expected { - errStr := diags.Err().Error() - if !strings.Contains(errStr, exp) { - t.Errorf("expected: %q,\nto contain: %q", errStr, exp) - } - } -} - -func TestConfigValidate_countCountVar(t *testing.T) { - c := testConfig(t, "validate-count-count-var") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_countNotInt(t *testing.T) { - c := testConfig(t, "validate-count-not-int") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_countUserVar(t *testing.T) { - c := testConfig(t, "validate-count-user-var") - if err := c.Validate(); err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestConfigValidate_countLocalValue(t *testing.T) { - c := testConfig(t, "validate-local-value-count") - if err := c.Validate(); err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestConfigValidate_countVar(t *testing.T) { - c := testConfig(t, "validate-count-var") - if err := c.Validate(); err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestConfigValidate_countVarInvalid(t *testing.T) { - c := testConfig(t, "validate-count-var-invalid") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_countVarUnknown(t *testing.T) { - c := testConfig(t, "validate-count-var-unknown") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_dependsOnVar(t *testing.T) { - c := testConfig(t, "validate-depends-on-var") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_dupModule(t *testing.T) { - c := testConfig(t, "validate-dup-module") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_dupResource(t *testing.T) { - c := testConfig(t, "validate-dup-resource") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_ignoreChanges(t *testing.T) { - c := testConfig(t, "validate-ignore-changes") - if err := c.Validate(); err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestConfigValidate_ignoreChangesBad(t *testing.T) { - c := testConfig(t, "validate-ignore-changes-bad") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_ignoreChangesInterpolate(t *testing.T) { - c := testConfig(t, "validate-ignore-changes-interpolate") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_moduleNameBad(t *testing.T) { - c := testConfig(t, "validate-module-name-bad") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_moduleSourceVar(t *testing.T) { - c := testConfig(t, "validate-module-source-var") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_moduleVarInt(t *testing.T) { - c := testConfig(t, "validate-module-var-int") - if err := c.Validate(); err != nil { - t.Fatalf("should be valid: %s", err) - } -} - -func TestConfigValidate_moduleVarMap(t *testing.T) { - c := testConfig(t, "validate-module-var-map") - if err := c.Validate(); err != nil { - t.Fatalf("should be valid: %s", err) - } -} - -func TestConfigValidate_moduleVarList(t *testing.T) { - c := testConfig(t, "validate-module-var-list") - if err := c.Validate(); err != nil { - t.Fatalf("should be valid: %s", err) - } -} - -func TestConfigValidate_moduleVarSelf(t *testing.T) { - c := testConfig(t, "validate-module-var-self") - if err := c.Validate(); err == nil { - t.Fatal("should be invalid") - } -} - -func TestConfigValidate_nil(t *testing.T) { - var c Config - if err := c.Validate(); err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestConfigValidate_outputBadField(t *testing.T) { - c := testConfig(t, "validate-output-bad-field") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_outputDescription(t *testing.T) { - c := testConfig(t, "validate-output-description") - if err := c.Validate(); err != nil { - t.Fatalf("err: %s", err) - } - if len(c.Outputs) != 1 { - t.Fatalf("got %d outputs; want 1", len(c.Outputs)) - } - if got, want := "Number 5", c.Outputs[0].Description; got != want { - t.Fatalf("got description %q; want %q", got, want) - } -} - -func TestConfigValidate_outputDuplicate(t *testing.T) { - c := testConfig(t, "validate-output-dup") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_pathVar(t *testing.T) { - c := testConfig(t, "validate-path-var") - if err := c.Validate(); err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestConfigValidate_pathVarInvalid(t *testing.T) { - c := testConfig(t, "validate-path-var-invalid") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_providerMulti(t *testing.T) { - c := testConfig(t, "validate-provider-multi") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_providerMultiGood(t *testing.T) { - c := testConfig(t, "validate-provider-multi-good") - if err := c.Validate(); err != nil { - t.Fatalf("should be valid: %s", err) - } -} - -func TestConfigValidate_providerMultiRefGood(t *testing.T) { - c := testConfig(t, "validate-provider-multi-ref-good") - if err := c.Validate(); err != nil { - t.Fatalf("should be valid: %s", err) - } -} - -func TestConfigValidate_provConnSplatOther(t *testing.T) { - c := testConfig(t, "validate-prov-conn-splat-other") - if err := c.Validate(); err != nil { - t.Fatalf("should be valid: %s", err) - } -} - -func TestConfigValidate_provConnSplatSelf(t *testing.T) { - c := testConfig(t, "validate-prov-conn-splat-self") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_provSplatOther(t *testing.T) { - c := testConfig(t, "validate-prov-splat-other") - if err := c.Validate(); err != nil { - t.Fatalf("should be valid: %s", err) - } -} - -func TestConfigValidate_provSplatSelf(t *testing.T) { - c := testConfig(t, "validate-prov-splat-self") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_resourceProvVarSelf(t *testing.T) { - c := testConfig(t, "validate-resource-prov-self") - if err := c.Validate(); err != nil { - t.Fatalf("should be valid: %s", err) - } -} - -func TestConfigValidate_resourceVarSelf(t *testing.T) { - c := testConfig(t, "validate-resource-self") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_unknownThing(t *testing.T) { - c := testConfig(t, "validate-unknownthing") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_unknownResourceVar(t *testing.T) { - c := testConfig(t, "validate-unknown-resource-var") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_unknownResourceVar_output(t *testing.T) { - c := testConfig(t, "validate-unknown-resource-var-output") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_unknownVar(t *testing.T) { - c := testConfig(t, "validate-unknownvar") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_unknownVarCount(t *testing.T) { - c := testConfig(t, "validate-unknownvar-count") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_varDefault(t *testing.T) { - c := testConfig(t, "validate-var-default") - if err := c.Validate(); err != nil { - t.Fatalf("should be valid: %s", err) - } -} - -func TestConfigValidate_varDefaultListType(t *testing.T) { - c := testConfig(t, "validate-var-default-list-type") - if err := c.Validate(); err != nil { - t.Fatalf("should be valid: %s", err) - } -} - -func TestConfigValidate_varDefaultInterpolate(t *testing.T) { - c := testConfig(t, "validate-var-default-interpolate") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_varDefaultInterpolateEscaped(t *testing.T) { - c := testConfig(t, "validate-var-default-interpolate-escaped") - if err := c.Validate(); err != nil { - t.Fatalf("should be valid, but got err: %s", err) - } -} - -func TestConfigValidate_varDup(t *testing.T) { - c := testConfig(t, "validate-var-dup") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_varMultiExactNonSlice(t *testing.T) { - c := testConfig(t, "validate-var-multi-exact-non-slice") - if err := c.Validate(); err != nil { - t.Fatalf("should be valid: %s", err) - } -} - -func TestConfigValidate_varMultiFunctionCall(t *testing.T) { - c := testConfig(t, "validate-var-multi-func") - if err := c.Validate(); err != nil { - t.Fatalf("should be valid: %s", err) - } -} - -func TestConfigValidate_varModule(t *testing.T) { - c := testConfig(t, "validate-var-module") - if err := c.Validate(); err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestConfigValidate_varModuleInvalid(t *testing.T) { - c := testConfig(t, "validate-var-module-invalid") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestConfigValidate_varProviderVersionInvalid(t *testing.T) { - c := testConfig(t, "validate-provider-version-invalid") - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestNameRegexp(t *testing.T) { - cases := []struct { - Input string - Match bool - }{ - {"hello", true}, - {"foo-bar", true}, - {"foo_bar", true}, - {"_hello", true}, - {"foo bar", false}, - {"foo.bar", false}, - } - - for _, tc := range cases { - if NameRegexp.Match([]byte(tc.Input)) != tc.Match { - t.Fatalf("Input: %s\n\nExpected: %#v", tc.Input, tc.Match) - } - } -} - -func TestConfigValidate_localValuesMultiFile(t *testing.T) { - c, err := LoadDir(filepath.Join(fixtureDir, "validate-local-multi-file")) - if err != nil { - t.Fatalf("unexpected error during load: %s", err) - } - if err := c.Validate(); err != nil { - t.Fatalf("unexpected error from validate: %s", err) - } - if len(c.Locals) != 1 { - t.Fatalf("got 0 locals; want 1") - } - if got, want := c.Locals[0].Name, "test"; got != want { - t.Errorf("wrong local name\ngot: %#v\nwant: %#v", got, want) - } -} - -func TestProviderConfigName(t *testing.T) { - pcs := []*ProviderConfig{ - &ProviderConfig{Name: "aw"}, - &ProviderConfig{Name: "aws"}, - &ProviderConfig{Name: "a"}, - &ProviderConfig{Name: "gce_"}, - } - - n := ProviderConfigName("aws_instance", pcs) - if n != "aws" { - t.Fatalf("bad: %s", n) - } -} - -func testConfig(t *testing.T, name string) *Config { - c, err := LoadFile(filepath.Join(fixtureDir, name, "main.tf")) - if err != nil { - t.Fatalf("file: %s\n\nerr: %s", name, err) - } - - return c -} - -func TestConfigDataCount(t *testing.T) { - c := testConfig(t, "data-count") - actual, err := c.Resources[0].Count() - if err != nil { - t.Fatalf("err: %s", err) - } - if actual != 5 { - t.Fatalf("bad: %#v", actual) - } - - // we need to make sure "count" has been removed from the RawConfig, since - // it's not a real key and won't validate. - if _, ok := c.Resources[0].RawConfig.Raw["count"]; ok { - t.Fatal("count key still exists in RawConfig") - } -} - -func TestConfigProviderVersion(t *testing.T) { - c := testConfig(t, "provider-version") - - if len(c.ProviderConfigs) != 1 { - t.Fatal("expected 1 provider") - } - - p := c.ProviderConfigs[0] - if p.Name != "aws" { - t.Fatalf("expected provider name 'aws', got %q", p.Name) - } - - if p.Version != "0.0.1" { - t.Fatalf("expected providers version '0.0.1', got %q", p.Version) - } - - if _, ok := p.RawConfig.Raw["version"]; ok { - t.Fatal("'version' should not exist in raw config") - } -} - -func TestConfigModuleProviders(t *testing.T) { - c := testConfig(t, "module-providers") - - if len(c.Modules) != 1 { - t.Fatalf("expected 1 module, got %d", len(c.Modules)) - } - - expected := map[string]string{ - "aws": "aws.foo", - } - - got := c.Modules[0].Providers - - if !reflect.DeepEqual(expected, got) { - t.Fatalf("exptected providers %#v, got providers %#v", expected, got) - } -} - -func TestValidateOutputErrorWarnings(t *testing.T) { - // TODO: remove this in 0.12 - c := testConfig(t, "output-warnings") - - diags := c.Validate() - if diags.HasErrors() { - t.Fatal("config should not have errors:", diags) - } - if len(diags) != 2 { - t.Fatalf("should have 2 warnings, got %d:\n%s", len(diags), diags) - } - - // this fixture has no explicit count, and should have no warning - c = testConfig(t, "output-no-warnings") - if err := c.Validate(); err != nil { - t.Fatal("config should have no warnings or errors") - } -} diff --git a/config/config_tree.go b/config/config_tree.go deleted file mode 100644 index 08dc0fe90..000000000 --- a/config/config_tree.go +++ /dev/null @@ -1,43 +0,0 @@ -package config - -// configTree represents a tree of configurations where the root is the -// first file and its children are the configurations it has imported. -type configTree struct { - Path string - Config *Config - Children []*configTree -} - -// Flatten flattens the entire tree down to a single merged Config -// structure. -func (t *configTree) Flatten() (*Config, error) { - // No children is easy: we're already merged! - if len(t.Children) == 0 { - return t.Config, nil - } - - // Depth-first, merge all the children first. - childConfigs := make([]*Config, len(t.Children)) - for i, ct := range t.Children { - c, err := ct.Flatten() - if err != nil { - return nil, err - } - - childConfigs[i] = c - } - - // Merge all the children in order - config := childConfigs[0] - childConfigs = childConfigs[1:] - for _, config2 := range childConfigs { - var err error - config, err = Merge(config, config2) - if err != nil { - return nil, err - } - } - - // Merge the final merged child config with our own - return Merge(config, t.Config) -} diff --git a/config/import_tree.go b/config/import_tree.go deleted file mode 100644 index 08cbc7736..000000000 --- a/config/import_tree.go +++ /dev/null @@ -1,151 +0,0 @@ -package config - -import ( - "bufio" - "fmt" - "io" - "os" - - "github.com/hashicorp/errwrap" -) - -// configurable is an interface that must be implemented by any configuration -// formats of Terraform in order to return a *Config. -type configurable interface { - Config() (*Config, error) -} - -// importTree is the result of the first-pass load of the configuration -// files. It is a tree of raw configurables and then any children (their -// imports). -// -// An importTree can be turned into a configTree. -type importTree struct { - Path string - Raw configurable - Children []*importTree -} - -// This is the function type that must be implemented by the configuration -// file loader to turn a single file into a configurable and any additional -// imports. -type fileLoaderFunc func(path string) (configurable, []string, error) - -// Set this to a non-empty value at link time to enable the HCL2 experiment. -// This is not currently enabled for release builds. -// -// For example: -// go install -ldflags="-X github.com/hashicorp/terraform/config.enableHCL2Experiment=true" github.com/hashicorp/terraform -var enableHCL2Experiment = "" - -// loadTree takes a single file and loads the entire importTree for that -// file. This function detects what kind of configuration file it is an -// executes the proper fileLoaderFunc. -func loadTree(root string) (*importTree, error) { - var f fileLoaderFunc - - // HCL2 experiment is currently activated at build time via the linker. - // See the comment on this variable for more information. - if enableHCL2Experiment == "" { - // Main-line behavior: always use the original HCL parser - switch ext(root) { - case ".tf", ".tf.json": - f = loadFileHcl - default: - } - } else { - // Experimental behavior: use the HCL2 parser if the opt-in comment - // is present. - switch ext(root) { - case ".tf": - // We need to sniff the file for the opt-in comment line to decide - // if the file is participating in the HCL2 experiment. - cf, err := os.Open(root) - if err != nil { - return nil, err - } - sc := bufio.NewScanner(cf) - for sc.Scan() { - if sc.Text() == "#terraform:hcl2" { - f = globalHCL2Loader.loadFile - } - } - if f == nil { - f = loadFileHcl - } - case ".tf.json": - f = loadFileHcl - default: - } - } - - if f == nil { - return nil, fmt.Errorf( - "%s: unknown configuration format. Use '.tf' or '.tf.json' extension", - root) - } - - c, imps, err := f(root) - if err != nil { - return nil, err - } - - children := make([]*importTree, len(imps)) - for i, imp := range imps { - t, err := loadTree(imp) - if err != nil { - return nil, err - } - - children[i] = t - } - - return &importTree{ - Path: root, - Raw: c, - Children: children, - }, nil -} - -// Close releases any resources we might be holding open for the importTree. -// -// This can safely be called even while ConfigTree results are alive. The -// importTree is not bound to these. -func (t *importTree) Close() error { - if c, ok := t.Raw.(io.Closer); ok { - c.Close() - } - for _, ct := range t.Children { - ct.Close() - } - - return nil -} - -// ConfigTree traverses the importTree and turns each node into a *Config -// object, ultimately returning a *configTree. -func (t *importTree) ConfigTree() (*configTree, error) { - config, err := t.Raw.Config() - if err != nil { - return nil, errwrap.Wrapf(fmt.Sprintf("Error loading %s: {{err}}", t.Path), err) - } - - // Build our result - result := &configTree{ - Path: t.Path, - Config: config, - } - - // Build the config trees for the children - result.Children = make([]*configTree, len(t.Children)) - for i, ct := range t.Children { - t, err := ct.ConfigTree() - if err != nil { - return nil, err - } - - result.Children[i] = t - } - - return result, nil -} diff --git a/config/import_tree_test.go b/config/import_tree_test.go deleted file mode 100644 index 53b778160..000000000 --- a/config/import_tree_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package config - -import ( - "testing" -) - -func TestImportTreeHCL2Experiment(t *testing.T) { - // Can only run this test if we're built with the experiment enabled. - // Enable this test by passing the following option to "go test": - // -ldflags="-X github.com/hashicorp/terraform/config.enableHCL2Experiment=true" - // See the comment associated with this flag variable for more information. - if enableHCL2Experiment == "" { - t.Skip("HCL2 experiment is not enabled") - } - - t.Run("HCL not opted in", func(t *testing.T) { - // .tf file without opt-in should use the old HCL parser - imp, err := loadTree("testdata/hcl2-experiment-switch/not-opted-in.tf") - if err != nil { - t.Fatal(err) - } - - tree, err := imp.ConfigTree() - if err != nil { - t.Fatalf("unexpected error loading not-opted-in.tf: %s", err) - } - - cfg := tree.Config - if got, want := len(cfg.Locals), 1; got != want { - t.Fatalf("wrong number of locals %#v; want %#v", got, want) - } - if cfg.Locals[0].RawConfig.Raw == nil { - // Having RawConfig.Raw indicates the old loader - t.Fatal("local has no RawConfig.Raw") - } - }) - - t.Run("HCL opted in", func(t *testing.T) { - // .tf file with opt-in should use the new HCL2 parser - imp, err := loadTree("testdata/hcl2-experiment-switch/opted-in.tf") - if err != nil { - t.Fatal(err) - } - - tree, err := imp.ConfigTree() - if err != nil { - t.Fatalf("unexpected error loading opted-in.tf: %s", err) - } - - cfg := tree.Config - if got, want := len(cfg.Locals), 1; got != want { - t.Fatalf("wrong number of locals %#v; want %#v", got, want) - } - if cfg.Locals[0].RawConfig.Body == nil { - // Having RawConfig.Body indicates the new loader - t.Fatal("local has no RawConfig.Body") - } - }) - - t.Run("JSON ineligible", func(t *testing.T) { - // .tf.json file should always use the old HCL parser - imp, err := loadTree("testdata/hcl2-experiment-switch/not-eligible.tf.json") - if err != nil { - t.Fatal(err) - } - - tree, err := imp.ConfigTree() - if err != nil { - t.Fatalf("unexpected error loading not-eligible.tf.json: %s", err) - } - - cfg := tree.Config - if got, want := len(cfg.Locals), 1; got != want { - t.Fatalf("wrong number of locals %#v; want %#v", got, want) - } - if cfg.Locals[0].RawConfig.Raw == nil { - // Having RawConfig.Raw indicates the old loader - t.Fatal("local has no RawConfig.Raw") - } - }) -} diff --git a/config/loader.go b/config/loader.go deleted file mode 100644 index 612e25b9e..000000000 --- a/config/loader.go +++ /dev/null @@ -1,212 +0,0 @@ -package config - -import ( - "encoding/json" - "fmt" - "io" - "os" - "path/filepath" - "sort" - "strings" - - "github.com/hashicorp/hcl" -) - -// ErrNoConfigsFound is the error returned by LoadDir if no -// Terraform configuration files were found in the given directory. -type ErrNoConfigsFound struct { - Dir string -} - -func (e ErrNoConfigsFound) Error() string { - return fmt.Sprintf( - "No Terraform configuration files found in directory: %s", - e.Dir) -} - -// LoadJSON loads a single Terraform configuration from a given JSON document. -// -// The document must be a complete Terraform configuration. This function will -// NOT try to load any additional modules so only the given document is loaded. -func LoadJSON(raw json.RawMessage) (*Config, error) { - obj, err := hcl.Parse(string(raw)) - if err != nil { - return nil, fmt.Errorf( - "Error parsing JSON document as HCL: %s", err) - } - - // Start building the result - hclConfig := &hclConfigurable{ - Root: obj, - } - - return hclConfig.Config() -} - -// LoadFile loads the Terraform configuration from a given file. -// -// This file can be any format that Terraform recognizes, and import any -// other format that Terraform recognizes. -func LoadFile(path string) (*Config, error) { - importTree, err := loadTree(path) - if err != nil { - return nil, err - } - - configTree, err := importTree.ConfigTree() - - // Close the importTree now so that we can clear resources as quickly - // as possible. - importTree.Close() - - if err != nil { - return nil, err - } - - return configTree.Flatten() -} - -// LoadDir loads all the Terraform configuration files in a single -// directory and appends them together. -// -// Special files known as "override files" can also be present, which -// are merged into the loaded configuration. That is, the non-override -// files are loaded first to create the configuration. Then, the overrides -// are merged into the configuration to create the final configuration. -// -// Files are loaded in lexical order. -func LoadDir(root string) (*Config, error) { - files, overrides, err := dirFiles(root) - if err != nil { - return nil, err - } - if len(files) == 0 && len(overrides) == 0 { - return nil, &ErrNoConfigsFound{Dir: root} - } - - // Determine the absolute path to the directory. - rootAbs, err := filepath.Abs(root) - if err != nil { - return nil, err - } - - var result *Config - - // Sort the files and overrides so we have a deterministic order - sort.Strings(files) - sort.Strings(overrides) - - // Load all the regular files, append them to each other. - for _, f := range files { - c, err := LoadFile(f) - if err != nil { - return nil, err - } - - if result != nil { - result, err = Append(result, c) - if err != nil { - return nil, err - } - } else { - result = c - } - } - if len(files) == 0 { - result = &Config{} - } - - // Load all the overrides, and merge them into the config - for _, f := range overrides { - c, err := LoadFile(f) - if err != nil { - return nil, err - } - - result, err = Merge(result, c) - if err != nil { - return nil, err - } - } - - // Mark the directory - result.Dir = rootAbs - - return result, nil -} - -// Ext returns the Terraform configuration extension of the given -// path, or a blank string if it is an invalid function. -func ext(path string) string { - if strings.HasSuffix(path, ".tf") { - return ".tf" - } else if strings.HasSuffix(path, ".tf.json") { - return ".tf.json" - } else { - return "" - } -} - -func dirFiles(dir string) ([]string, []string, error) { - f, err := os.Open(dir) - if err != nil { - return nil, nil, err - } - defer f.Close() - - fi, err := f.Stat() - if err != nil { - return nil, nil, err - } - if !fi.IsDir() { - return nil, nil, fmt.Errorf( - "configuration path must be a directory: %s", - dir) - } - - var files, overrides []string - err = nil - for err != io.EOF { - var fis []os.FileInfo - fis, err = f.Readdir(128) - if err != nil && err != io.EOF { - return nil, nil, err - } - - for _, fi := range fis { - // Ignore directories - if fi.IsDir() { - continue - } - - // Only care about files that are valid to load - name := fi.Name() - extValue := ext(name) - if extValue == "" || IsIgnoredFile(name) { - continue - } - - // Determine if we're dealing with an override - nameNoExt := name[:len(name)-len(extValue)] - override := nameNoExt == "override" || - strings.HasSuffix(nameNoExt, "_override") - - path := filepath.Join(dir, name) - if override { - overrides = append(overrides, path) - } else { - files = append(files, path) - } - } - } - - return files, overrides, nil -} - -// IsIgnoredFile returns true or false depending on whether the -// provided file name is a file that should be ignored. -func IsIgnoredFile(name string) bool { - return strings.HasPrefix(name, ".") || // Unix-like hidden files - strings.HasSuffix(name, "~") || // vim - strings.HasPrefix(name, "#") && strings.HasSuffix(name, "#") // emacs -} diff --git a/config/loader_hcl.go b/config/loader_hcl.go deleted file mode 100644 index 68cffe2cc..000000000 --- a/config/loader_hcl.go +++ /dev/null @@ -1,1270 +0,0 @@ -package config - -import ( - "fmt" - "io/ioutil" - - "github.com/hashicorp/go-multierror" - "github.com/hashicorp/hcl" - "github.com/hashicorp/hcl/hcl/ast" - "github.com/mitchellh/mapstructure" -) - -// hclConfigurable is an implementation of configurable that knows -// how to turn HCL configuration into a *Config object. -type hclConfigurable struct { - File string - Root *ast.File -} - -var ReservedDataSourceFields = []string{ - "connection", - "count", - "depends_on", - "lifecycle", - "provider", - "provisioner", -} - -var ReservedResourceFields = []string{ - "connection", - "count", - "depends_on", - "id", - "lifecycle", - "provider", - "provisioner", -} - -var ReservedProviderFields = []string{ - "alias", - "version", -} - -func (t *hclConfigurable) Config() (*Config, error) { - validKeys := map[string]struct{}{ - "atlas": struct{}{}, - "data": struct{}{}, - "locals": struct{}{}, - "module": struct{}{}, - "output": struct{}{}, - "provider": struct{}{}, - "resource": struct{}{}, - "terraform": struct{}{}, - "variable": struct{}{}, - } - - // Top-level item should be the object list - list, ok := t.Root.Node.(*ast.ObjectList) - if !ok { - return nil, fmt.Errorf("error parsing: file doesn't contain a root object") - } - - // Start building up the actual configuration. - config := new(Config) - - // Terraform config - if o := list.Filter("terraform"); len(o.Items) > 0 { - var err error - config.Terraform, err = loadTerraformHcl(o) - if err != nil { - return nil, err - } - } - - // Build the variables - if vars := list.Filter("variable"); len(vars.Items) > 0 { - var err error - config.Variables, err = loadVariablesHcl(vars) - if err != nil { - return nil, err - } - } - - // Build local values - if locals := list.Filter("locals"); len(locals.Items) > 0 { - var err error - config.Locals, err = loadLocalsHcl(locals) - if err != nil { - return nil, err - } - } - - // Get Atlas configuration - if atlas := list.Filter("atlas"); len(atlas.Items) > 0 { - var err error - config.Atlas, err = loadAtlasHcl(atlas) - if err != nil { - return nil, err - } - } - - // Build the modules - if modules := list.Filter("module"); len(modules.Items) > 0 { - var err error - config.Modules, err = loadModulesHcl(modules) - if err != nil { - return nil, err - } - } - - // Build the provider configs - if providers := list.Filter("provider"); len(providers.Items) > 0 { - var err error - config.ProviderConfigs, err = loadProvidersHcl(providers) - if err != nil { - return nil, err - } - } - - // Build the resources - { - var err error - managedResourceConfigs := list.Filter("resource") - dataResourceConfigs := list.Filter("data") - - config.Resources = make( - []*Resource, 0, - len(managedResourceConfigs.Items)+len(dataResourceConfigs.Items), - ) - - managedResources, err := loadManagedResourcesHcl(managedResourceConfigs) - if err != nil { - return nil, err - } - dataResources, err := loadDataResourcesHcl(dataResourceConfigs) - if err != nil { - return nil, err - } - - config.Resources = append(config.Resources, dataResources...) - config.Resources = append(config.Resources, managedResources...) - } - - // Build the outputs - if outputs := list.Filter("output"); len(outputs.Items) > 0 { - var err error - config.Outputs, err = loadOutputsHcl(outputs) - if err != nil { - return nil, err - } - } - - // Check for invalid keys - for _, item := range list.Items { - if len(item.Keys) == 0 { - // Not sure how this would happen, but let's avoid a panic - continue - } - - k := item.Keys[0].Token.Value().(string) - if _, ok := validKeys[k]; ok { - continue - } - - config.unknownKeys = append(config.unknownKeys, k) - } - - return config, nil -} - -// loadFileHcl is a fileLoaderFunc that knows how to read HCL -// files and turn them into hclConfigurables. -func loadFileHcl(root string) (configurable, []string, error) { - // Read the HCL file and prepare for parsing - d, err := ioutil.ReadFile(root) - if err != nil { - return nil, nil, fmt.Errorf( - "Error reading %s: %s", root, err) - } - - // Parse it - hclRoot, err := hcl.Parse(string(d)) - if err != nil { - return nil, nil, fmt.Errorf( - "Error parsing %s: %s", root, err) - } - - // Start building the result - result := &hclConfigurable{ - File: root, - Root: hclRoot, - } - - // Dive in, find the imports. This is disabled for now since - // imports were removed prior to Terraform 0.1. The code is - // remaining here commented for historical purposes. - /* - imports := obj.Get("import") - if imports == nil { - result.Object.Ref() - return result, nil, nil - } - - if imports.Type() != libucl.ObjectTypeString { - imports.Close() - - return nil, nil, fmt.Errorf( - "Error in %s: all 'import' declarations should be in the format\n"+ - "`import \"foo\"` (Got type %s)", - root, - imports.Type()) - } - - // Gather all the import paths - importPaths := make([]string, 0, imports.Len()) - iter := imports.Iterate(false) - for imp := iter.Next(); imp != nil; imp = iter.Next() { - path := imp.ToString() - if !filepath.IsAbs(path) { - // Relative paths are relative to the Terraform file itself - dir := filepath.Dir(root) - path = filepath.Join(dir, path) - } - - importPaths = append(importPaths, path) - imp.Close() - } - iter.Close() - imports.Close() - - result.Object.Ref() - */ - - return result, nil, nil -} - -// Given a handle to a HCL object, this transforms it into the Terraform config -func loadTerraformHcl(list *ast.ObjectList) (*Terraform, error) { - if len(list.Items) > 1 { - return nil, fmt.Errorf("only one 'terraform' block allowed per module") - } - - // Get our one item - item := list.Items[0] - - // This block should have an empty top level ObjectItem. If there are keys - // here, it's likely because we have a flattened JSON object, and we can - // lift this into a nested ObjectList to decode properly. - if len(item.Keys) > 0 { - item = &ast.ObjectItem{ - Val: &ast.ObjectType{ - List: &ast.ObjectList{ - Items: []*ast.ObjectItem{item}, - }, - }, - } - } - - // We need the item value as an ObjectList - var listVal *ast.ObjectList - if ot, ok := item.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return nil, fmt.Errorf("terraform block: should be an object") - } - - // NOTE: We purposely don't validate unknown HCL keys here so that - // we can potentially read _future_ Terraform version config (to - // still be able to validate the required version). - // - // We should still keep track of unknown keys to validate later, but - // HCL doesn't currently support that. - - var config Terraform - if err := hcl.DecodeObject(&config, item.Val); err != nil { - return nil, fmt.Errorf( - "Error reading terraform config: %s", - err) - } - - // If we have provisioners, then parse those out - if os := listVal.Filter("backend"); len(os.Items) > 0 { - var err error - config.Backend, err = loadTerraformBackendHcl(os) - if err != nil { - return nil, fmt.Errorf( - "Error reading backend config for terraform block: %s", - err) - } - } - - return &config, nil -} - -// Loads the Backend configuration from an object list. -func loadTerraformBackendHcl(list *ast.ObjectList) (*Backend, error) { - if len(list.Items) > 1 { - return nil, fmt.Errorf("only one 'backend' block allowed") - } - - // Get our one item - item := list.Items[0] - - // Verify the keys - if len(item.Keys) != 1 { - return nil, fmt.Errorf( - "position %s: 'backend' must be followed by exactly one string: a type", - item.Pos()) - } - - typ := item.Keys[0].Token.Value().(string) - - // Decode the raw config - var config map[string]interface{} - if err := hcl.DecodeObject(&config, item.Val); err != nil { - return nil, fmt.Errorf( - "Error reading backend config: %s", - err) - } - - rawConfig, err := NewRawConfig(config) - if err != nil { - return nil, fmt.Errorf( - "Error reading backend config: %s", - err) - } - - b := &Backend{ - Type: typ, - RawConfig: rawConfig, - } - b.Hash = b.Rehash() - - return b, nil -} - -// Given a handle to a HCL object, this transforms it into the Atlas -// configuration. -func loadAtlasHcl(list *ast.ObjectList) (*AtlasConfig, error) { - if len(list.Items) > 1 { - return nil, fmt.Errorf("only one 'atlas' block allowed") - } - - // Get our one item - item := list.Items[0] - - var config AtlasConfig - if err := hcl.DecodeObject(&config, item.Val); err != nil { - return nil, fmt.Errorf( - "Error reading atlas config: %s", - err) - } - - return &config, nil -} - -// Given a handle to a HCL object, this recurses into the structure -// and pulls out a list of modules. -// -// The resulting modules may not be unique, but each module -// represents exactly one module definition in the HCL configuration. -// We leave it up to another pass to merge them together. -func loadModulesHcl(list *ast.ObjectList) ([]*Module, error) { - if err := assertAllBlocksHaveNames("module", list); err != nil { - return nil, err - } - - list = list.Children() - if len(list.Items) == 0 { - return nil, nil - } - - // Where all the results will go - var result []*Module - - // Now go over all the types and their children in order to get - // all of the actual resources. - for _, item := range list.Items { - k := item.Keys[0].Token.Value().(string) - - var listVal *ast.ObjectList - if ot, ok := item.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return nil, fmt.Errorf("module '%s': should be an object", k) - } - - var config map[string]interface{} - if err := hcl.DecodeObject(&config, item.Val); err != nil { - return nil, fmt.Errorf( - "Error reading config for %s: %s", - k, - err) - } - - rawConfig, err := NewRawConfig(config) - if err != nil { - return nil, fmt.Errorf( - "Error reading config for %s: %s", - k, - err) - } - - // Remove the fields we handle specially - delete(config, "source") - delete(config, "version") - delete(config, "providers") - - var source string - if o := listVal.Filter("source"); len(o.Items) > 0 { - err = hcl.DecodeObject(&source, o.Items[0].Val) - if err != nil { - return nil, fmt.Errorf( - "Error parsing source for %s: %s", - k, - err) - } - } - - var version string - if o := listVal.Filter("version"); len(o.Items) > 0 { - err = hcl.DecodeObject(&version, o.Items[0].Val) - if err != nil { - return nil, fmt.Errorf( - "Error parsing version for %s: %s", - k, - err) - } - } - - var providers map[string]string - if o := listVal.Filter("providers"); len(o.Items) > 0 { - err = hcl.DecodeObject(&providers, o.Items[0].Val) - if err != nil { - return nil, fmt.Errorf( - "Error parsing providers for %s: %s", - k, - err) - } - } - - result = append(result, &Module{ - Name: k, - Source: source, - Version: version, - Providers: providers, - RawConfig: rawConfig, - }) - } - - return result, nil -} - -// loadLocalsHcl recurses into the given HCL object turns it into -// a list of locals. -func loadLocalsHcl(list *ast.ObjectList) ([]*Local, error) { - - result := make([]*Local, 0, len(list.Items)) - - for _, block := range list.Items { - if len(block.Keys) > 0 { - return nil, fmt.Errorf( - "locals block at %s should not have label %q", - block.Pos(), block.Keys[0].Token.Value(), - ) - } - - blockObj, ok := block.Val.(*ast.ObjectType) - if !ok { - return nil, fmt.Errorf("locals value at %s should be a block", block.Val.Pos()) - } - - // blockObj now contains directly our local decls - for _, item := range blockObj.List.Items { - if len(item.Keys) != 1 { - return nil, fmt.Errorf("local declaration at %s may not be a block", item.Val.Pos()) - } - - // By the time we get here there can only be one item left, but - // we'll decode into a map anyway because it's a convenient way - // to extract both the key and the value robustly. - kv := map[string]interface{}{} - hcl.DecodeObject(&kv, item) - for k, v := range kv { - rawConfig, err := NewRawConfig(map[string]interface{}{ - "value": v, - }) - - if err != nil { - return nil, fmt.Errorf( - "error parsing local value %q at %s: %s", - k, item.Val.Pos(), err, - ) - } - - result = append(result, &Local{ - Name: k, - RawConfig: rawConfig, - }) - } - } - } - - return result, nil -} - -// LoadOutputsHcl recurses into the given HCL object and turns -// it into a mapping of outputs. -func loadOutputsHcl(list *ast.ObjectList) ([]*Output, error) { - if err := assertAllBlocksHaveNames("output", list); err != nil { - return nil, err - } - - list = list.Children() - - // Go through each object and turn it into an actual result. - result := make([]*Output, 0, len(list.Items)) - for _, item := range list.Items { - n := item.Keys[0].Token.Value().(string) - - var listVal *ast.ObjectList - if ot, ok := item.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return nil, fmt.Errorf("output '%s': should be an object", n) - } - - var config map[string]interface{} - if err := hcl.DecodeObject(&config, item.Val); err != nil { - return nil, err - } - - // Delete special keys - delete(config, "depends_on") - delete(config, "description") - - rawConfig, err := NewRawConfig(config) - if err != nil { - return nil, fmt.Errorf( - "Error reading config for output %s: %s", - n, - err) - } - - // If we have depends fields, then add those in - var dependsOn []string - if o := listVal.Filter("depends_on"); len(o.Items) > 0 { - err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) - if err != nil { - return nil, fmt.Errorf( - "Error reading depends_on for output %q: %s", - n, - err) - } - } - - // If we have a description field, then filter that - var description string - if o := listVal.Filter("description"); len(o.Items) > 0 { - err := hcl.DecodeObject(&description, o.Items[0].Val) - if err != nil { - return nil, fmt.Errorf( - "Error reading description for output %q: %s", - n, - err) - } - } - - result = append(result, &Output{ - Name: n, - RawConfig: rawConfig, - DependsOn: dependsOn, - Description: description, - }) - } - - return result, nil -} - -// LoadVariablesHcl recurses into the given HCL object and turns -// it into a list of variables. -func loadVariablesHcl(list *ast.ObjectList) ([]*Variable, error) { - if err := assertAllBlocksHaveNames("variable", list); err != nil { - return nil, err - } - - list = list.Children() - - // hclVariable is the structure each variable is decoded into - type hclVariable struct { - DeclaredType string `hcl:"type"` - Default interface{} - Description string - Fields []string `hcl:",decodedFields"` - } - - // Go through each object and turn it into an actual result. - result := make([]*Variable, 0, len(list.Items)) - for _, item := range list.Items { - // Clean up items from JSON - unwrapHCLObjectKeysFromJSON(item, 1) - - // Verify the keys - if len(item.Keys) != 1 { - return nil, fmt.Errorf( - "position %s: 'variable' must be followed by exactly one strings: a name", - item.Pos()) - } - - n := item.Keys[0].Token.Value().(string) - if !NameRegexp.MatchString(n) { - return nil, fmt.Errorf( - "position %s: 'variable' name must match regular expression: %s", - item.Pos(), NameRegexp) - } - - // Check for invalid keys - valid := []string{"type", "default", "description"} - if err := checkHCLKeys(item.Val, valid); err != nil { - return nil, multierror.Prefix(err, fmt.Sprintf( - "variable[%s]:", n)) - } - - // Decode into hclVariable to get typed values - var hclVar hclVariable - if err := hcl.DecodeObject(&hclVar, item.Val); err != nil { - return nil, err - } - - // Defaults turn into a slice of map[string]interface{} and - // we need to make sure to convert that down into the - // proper type for Config. - if ms, ok := hclVar.Default.([]map[string]interface{}); ok { - def := make(map[string]interface{}) - for _, m := range ms { - for k, v := range m { - def[k] = v - } - } - - hclVar.Default = def - } - - // Build the new variable and do some basic validation - newVar := &Variable{ - Name: n, - DeclaredType: hclVar.DeclaredType, - Default: hclVar.Default, - Description: hclVar.Description, - } - if err := newVar.ValidateTypeAndDefault(); err != nil { - return nil, err - } - - result = append(result, newVar) - } - - return result, nil -} - -// LoadProvidersHcl recurses into the given HCL object and turns -// it into a mapping of provider configs. -func loadProvidersHcl(list *ast.ObjectList) ([]*ProviderConfig, error) { - if err := assertAllBlocksHaveNames("provider", list); err != nil { - return nil, err - } - - list = list.Children() - if len(list.Items) == 0 { - return nil, nil - } - - // Go through each object and turn it into an actual result. - result := make([]*ProviderConfig, 0, len(list.Items)) - for _, item := range list.Items { - n := item.Keys[0].Token.Value().(string) - - var listVal *ast.ObjectList - if ot, ok := item.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return nil, fmt.Errorf("module '%s': should be an object", n) - } - - var config map[string]interface{} - if err := hcl.DecodeObject(&config, item.Val); err != nil { - return nil, err - } - - delete(config, "alias") - delete(config, "version") - - rawConfig, err := NewRawConfig(config) - if err != nil { - return nil, fmt.Errorf( - "Error reading config for provider config %s: %s", - n, - err) - } - - // If we have an alias field, then add those in - var alias string - if a := listVal.Filter("alias"); len(a.Items) > 0 { - err := hcl.DecodeObject(&alias, a.Items[0].Val) - if err != nil { - return nil, fmt.Errorf( - "Error reading alias for provider[%s]: %s", - n, - err) - } - } - - // If we have a version field then extract it - var version string - if a := listVal.Filter("version"); len(a.Items) > 0 { - err := hcl.DecodeObject(&version, a.Items[0].Val) - if err != nil { - return nil, fmt.Errorf( - "Error reading version for provider[%s]: %s", - n, - err) - } - } - - result = append(result, &ProviderConfig{ - Name: n, - Alias: alias, - Version: version, - RawConfig: rawConfig, - }) - } - - return result, nil -} - -// Given a handle to a HCL object, this recurses into the structure -// and pulls out a list of data sources. -// -// The resulting data sources may not be unique, but each one -// represents exactly one data definition in the HCL configuration. -// We leave it up to another pass to merge them together. -func loadDataResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { - if err := assertAllBlocksHaveNames("data", list); err != nil { - return nil, err - } - - list = list.Children() - if len(list.Items) == 0 { - return nil, nil - } - - // Where all the results will go - var result []*Resource - - // Now go over all the types and their children in order to get - // all of the actual resources. - for _, item := range list.Items { - if len(item.Keys) != 2 { - return nil, fmt.Errorf( - "position %s: 'data' must be followed by exactly two strings: a type and a name", - item.Pos()) - } - - t := item.Keys[0].Token.Value().(string) - k := item.Keys[1].Token.Value().(string) - - var listVal *ast.ObjectList - if ot, ok := item.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return nil, fmt.Errorf("data sources %s[%s]: should be an object", t, k) - } - - var config map[string]interface{} - if err := hcl.DecodeObject(&config, item.Val); err != nil { - return nil, fmt.Errorf( - "Error reading config for %s[%s]: %s", - t, - k, - err) - } - - // Remove the fields we handle specially - delete(config, "depends_on") - delete(config, "provider") - delete(config, "count") - - rawConfig, err := NewRawConfig(config) - if err != nil { - return nil, fmt.Errorf( - "Error reading config for %s[%s]: %s", - t, - k, - err) - } - - // If we have a count, then figure it out - var count string = "1" - if o := listVal.Filter("count"); len(o.Items) > 0 { - err = hcl.DecodeObject(&count, o.Items[0].Val) - if err != nil { - return nil, fmt.Errorf( - "Error parsing count for %s[%s]: %s", - t, - k, - err) - } - } - countConfig, err := NewRawConfig(map[string]interface{}{ - "count": count, - }) - if err != nil { - return nil, err - } - countConfig.Key = "count" - - // If we have depends fields, then add those in - var dependsOn []string - if o := listVal.Filter("depends_on"); len(o.Items) > 0 { - err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) - if err != nil { - return nil, fmt.Errorf( - "Error reading depends_on for %s[%s]: %s", - t, - k, - err) - } - } - - // If we have a provider, then parse it out - var provider string - if o := listVal.Filter("provider"); len(o.Items) > 0 { - err := hcl.DecodeObject(&provider, o.Items[0].Val) - if err != nil { - return nil, fmt.Errorf( - "Error reading provider for %s[%s]: %s", - t, - k, - err) - } - } - - result = append(result, &Resource{ - Mode: DataResourceMode, - Name: k, - Type: t, - RawCount: countConfig, - RawConfig: rawConfig, - Provider: provider, - Provisioners: []*Provisioner{}, - DependsOn: dependsOn, - Lifecycle: ResourceLifecycle{}, - }) - } - - return result, nil -} - -// Given a handle to a HCL object, this recurses into the structure -// and pulls out a list of managed resources. -// -// The resulting resources may not be unique, but each resource -// represents exactly one "resource" block in the HCL configuration. -// We leave it up to another pass to merge them together. -func loadManagedResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { - list = list.Children() - if len(list.Items) == 0 { - return nil, nil - } - - // Where all the results will go - var result []*Resource - - // Now go over all the types and their children in order to get - // all of the actual resources. - for _, item := range list.Items { - // GH-4385: We detect a pure provisioner resource and give the user - // an error about how to do it cleanly. - if len(item.Keys) == 4 && item.Keys[2].Token.Value().(string) == "provisioner" { - return nil, fmt.Errorf( - "position %s: provisioners in a resource should be wrapped in a list\n\n"+ - "Example: \"provisioner\": [ { \"local-exec\": ... } ]", - item.Pos()) - } - - // Fix up JSON input - unwrapHCLObjectKeysFromJSON(item, 2) - - if len(item.Keys) != 2 { - return nil, fmt.Errorf( - "position %s: resource must be followed by exactly two strings, a type and a name", - item.Pos()) - } - - t := item.Keys[0].Token.Value().(string) - k := item.Keys[1].Token.Value().(string) - - var listVal *ast.ObjectList - if ot, ok := item.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return nil, fmt.Errorf("resources %s[%s]: should be an object", t, k) - } - - var config map[string]interface{} - if err := hcl.DecodeObject(&config, item.Val); err != nil { - return nil, fmt.Errorf( - "Error reading config for %s[%s]: %s", - t, - k, - err) - } - - // Remove the fields we handle specially - delete(config, "connection") - delete(config, "count") - delete(config, "depends_on") - delete(config, "provisioner") - delete(config, "provider") - delete(config, "lifecycle") - - rawConfig, err := NewRawConfig(config) - if err != nil { - return nil, fmt.Errorf( - "Error reading config for %s[%s]: %s", - t, - k, - err) - } - - // If we have a count, then figure it out - var count string = "1" - if o := listVal.Filter("count"); len(o.Items) > 0 { - err = hcl.DecodeObject(&count, o.Items[0].Val) - if err != nil { - return nil, fmt.Errorf( - "Error parsing count for %s[%s]: %s", - t, - k, - err) - } - } - countConfig, err := NewRawConfig(map[string]interface{}{ - "count": count, - }) - if err != nil { - return nil, err - } - countConfig.Key = "count" - - // If we have depends fields, then add those in - var dependsOn []string - if o := listVal.Filter("depends_on"); len(o.Items) > 0 { - err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) - if err != nil { - return nil, fmt.Errorf( - "Error reading depends_on for %s[%s]: %s", - t, - k, - err) - } - } - - // If we have connection info, then parse those out - var connInfo map[string]interface{} - if o := listVal.Filter("connection"); len(o.Items) > 0 { - err := hcl.DecodeObject(&connInfo, o.Items[0].Val) - if err != nil { - return nil, fmt.Errorf( - "Error reading connection info for %s[%s]: %s", - t, - k, - err) - } - } - - // If we have provisioners, then parse those out - var provisioners []*Provisioner - if os := listVal.Filter("provisioner"); len(os.Items) > 0 { - var err error - provisioners, err = loadProvisionersHcl(os, connInfo) - if err != nil { - return nil, fmt.Errorf( - "Error reading provisioners for %s[%s]: %s", - t, - k, - err) - } - } - - // If we have a provider, then parse it out - var provider string - if o := listVal.Filter("provider"); len(o.Items) > 0 { - err := hcl.DecodeObject(&provider, o.Items[0].Val) - if err != nil { - return nil, fmt.Errorf( - "Error reading provider for %s[%s]: %s", - t, - k, - err) - } - } - - // Check if the resource should be re-created before - // destroying the existing instance - var lifecycle ResourceLifecycle - if o := listVal.Filter("lifecycle"); len(o.Items) > 0 { - if len(o.Items) > 1 { - return nil, fmt.Errorf( - "%s[%s]: Multiple lifecycle blocks found, expected one", - t, k) - } - - // Check for invalid keys - valid := []string{"create_before_destroy", "ignore_changes", "prevent_destroy"} - if err := checkHCLKeys(o.Items[0].Val, valid); err != nil { - return nil, multierror.Prefix(err, fmt.Sprintf( - "%s[%s]:", t, k)) - } - - var raw map[string]interface{} - if err = hcl.DecodeObject(&raw, o.Items[0].Val); err != nil { - return nil, fmt.Errorf( - "Error parsing lifecycle for %s[%s]: %s", - t, - k, - err) - } - - if err := mapstructure.WeakDecode(raw, &lifecycle); err != nil { - return nil, fmt.Errorf( - "Error parsing lifecycle for %s[%s]: %s", - t, - k, - err) - } - } - - result = append(result, &Resource{ - Mode: ManagedResourceMode, - Name: k, - Type: t, - RawCount: countConfig, - RawConfig: rawConfig, - Provisioners: provisioners, - Provider: provider, - DependsOn: dependsOn, - Lifecycle: lifecycle, - }) - } - - return result, nil -} - -func loadProvisionersHcl(list *ast.ObjectList, connInfo map[string]interface{}) ([]*Provisioner, error) { - if err := assertAllBlocksHaveNames("provisioner", list); err != nil { - return nil, err - } - - list = list.Children() - if len(list.Items) == 0 { - return nil, nil - } - - // Go through each object and turn it into an actual result. - result := make([]*Provisioner, 0, len(list.Items)) - for _, item := range list.Items { - n := item.Keys[0].Token.Value().(string) - - var listVal *ast.ObjectList - if ot, ok := item.Val.(*ast.ObjectType); ok { - listVal = ot.List - } else { - return nil, fmt.Errorf("provisioner '%s': should be an object", n) - } - - var config map[string]interface{} - if err := hcl.DecodeObject(&config, item.Val); err != nil { - return nil, err - } - - // Parse the "when" value - when := ProvisionerWhenCreate - if v, ok := config["when"]; ok { - switch v { - case "create": - when = ProvisionerWhenCreate - case "destroy": - when = ProvisionerWhenDestroy - default: - return nil, fmt.Errorf( - "position %s: 'provisioner' when must be 'create' or 'destroy'", - item.Pos()) - } - } - - // Parse the "on_failure" value - onFailure := ProvisionerOnFailureFail - if v, ok := config["on_failure"]; ok { - switch v { - case "continue": - onFailure = ProvisionerOnFailureContinue - case "fail": - onFailure = ProvisionerOnFailureFail - default: - return nil, fmt.Errorf( - "position %s: 'provisioner' on_failure must be 'continue' or 'fail'", - item.Pos()) - } - } - - // Delete fields we special case - delete(config, "connection") - delete(config, "when") - delete(config, "on_failure") - - rawConfig, err := NewRawConfig(config) - if err != nil { - return nil, err - } - - // Check if we have a provisioner-level connection - // block that overrides the resource-level - var subConnInfo map[string]interface{} - if o := listVal.Filter("connection"); len(o.Items) > 0 { - err := hcl.DecodeObject(&subConnInfo, o.Items[0].Val) - if err != nil { - return nil, err - } - } - - // Inherit from the resource connInfo any keys - // that are not explicitly overriden. - if connInfo != nil && subConnInfo != nil { - for k, v := range connInfo { - if _, ok := subConnInfo[k]; !ok { - subConnInfo[k] = v - } - } - } else if subConnInfo == nil { - subConnInfo = connInfo - } - - // Parse the connInfo - connRaw, err := NewRawConfig(subConnInfo) - if err != nil { - return nil, err - } - - result = append(result, &Provisioner{ - Type: n, - RawConfig: rawConfig, - ConnInfo: connRaw, - When: when, - OnFailure: onFailure, - }) - } - - return result, nil -} - -/* -func hclObjectMap(os *hclobj.Object) map[string]ast.ListNode { - objects := make(map[string][]*hclobj.Object) - - for _, o := range os.Elem(false) { - for _, elem := range o.Elem(true) { - val, ok := objects[elem.Key] - if !ok { - val = make([]*hclobj.Object, 0, 1) - } - - val = append(val, elem) - objects[elem.Key] = val - } - } - - return objects -} -*/ - -// assertAllBlocksHaveNames returns an error if any of the items in -// the given object list are blocks without keys (like "module {}") -// or simple assignments (like "module = 1"). It returns nil if -// neither of these things are true. -// -// The given name is used in any generated error messages, and should -// be the name of the block we're dealing with. The given list should -// be the result of calling .Filter on an object list with that same -// name. -func assertAllBlocksHaveNames(name string, list *ast.ObjectList) error { - if elem := list.Elem(); len(elem.Items) != 0 { - switch et := elem.Items[0].Val.(type) { - case *ast.ObjectType: - pos := et.Lbrace - return fmt.Errorf("%s: %q must be followed by a name", pos, name) - default: - pos := elem.Items[0].Val.Pos() - return fmt.Errorf("%s: %q must be a configuration block", pos, name) - } - } - return nil -} - -func checkHCLKeys(node ast.Node, valid []string) error { - var list *ast.ObjectList - switch n := node.(type) { - case *ast.ObjectList: - list = n - case *ast.ObjectType: - list = n.List - default: - return fmt.Errorf("cannot check HCL keys of type %T", n) - } - - validMap := make(map[string]struct{}, len(valid)) - for _, v := range valid { - validMap[v] = struct{}{} - } - - var result error - for _, item := range list.Items { - key := item.Keys[0].Token.Value().(string) - if _, ok := validMap[key]; !ok { - result = multierror.Append(result, fmt.Errorf( - "invalid key: %s", key)) - } - } - - return result -} - -// unwrapHCLObjectKeysFromJSON cleans up an edge case that can occur when -// parsing JSON as input: if we're parsing JSON then directly nested -// items will show up as additional "keys". -// -// For objects that expect a fixed number of keys, this breaks the -// decoding process. This function unwraps the object into what it would've -// looked like if it came directly from HCL by specifying the number of keys -// you expect. -// -// Example: -// -// { "foo": { "baz": {} } } -// -// Will show up with Keys being: []string{"foo", "baz"} -// when we really just want the first two. This function will fix this. -func unwrapHCLObjectKeysFromJSON(item *ast.ObjectItem, depth int) { - if len(item.Keys) > depth && item.Keys[0].Token.JSON { - for len(item.Keys) > depth { - // Pop off the last key - n := len(item.Keys) - key := item.Keys[n-1] - item.Keys[n-1] = nil - item.Keys = item.Keys[:n-1] - - // Wrap our value in a list - item.Val = &ast.ObjectType{ - List: &ast.ObjectList{ - Items: []*ast.ObjectItem{ - &ast.ObjectItem{ - Keys: []*ast.ObjectKey{key}, - Val: item.Val, - }, - }, - }, - } - } - } -} diff --git a/config/loader_hcl2.go b/config/loader_hcl2.go deleted file mode 100644 index da7559a9d..000000000 --- a/config/loader_hcl2.go +++ /dev/null @@ -1,473 +0,0 @@ -package config - -import ( - "fmt" - "sort" - "strings" - - hcl2 "github.com/hashicorp/hcl/v2" - gohcl2 "github.com/hashicorp/hcl/v2/gohcl" - hcl2parse "github.com/hashicorp/hcl/v2/hclparse" - "github.com/hashicorp/terraform/configs/hcl2shim" - "github.com/zclconf/go-cty/cty" -) - -// hcl2Configurable is an implementation of configurable that knows -// how to turn a HCL Body into a *Config object. -type hcl2Configurable struct { - SourceFilename string - Body hcl2.Body -} - -// hcl2Loader is a wrapper around a HCL parser that provides a fileLoaderFunc. -type hcl2Loader struct { - Parser *hcl2parse.Parser -} - -// For the moment we'll just have a global loader since we don't have anywhere -// better to stash this. -// TODO: refactor the loader API so that it uses some sort of object we can -// stash the parser inside. -var globalHCL2Loader = newHCL2Loader() - -// newHCL2Loader creates a new hcl2Loader containing a new HCL Parser. -// -// HCL parsers retain information about files that are loaded to aid in -// producing diagnostic messages, so all files within a single configuration -// should be loaded with the same parser to ensure the availability of -// full diagnostic information. -func newHCL2Loader() hcl2Loader { - return hcl2Loader{ - Parser: hcl2parse.NewParser(), - } -} - -// loadFile is a fileLoaderFunc that knows how to read a HCL2 file and turn it -// into a hcl2Configurable. -func (l hcl2Loader) loadFile(filename string) (configurable, []string, error) { - var f *hcl2.File - var diags hcl2.Diagnostics - if strings.HasSuffix(filename, ".json") { - f, diags = l.Parser.ParseJSONFile(filename) - } else { - f, diags = l.Parser.ParseHCLFile(filename) - } - if diags.HasErrors() { - // Return diagnostics as an error; callers may type-assert this to - // recover the original diagnostics, if it doesn't end up wrapped - // in another error. - return nil, nil, diags - } - - return &hcl2Configurable{ - SourceFilename: filename, - Body: f.Body, - }, nil, nil -} - -func (t *hcl2Configurable) Config() (*Config, error) { - config := &Config{} - - // these structs are used only for the initial shallow decoding; we'll - // expand this into the main, public-facing config structs afterwards. - type atlas struct { - Name string `hcl:"name"` - Include *[]string `hcl:"include"` - Exclude *[]string `hcl:"exclude"` - } - type provider struct { - Name string `hcl:"name,label"` - Alias *string `hcl:"alias,attr"` - Version *string `hcl:"version,attr"` - Config hcl2.Body `hcl:",remain"` - } - type module struct { - Name string `hcl:"name,label"` - Source string `hcl:"source,attr"` - Version *string `hcl:"version,attr"` - Providers *map[string]string `hcl:"providers,attr"` - Config hcl2.Body `hcl:",remain"` - } - type resourceLifecycle struct { - CreateBeforeDestroy *bool `hcl:"create_before_destroy,attr"` - PreventDestroy *bool `hcl:"prevent_destroy,attr"` - IgnoreChanges *[]string `hcl:"ignore_changes,attr"` - } - type connection struct { - Config hcl2.Body `hcl:",remain"` - } - type provisioner struct { - Type string `hcl:"type,label"` - - When *string `hcl:"when,attr"` - OnFailure *string `hcl:"on_failure,attr"` - - Connection *connection `hcl:"connection,block"` - Config hcl2.Body `hcl:",remain"` - } - type managedResource struct { - Type string `hcl:"type,label"` - Name string `hcl:"name,label"` - - CountExpr hcl2.Expression `hcl:"count,attr"` - Provider *string `hcl:"provider,attr"` - DependsOn *[]string `hcl:"depends_on,attr"` - - Lifecycle *resourceLifecycle `hcl:"lifecycle,block"` - Provisioners []provisioner `hcl:"provisioner,block"` - Connection *connection `hcl:"connection,block"` - - Config hcl2.Body `hcl:",remain"` - } - type dataResource struct { - Type string `hcl:"type,label"` - Name string `hcl:"name,label"` - - CountExpr hcl2.Expression `hcl:"count,attr"` - Provider *string `hcl:"provider,attr"` - DependsOn *[]string `hcl:"depends_on,attr"` - - Config hcl2.Body `hcl:",remain"` - } - type variable struct { - Name string `hcl:"name,label"` - - DeclaredType *string `hcl:"type,attr"` - Default *cty.Value `hcl:"default,attr"` - Description *string `hcl:"description,attr"` - Sensitive *bool `hcl:"sensitive,attr"` - } - type output struct { - Name string `hcl:"name,label"` - - ValueExpr hcl2.Expression `hcl:"value,attr"` - DependsOn *[]string `hcl:"depends_on,attr"` - Description *string `hcl:"description,attr"` - Sensitive *bool `hcl:"sensitive,attr"` - } - type locals struct { - Definitions hcl2.Attributes `hcl:",remain"` - } - type backend struct { - Type string `hcl:"type,label"` - Config hcl2.Body `hcl:",remain"` - } - type terraform struct { - RequiredVersion *string `hcl:"required_version,attr"` - Backend *backend `hcl:"backend,block"` - } - type topLevel struct { - Atlas *atlas `hcl:"atlas,block"` - Datas []dataResource `hcl:"data,block"` - Modules []module `hcl:"module,block"` - Outputs []output `hcl:"output,block"` - Providers []provider `hcl:"provider,block"` - Resources []managedResource `hcl:"resource,block"` - Terraform *terraform `hcl:"terraform,block"` - Variables []variable `hcl:"variable,block"` - Locals []*locals `hcl:"locals,block"` - } - - var raw topLevel - diags := gohcl2.DecodeBody(t.Body, nil, &raw) - if diags.HasErrors() { - // Do some minimal decoding to see if we can at least get the - // required Terraform version, which might help explain why we - // couldn't parse the rest. - if raw.Terraform != nil && raw.Terraform.RequiredVersion != nil { - config.Terraform = &Terraform{ - RequiredVersion: *raw.Terraform.RequiredVersion, - } - } - - // We return the diags as an implementation of error, which the - // caller than then type-assert if desired to recover the individual - // diagnostics. - // FIXME: The current API gives us no way to return warnings in the - // absence of any errors. - return config, diags - } - - if raw.Terraform != nil { - var reqdVersion string - var backend *Backend - - if raw.Terraform.RequiredVersion != nil { - reqdVersion = *raw.Terraform.RequiredVersion - } - if raw.Terraform.Backend != nil { - backend = new(Backend) - backend.Type = raw.Terraform.Backend.Type - - // We don't permit interpolations or nested blocks inside the - // backend config, so we can decode the config early here and - // get direct access to the values, which is important for the - // config hashing to work as expected. - var config map[string]string - configDiags := gohcl2.DecodeBody(raw.Terraform.Backend.Config, nil, &config) - diags = append(diags, configDiags...) - - raw := make(map[string]interface{}, len(config)) - for k, v := range config { - raw[k] = v - } - - var err error - backend.RawConfig, err = NewRawConfig(raw) - if err != nil { - diags = append(diags, &hcl2.Diagnostic{ - Severity: hcl2.DiagError, - Summary: "Invalid backend configuration", - Detail: fmt.Sprintf("Error in backend configuration: %s", err), - }) - } - } - - config.Terraform = &Terraform{ - RequiredVersion: reqdVersion, - Backend: backend, - } - } - - if raw.Atlas != nil { - var include, exclude []string - if raw.Atlas.Include != nil { - include = *raw.Atlas.Include - } - if raw.Atlas.Exclude != nil { - exclude = *raw.Atlas.Exclude - } - config.Atlas = &AtlasConfig{ - Name: raw.Atlas.Name, - Include: include, - Exclude: exclude, - } - } - - for _, rawM := range raw.Modules { - m := &Module{ - Name: rawM.Name, - Source: rawM.Source, - RawConfig: NewRawConfigHCL2(rawM.Config), - } - - if rawM.Version != nil { - m.Version = *rawM.Version - } - - if rawM.Providers != nil { - m.Providers = *rawM.Providers - } - - config.Modules = append(config.Modules, m) - } - - for _, rawV := range raw.Variables { - v := &Variable{ - Name: rawV.Name, - } - if rawV.DeclaredType != nil { - v.DeclaredType = *rawV.DeclaredType - } - if rawV.Default != nil { - v.Default = hcl2shim.ConfigValueFromHCL2(*rawV.Default) - } - if rawV.Description != nil { - v.Description = *rawV.Description - } - - config.Variables = append(config.Variables, v) - } - - for _, rawO := range raw.Outputs { - o := &Output{ - Name: rawO.Name, - } - - if rawO.Description != nil { - o.Description = *rawO.Description - } - if rawO.DependsOn != nil { - o.DependsOn = *rawO.DependsOn - } - if rawO.Sensitive != nil { - o.Sensitive = *rawO.Sensitive - } - - // The result is expected to be a map like map[string]interface{}{"value": something}, - // so we'll fake that with our hcl2shim.SingleAttrBody shim. - o.RawConfig = NewRawConfigHCL2(hcl2shim.SingleAttrBody{ - Name: "value", - Expr: rawO.ValueExpr, - }) - - config.Outputs = append(config.Outputs, o) - } - - for _, rawR := range raw.Resources { - r := &Resource{ - Mode: ManagedResourceMode, - Type: rawR.Type, - Name: rawR.Name, - } - if rawR.Lifecycle != nil { - var l ResourceLifecycle - if rawR.Lifecycle.CreateBeforeDestroy != nil { - l.CreateBeforeDestroy = *rawR.Lifecycle.CreateBeforeDestroy - } - if rawR.Lifecycle.PreventDestroy != nil { - l.PreventDestroy = *rawR.Lifecycle.PreventDestroy - } - if rawR.Lifecycle.IgnoreChanges != nil { - l.IgnoreChanges = *rawR.Lifecycle.IgnoreChanges - } - r.Lifecycle = l - } - if rawR.Provider != nil { - r.Provider = *rawR.Provider - } - if rawR.DependsOn != nil { - r.DependsOn = *rawR.DependsOn - } - - var defaultConnInfo *RawConfig - if rawR.Connection != nil { - defaultConnInfo = NewRawConfigHCL2(rawR.Connection.Config) - } - - for _, rawP := range rawR.Provisioners { - p := &Provisioner{ - Type: rawP.Type, - } - - switch { - case rawP.When == nil: - p.When = ProvisionerWhenCreate - case *rawP.When == "create": - p.When = ProvisionerWhenCreate - case *rawP.When == "destroy": - p.When = ProvisionerWhenDestroy - default: - p.When = ProvisionerWhenInvalid - } - - switch { - case rawP.OnFailure == nil: - p.OnFailure = ProvisionerOnFailureFail - case *rawP.When == "fail": - p.OnFailure = ProvisionerOnFailureFail - case *rawP.When == "continue": - p.OnFailure = ProvisionerOnFailureContinue - default: - p.OnFailure = ProvisionerOnFailureInvalid - } - - if rawP.Connection != nil { - p.ConnInfo = NewRawConfigHCL2(rawP.Connection.Config) - } else { - p.ConnInfo = defaultConnInfo - } - - p.RawConfig = NewRawConfigHCL2(rawP.Config) - - r.Provisioners = append(r.Provisioners, p) - } - - // The old loader records the count expression as a weird RawConfig with - // a single-element map inside. Since the rest of the world is assuming - // that, we'll mimic it here. - { - countBody := hcl2shim.SingleAttrBody{ - Name: "count", - Expr: rawR.CountExpr, - } - - r.RawCount = NewRawConfigHCL2(countBody) - r.RawCount.Key = "count" - } - - r.RawConfig = NewRawConfigHCL2(rawR.Config) - - config.Resources = append(config.Resources, r) - - } - - for _, rawR := range raw.Datas { - r := &Resource{ - Mode: DataResourceMode, - Type: rawR.Type, - Name: rawR.Name, - } - - if rawR.Provider != nil { - r.Provider = *rawR.Provider - } - if rawR.DependsOn != nil { - r.DependsOn = *rawR.DependsOn - } - - // The old loader records the count expression as a weird RawConfig with - // a single-element map inside. Since the rest of the world is assuming - // that, we'll mimic it here. - { - countBody := hcl2shim.SingleAttrBody{ - Name: "count", - Expr: rawR.CountExpr, - } - - r.RawCount = NewRawConfigHCL2(countBody) - r.RawCount.Key = "count" - } - - r.RawConfig = NewRawConfigHCL2(rawR.Config) - - config.Resources = append(config.Resources, r) - } - - for _, rawP := range raw.Providers { - p := &ProviderConfig{ - Name: rawP.Name, - } - - if rawP.Alias != nil { - p.Alias = *rawP.Alias - } - if rawP.Version != nil { - p.Version = *rawP.Version - } - - // The result is expected to be a map like map[string]interface{}{"value": something}, - // so we'll fake that with our hcl2shim.SingleAttrBody shim. - p.RawConfig = NewRawConfigHCL2(rawP.Config) - - config.ProviderConfigs = append(config.ProviderConfigs, p) - } - - for _, rawL := range raw.Locals { - names := make([]string, 0, len(rawL.Definitions)) - for n := range rawL.Definitions { - names = append(names, n) - } - sort.Strings(names) - for _, n := range names { - attr := rawL.Definitions[n] - l := &Local{ - Name: n, - RawConfig: NewRawConfigHCL2(hcl2shim.SingleAttrBody{ - Name: "value", - Expr: attr.Expr, - }), - } - config.Locals = append(config.Locals, l) - } - } - - // FIXME: The current API gives us no way to return warnings in the - // absence of any errors. - var err error - if diags.HasErrors() { - err = diags - } - - return config, err -} diff --git a/config/loader_hcl2_test.go b/config/loader_hcl2_test.go deleted file mode 100644 index ab57654c5..000000000 --- a/config/loader_hcl2_test.go +++ /dev/null @@ -1,510 +0,0 @@ -package config - -import ( - "reflect" - "testing" - - "github.com/zclconf/go-cty/cty" - - hcl2 "github.com/hashicorp/hcl/v2" - gohcl2 "github.com/hashicorp/hcl/v2/gohcl" -) - -func TestHCL2ConfigurableConfigurable(t *testing.T) { - var _ configurable = new(hcl2Configurable) -} - -func TestHCL2Basic(t *testing.T) { - loader := globalHCL2Loader - cbl, _, err := loader.loadFile("testdata/basic-hcl2.tf") - if err != nil { - if diags, isDiags := err.(hcl2.Diagnostics); isDiags { - for _, diag := range diags { - t.Logf("- %s", diag.Error()) - } - t.Fatalf("unexpected diagnostics in load") - } else { - t.Fatalf("unexpected error in load: %s", err) - } - } - - cfg, err := cbl.Config() - if err != nil { - if diags, isDiags := err.(hcl2.Diagnostics); isDiags { - for _, diag := range diags { - t.Logf("- %s", diag.Error()) - } - t.Fatalf("unexpected diagnostics in decode") - } else { - t.Fatalf("unexpected error in decode: %s", err) - } - } - - // Unfortunately the config structure isn't DeepEqual-friendly because - // of all the nested RawConfig, etc structures, so we'll need to - // hand-assert each item. - - // The "terraform" block - if cfg.Terraform == nil { - t.Fatalf("Terraform field is nil") - } - if got, want := cfg.Terraform.RequiredVersion, "foo"; got != want { - t.Errorf("wrong Terraform.RequiredVersion %q; want %q", got, want) - } - if cfg.Terraform.Backend == nil { - t.Fatalf("Terraform.Backend is nil") - } - if got, want := cfg.Terraform.Backend.Type, "baz"; got != want { - t.Errorf("wrong Terraform.Backend.Type %q; want %q", got, want) - } - if got, want := cfg.Terraform.Backend.RawConfig.Raw, map[string]interface{}{"something": "nothing"}; !reflect.DeepEqual(got, want) { - t.Errorf("wrong Terraform.Backend.RawConfig.Raw %#v; want %#v", got, want) - } - - // The "atlas" block - if cfg.Atlas == nil { - t.Fatalf("Atlas field is nil") - } - if got, want := cfg.Atlas.Name, "example/foo"; got != want { - t.Errorf("wrong Atlas.Name %q; want %q", got, want) - } - - // "module" blocks - if got, want := len(cfg.Modules), 1; got != want { - t.Errorf("Modules slice has wrong length %#v; want %#v", got, want) - } else { - m := cfg.Modules[0] - if got, want := m.Name, "child"; got != want { - t.Errorf("wrong Modules[0].Name %#v; want %#v", got, want) - } - if got, want := m.Source, "./baz"; got != want { - t.Errorf("wrong Modules[0].Source %#v; want %#v", got, want) - } - want := map[string]string{"toasty": "true"} - var got map[string]string - gohcl2.DecodeBody(m.RawConfig.Body, nil, &got) - if !reflect.DeepEqual(got, want) { - t.Errorf("wrong Modules[0].RawConfig.Body %#v; want %#v", got, want) - } - } - - // "resource" blocks - if got, want := len(cfg.Resources), 5; got != want { - t.Errorf("Resources slice has wrong length %#v; want %#v", got, want) - } else { - { - r := cfg.Resources[0] - - if got, want := r.Id(), "aws_security_group.firewall"; got != want { - t.Errorf("wrong Resources[0].Id() %#v; want %#v", got, want) - } - - wantConfig := map[string]string{} - var gotConfig map[string]string - gohcl2.DecodeBody(r.RawConfig.Body, nil, &gotConfig) - if !reflect.DeepEqual(gotConfig, wantConfig) { - t.Errorf("wrong Resources[0].RawConfig.Body %#v; want %#v", gotConfig, wantConfig) - } - - wantCount := map[string]string{"count": "5"} - var gotCount map[string]string - gohcl2.DecodeBody(r.RawCount.Body, nil, &gotCount) - if !reflect.DeepEqual(gotCount, wantCount) { - t.Errorf("wrong Resources[0].RawCount.Body %#v; want %#v", gotCount, wantCount) - } - if got, want := r.RawCount.Key, "count"; got != want { - t.Errorf("wrong Resources[0].RawCount.Key %#v; want %#v", got, want) - } - - if got, want := len(r.Provisioners), 0; got != want { - t.Errorf("wrong Resources[0].Provisioners length %#v; want %#v", got, want) - } - if got, want := len(r.DependsOn), 0; got != want { - t.Errorf("wrong Resources[0].DependsOn length %#v; want %#v", got, want) - } - if got, want := r.Provider, "another"; got != want { - t.Errorf("wrong Resources[0].Provider %#v; want %#v", got, want) - } - if got, want := r.Lifecycle, (ResourceLifecycle{}); !reflect.DeepEqual(got, want) { - t.Errorf("wrong Resources[0].Lifecycle %#v; want %#v", got, want) - } - } - { - r := cfg.Resources[1] - - if got, want := r.Id(), "aws_instance.web"; got != want { - t.Errorf("wrong Resources[1].Id() %#v; want %#v", got, want) - } - if got, want := r.Provider, ""; got != want { - t.Errorf("wrong Resources[1].Provider %#v; want %#v", got, want) - } - - if got, want := len(r.Provisioners), 1; got != want { - t.Errorf("wrong Resources[1].Provisioners length %#v; want %#v", got, want) - } else { - p := r.Provisioners[0] - - if got, want := p.Type, "file"; got != want { - t.Errorf("wrong Resources[1].Provisioners[0].Type %#v; want %#v", got, want) - } - - wantConfig := map[string]string{ - "source": "foo", - "destination": "bar", - } - var gotConfig map[string]string - gohcl2.DecodeBody(p.RawConfig.Body, nil, &gotConfig) - if !reflect.DeepEqual(gotConfig, wantConfig) { - t.Errorf("wrong Resources[1].Provisioners[0].RawConfig.Body %#v; want %#v", gotConfig, wantConfig) - } - - wantConn := map[string]string{ - "default": "true", - } - var gotConn map[string]string - gohcl2.DecodeBody(p.ConnInfo.Body, nil, &gotConn) - if !reflect.DeepEqual(gotConn, wantConn) { - t.Errorf("wrong Resources[1].Provisioners[0].ConnInfo.Body %#v; want %#v", gotConn, wantConn) - } - } - - // We'll use these throwaway structs to more easily decode and - // compare the main config body. - type instanceNetworkInterface struct { - DeviceIndex int `hcl:"device_index"` - Description string `hcl:"description"` - } - type instanceConfig struct { - AMI string `hcl:"ami"` - SecurityGroups []string `hcl:"security_groups"` - NetworkInterface instanceNetworkInterface `hcl:"network_interface,block"` - } - var gotConfig instanceConfig - wantConfig := instanceConfig{ - AMI: "ami-abc123", - SecurityGroups: []string{"foo", "sg-firewall"}, - NetworkInterface: instanceNetworkInterface{ - DeviceIndex: 0, - Description: "Main network interface", - }, - } - ctx := &hcl2.EvalContext{ - Variables: map[string]cty.Value{ - "var": cty.ObjectVal(map[string]cty.Value{ - "foo": cty.StringVal("ami-abc123"), - }), - "aws_security_group": cty.ObjectVal(map[string]cty.Value{ - "firewall": cty.ObjectVal(map[string]cty.Value{ - "foo": cty.StringVal("sg-firewall"), - }), - }), - }, - } - diags := gohcl2.DecodeBody(r.RawConfig.Body, ctx, &gotConfig) - if len(diags) != 0 { - t.Errorf("unexpected diagnostics decoding Resources[1].RawConfig.Body") - for _, diag := range diags { - t.Logf("- %s", diag.Error()) - } - } - if !reflect.DeepEqual(gotConfig, wantConfig) { - t.Errorf("wrong Resources[1].RawConfig.Body %#v; want %#v", gotConfig, wantConfig) - } - - } - { - r := cfg.Resources[2] - - if got, want := r.Id(), "aws_instance.db"; got != want { - t.Errorf("wrong Resources[2].Id() %#v; want %#v", got, want) - } - if got, want := r.DependsOn, []string{"aws_instance.web"}; !reflect.DeepEqual(got, want) { - t.Errorf("wrong Resources[2].DependsOn %#v; want %#v", got, want) - } - - if got, want := len(r.Provisioners), 1; got != want { - t.Errorf("wrong Resources[2].Provisioners length %#v; want %#v", got, want) - } else { - p := r.Provisioners[0] - - if got, want := p.Type, "file"; got != want { - t.Errorf("wrong Resources[2].Provisioners[0].Type %#v; want %#v", got, want) - } - - wantConfig := map[string]string{ - "source": "here", - "destination": "there", - } - var gotConfig map[string]string - gohcl2.DecodeBody(p.RawConfig.Body, nil, &gotConfig) - if !reflect.DeepEqual(gotConfig, wantConfig) { - t.Errorf("wrong Resources[2].Provisioners[0].RawConfig.Body %#v; want %#v", gotConfig, wantConfig) - } - - wantConn := map[string]string{ - "default": "false", - } - var gotConn map[string]string - gohcl2.DecodeBody(p.ConnInfo.Body, nil, &gotConn) - if !reflect.DeepEqual(gotConn, wantConn) { - t.Errorf("wrong Resources[2].Provisioners[0].ConnInfo.Body %#v; want %#v", gotConn, wantConn) - } - } - } - { - r := cfg.Resources[3] - - if got, want := r.Id(), "data.do.simple"; got != want { - t.Errorf("wrong Resources[3].Id() %#v; want %#v", got, want) - } - if got, want := r.DependsOn, []string(nil); !reflect.DeepEqual(got, want) { - t.Errorf("wrong Resources[3].DependsOn %#v; want %#v", got, want) - } - if got, want := r.Provider, "do.foo"; got != want { - t.Errorf("wrong Resources[3].Provider %#v; want %#v", got, want) - } - - wantConfig := map[string]string{ - "foo": "baz", - } - var gotConfig map[string]string - gohcl2.DecodeBody(r.RawConfig.Body, nil, &gotConfig) - if !reflect.DeepEqual(gotConfig, wantConfig) { - t.Errorf("wrong Resources[3].RawConfig.Body %#v; want %#v", gotConfig, wantConfig) - } - } - { - r := cfg.Resources[4] - - if got, want := r.Id(), "data.do.depends"; got != want { - t.Errorf("wrong Resources[4].Id() %#v; want %#v", got, want) - } - if got, want := r.DependsOn, []string{"data.do.simple"}; !reflect.DeepEqual(got, want) { - t.Errorf("wrong Resources[4].DependsOn %#v; want %#v", got, want) - } - if got, want := r.Provider, ""; got != want { - t.Errorf("wrong Resources[4].Provider %#v; want %#v", got, want) - } - - wantConfig := map[string]string{} - var gotConfig map[string]string - gohcl2.DecodeBody(r.RawConfig.Body, nil, &gotConfig) - if !reflect.DeepEqual(gotConfig, wantConfig) { - t.Errorf("wrong Resources[4].RawConfig.Body %#v; want %#v", gotConfig, wantConfig) - } - } - } - - // "variable" blocks - if got, want := len(cfg.Variables), 3; got != want { - t.Errorf("Variables slice has wrong length %#v; want %#v", got, want) - } else { - { - v := cfg.Variables[0] - - if got, want := v.Name, "foo"; got != want { - t.Errorf("wrong Variables[0].Name %#v; want %#v", got, want) - } - if got, want := v.Default, "bar"; got != want { - t.Errorf("wrong Variables[0].Default %#v; want %#v", got, want) - } - if got, want := v.Description, "barbar"; got != want { - t.Errorf("wrong Variables[0].Description %#v; want %#v", got, want) - } - if got, want := v.DeclaredType, ""; got != want { - t.Errorf("wrong Variables[0].DeclaredType %#v; want %#v", got, want) - } - } - { - v := cfg.Variables[1] - - if got, want := v.Name, "bar"; got != want { - t.Errorf("wrong Variables[1].Name %#v; want %#v", got, want) - } - if got, want := v.Default, interface{}(nil); got != want { - t.Errorf("wrong Variables[1].Default %#v; want %#v", got, want) - } - if got, want := v.Description, ""; got != want { - t.Errorf("wrong Variables[1].Description %#v; want %#v", got, want) - } - if got, want := v.DeclaredType, "string"; got != want { - t.Errorf("wrong Variables[1].DeclaredType %#v; want %#v", got, want) - } - } - { - v := cfg.Variables[2] - - if got, want := v.Name, "baz"; got != want { - t.Errorf("wrong Variables[2].Name %#v; want %#v", got, want) - } - if got, want := v.Default, map[string]interface{}{"key": "value"}; !reflect.DeepEqual(got, want) { - t.Errorf("wrong Variables[2].Default %#v; want %#v", got, want) - } - if got, want := v.Description, ""; got != want { - t.Errorf("wrong Variables[2].Description %#v; want %#v", got, want) - } - if got, want := v.DeclaredType, "map"; got != want { - t.Errorf("wrong Variables[2].DeclaredType %#v; want %#v", got, want) - } - } - } - - // "output" blocks - if got, want := len(cfg.Outputs), 2; got != want { - t.Errorf("Outputs slice has wrong length %#v; want %#v", got, want) - } else { - { - o := cfg.Outputs[0] - - if got, want := o.Name, "web_ip"; got != want { - t.Errorf("wrong Outputs[0].Name %#v; want %#v", got, want) - } - if got, want := o.DependsOn, []string(nil); !reflect.DeepEqual(got, want) { - t.Errorf("wrong Outputs[0].DependsOn %#v; want %#v", got, want) - } - if got, want := o.Description, ""; got != want { - t.Errorf("wrong Outputs[0].Description %#v; want %#v", got, want) - } - if got, want := o.Sensitive, true; got != want { - t.Errorf("wrong Outputs[0].Sensitive %#v; want %#v", got, want) - } - - wantConfig := map[string]string{ - "value": "312.213.645.123", - } - var gotConfig map[string]string - ctx := &hcl2.EvalContext{ - Variables: map[string]cty.Value{ - "aws_instance": cty.ObjectVal(map[string]cty.Value{ - "web": cty.ObjectVal(map[string]cty.Value{ - "private_ip": cty.StringVal("312.213.645.123"), - }), - }), - }, - } - gohcl2.DecodeBody(o.RawConfig.Body, ctx, &gotConfig) - if !reflect.DeepEqual(gotConfig, wantConfig) { - t.Errorf("wrong Outputs[0].RawConfig.Body %#v; want %#v", gotConfig, wantConfig) - } - } - { - o := cfg.Outputs[1] - - if got, want := o.Name, "web_id"; got != want { - t.Errorf("wrong Outputs[1].Name %#v; want %#v", got, want) - } - if got, want := o.DependsOn, []string{"aws_instance.db"}; !reflect.DeepEqual(got, want) { - t.Errorf("wrong Outputs[1].DependsOn %#v; want %#v", got, want) - } - if got, want := o.Description, "The ID"; got != want { - t.Errorf("wrong Outputs[1].Description %#v; want %#v", got, want) - } - if got, want := o.Sensitive, false; got != want { - t.Errorf("wrong Outputs[1].Sensitive %#v; want %#v", got, want) - } - } - } - - // "provider" blocks - if got, want := len(cfg.ProviderConfigs), 2; got != want { - t.Errorf("ProviderConfigs slice has wrong length %#v; want %#v", got, want) - } else { - { - p := cfg.ProviderConfigs[0] - - if got, want := p.Name, "aws"; got != want { - t.Errorf("wrong ProviderConfigs[0].Name %#v; want %#v", got, want) - } - if got, want := p.Alias, ""; got != want { - t.Errorf("wrong ProviderConfigs[0].Alias %#v; want %#v", got, want) - } - if got, want := p.Version, "1.0.0"; got != want { - t.Errorf("wrong ProviderConfigs[0].Version %#v; want %#v", got, want) - } - - wantConfig := map[string]string{ - "access_key": "foo", - "secret_key": "bar", - } - var gotConfig map[string]string - gohcl2.DecodeBody(p.RawConfig.Body, nil, &gotConfig) - if !reflect.DeepEqual(gotConfig, wantConfig) { - t.Errorf("wrong ProviderConfigs[0].RawConfig.Body %#v; want %#v", gotConfig, wantConfig) - } - - } - { - p := cfg.ProviderConfigs[1] - - if got, want := p.Name, "do"; got != want { - t.Errorf("wrong ProviderConfigs[1].Name %#v; want %#v", got, want) - } - if got, want := p.Alias, "fum"; got != want { - t.Errorf("wrong ProviderConfigs[1].Alias %#v; want %#v", got, want) - } - if got, want := p.Version, ""; got != want { - t.Errorf("wrong ProviderConfigs[1].Version %#v; want %#v", got, want) - } - - } - } - - // "locals" definitions - if got, want := len(cfg.Locals), 5; got != want { - t.Errorf("Locals slice has wrong length %#v; want %#v", got, want) - } else { - { - l := cfg.Locals[0] - - if got, want := l.Name, "security_group_ids"; got != want { - t.Errorf("wrong Locals[0].Name %#v; want %#v", got, want) - } - - wantConfig := map[string][]string{ - "value": []string{"sg-abc123"}, - } - var gotConfig map[string][]string - ctx := &hcl2.EvalContext{ - Variables: map[string]cty.Value{ - "aws_security_group": cty.ObjectVal(map[string]cty.Value{ - "firewall": cty.ObjectVal(map[string]cty.Value{ - "id": cty.StringVal("sg-abc123"), - }), - }), - }, - } - gohcl2.DecodeBody(l.RawConfig.Body, ctx, &gotConfig) - if !reflect.DeepEqual(gotConfig, wantConfig) { - t.Errorf("wrong Locals[0].RawConfig.Body %#v; want %#v", gotConfig, wantConfig) - } - } - { - l := cfg.Locals[1] - - if got, want := l.Name, "web_ip"; got != want { - t.Errorf("wrong Locals[1].Name %#v; want %#v", got, want) - } - } - { - l := cfg.Locals[2] - - if got, want := l.Name, "literal"; got != want { - t.Errorf("wrong Locals[2].Name %#v; want %#v", got, want) - } - } - { - l := cfg.Locals[3] - - if got, want := l.Name, "literal_list"; got != want { - t.Errorf("wrong Locals[3].Name %#v; want %#v", got, want) - } - } - { - l := cfg.Locals[4] - - if got, want := l.Name, "literal_map"; got != want { - t.Errorf("wrong Locals[4].Name %#v; want %#v", got, want) - } - } - } -} diff --git a/config/loader_hcl_test.go b/config/loader_hcl_test.go deleted file mode 100644 index 85d97e851..000000000 --- a/config/loader_hcl_test.go +++ /dev/null @@ -1,9 +0,0 @@ -package config - -import ( - "testing" -) - -func TestHCLConfigurableConfigurable(t *testing.T) { - var _ configurable = new(hclConfigurable) -} diff --git a/config/loader_test.go b/config/loader_test.go deleted file mode 100644 index f151aafd1..000000000 --- a/config/loader_test.go +++ /dev/null @@ -1,1316 +0,0 @@ -package config - -import ( - "io/ioutil" - "path/filepath" - "reflect" - "strings" - "testing" -) - -func TestErrNoConfigsFound_impl(t *testing.T) { - var _ error = new(ErrNoConfigsFound) -} - -func TestLoadFile_badType(t *testing.T) { - _, err := LoadFile(filepath.Join(fixtureDir, "bad_type.tf.nope")) - if err == nil { - t.Fatal("should have error") - } -} - -func TestLoadFile_gitCrypt(t *testing.T) { - _, err := LoadFile(filepath.Join(fixtureDir, "git-crypt.tf")) - if err == nil { - t.Fatal("should have error") - } - - t.Logf("err: %s", err) -} - -func TestLoadFile_lifecycleKeyCheck(t *testing.T) { - _, err := LoadFile(filepath.Join(fixtureDir, "lifecycle_cbd_typo.tf")) - if err == nil { - t.Fatal("should have error") - } - - t.Logf("err: %s", err) -} - -func TestLoadFile_varInvalidKey(t *testing.T) { - _, err := LoadFile(filepath.Join(fixtureDir, "var-invalid-key.tf")) - if err == nil { - t.Fatal("should have error") - } -} - -func TestLoadFile_resourceArityMistake(t *testing.T) { - _, err := LoadFile(filepath.Join(fixtureDir, "resource-arity-mistake.tf")) - if err == nil { - t.Fatal("should have error") - } - expected := "Error loading testdata/resource-arity-mistake.tf: position 2:10: resource must be followed by exactly two strings, a type and a name" - if err.Error() != expected { - t.Fatalf("expected:\n%s\ngot:\n%s", expected, err) - } -} - -func TestLoadFile_resourceMultiLifecycle(t *testing.T) { - _, err := LoadFile(filepath.Join(fixtureDir, "resource-multi-lifecycle.tf")) - if err == nil { - t.Fatal("should have error") - } -} - -func TestLoadFile_dataSourceArityMistake(t *testing.T) { - _, err := LoadFile(filepath.Join(fixtureDir, "data-source-arity-mistake.tf")) - if err == nil { - t.Fatal("should have error") - } - expected := "Error loading testdata/data-source-arity-mistake.tf: position 2:6: 'data' must be followed by exactly two strings: a type and a name" - if err.Error() != expected { - t.Fatalf("expected:\n%s\ngot:\n%s", expected, err) - } -} - -func TestLoadFileWindowsLineEndings(t *testing.T) { - testFile := filepath.Join(fixtureDir, "windows-line-endings.tf") - - contents, err := ioutil.ReadFile(testFile) - if err != nil { - t.Fatalf("err: %s", err) - } - if !strings.Contains(string(contents), "\r\n") { - t.Fatalf("Windows line endings test file %s contains no windows line endings - this may be an autocrlf related issue.", testFile) - } - - c, err := LoadFile(testFile) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - if c.Dir != "" { - t.Fatalf("bad: %#v", c.Dir) - } - - actual := resourcesStr(c.Resources) - if actual != strings.TrimSpace(windowsHeredocResourcesStr) { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestLoadFileHeredoc(t *testing.T) { - c, err := LoadFile(filepath.Join(fixtureDir, "heredoc.tf")) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - if c.Dir != "" { - t.Fatalf("bad: %#v", c.Dir) - } - - actual := providerConfigsStr(c.ProviderConfigs) - if actual != strings.TrimSpace(heredocProvidersStr) { - t.Fatalf("bad:\n%s", actual) - } - - actual = resourcesStr(c.Resources) - if actual != strings.TrimSpace(heredocResourcesStr) { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestLoadFileEscapedQuotes(t *testing.T) { - _, err := LoadFile(filepath.Join(fixtureDir, "escapedquotes.tf")) - if err == nil { - t.Fatalf("expected syntax error as escaped quotes are no longer supported") - } - - if !strings.Contains(err.Error(), "parse error") { - t.Fatalf("expected \"syntax error\", got: %s", err) - } -} - -func TestLoadFileBasic(t *testing.T) { - c, err := LoadFile(filepath.Join(fixtureDir, "basic.tf")) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - if c.Dir != "" { - t.Fatalf("wrong dir %#v; want %#v", c.Dir, "") - } - - expectedTF := &Terraform{RequiredVersion: "foo"} - if !reflect.DeepEqual(c.Terraform, expectedTF) { - t.Fatalf("wrong terraform block %#v; want %#v", c.Terraform, expectedTF) - } - - expectedAtlas := &AtlasConfig{Name: "mitchellh/foo"} - if !reflect.DeepEqual(c.Atlas, expectedAtlas) { - t.Fatalf("wrong atlas config %#v; want %#v", c.Atlas, expectedAtlas) - } - - actual := variablesStr(c.Variables) - if actual != strings.TrimSpace(basicVariablesStr) { - t.Fatalf("bad:\n%s", actual) - } - - actual = providerConfigsStr(c.ProviderConfigs) - if actual != strings.TrimSpace(basicProvidersStr) { - t.Fatalf("bad:\n%s", actual) - } - - actual = resourcesStr(c.Resources) - if actual != strings.TrimSpace(basicResourcesStr) { - t.Fatalf("bad:\n%s", actual) - } - - if actual, want := localsStr(c.Locals), strings.TrimSpace(basicLocalsStr); actual != want { - t.Fatalf("wrong locals:\n%s\nwant:\n%s", actual, want) - } - - actual = outputsStr(c.Outputs) - if actual != strings.TrimSpace(basicOutputsStr) { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestLoadFileBasic_empty(t *testing.T) { - c, err := LoadFile(filepath.Join(fixtureDir, "empty.tf")) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } -} - -func TestLoadFileBasic_import(t *testing.T) { - // Skip because we disabled importing - t.Skip() - - c, err := LoadFile(filepath.Join(fixtureDir, "import.tf")) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - actual := variablesStr(c.Variables) - if actual != strings.TrimSpace(importVariablesStr) { - t.Fatalf("bad:\n%s", actual) - } - - actual = providerConfigsStr(c.ProviderConfigs) - if actual != strings.TrimSpace(importProvidersStr) { - t.Fatalf("bad:\n%s", actual) - } - - actual = resourcesStr(c.Resources) - if actual != strings.TrimSpace(importResourcesStr) { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestLoadFileBasic_json(t *testing.T) { - c, err := LoadFile(filepath.Join(fixtureDir, "basic.tf.json")) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - if c.Dir != "" { - t.Fatalf("bad: %#v", c.Dir) - } - - expectedAtlas := &AtlasConfig{Name: "mitchellh/foo"} - if !reflect.DeepEqual(c.Atlas, expectedAtlas) { - t.Fatalf("bad: %#v", c.Atlas) - } - - actual := variablesStr(c.Variables) - if actual != strings.TrimSpace(basicVariablesStr) { - t.Fatalf("bad:\n%s", actual) - } - - actual = providerConfigsStr(c.ProviderConfigs) - if actual != strings.TrimSpace(basicProvidersStr) { - t.Fatalf("bad:\n%s", actual) - } - - actual = resourcesStr(c.Resources) - if actual != strings.TrimSpace(basicResourcesStr) { - t.Fatalf("bad:\n%s", actual) - } - - if actual, want := localsStr(c.Locals), strings.TrimSpace(basicLocalsStr); actual != want { - t.Fatalf("wrong locals:\n%s\nwant:\n%s", actual, want) - } - - actual = outputsStr(c.Outputs) - if actual != strings.TrimSpace(basicOutputsStr) { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestLoadFileBasic_modules(t *testing.T) { - c, err := LoadFile(filepath.Join(fixtureDir, "modules.tf")) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - if c.Dir != "" { - t.Fatalf("bad: %#v", c.Dir) - } - - actual := modulesStr(c.Modules) - if actual != strings.TrimSpace(modulesModulesStr) { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestLoadFile_unnamedModule(t *testing.T) { - _, err := LoadFile(filepath.Join(fixtureDir, "module-unnamed.tf")) - if err == nil { - t.Fatalf("bad: expected error") - } - - errorStr := err.Error() - if !strings.Contains(errorStr, `"module" must be followed`) { - t.Fatalf("bad: expected error has wrong text: %s", errorStr) - } -} - -func TestLoadFile_outputDependsOn(t *testing.T) { - c, err := LoadFile(filepath.Join(fixtureDir, "output-depends-on.tf")) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - if c.Dir != "" { - t.Fatalf("bad: %#v", c.Dir) - } - - actual := outputsStr(c.Outputs) - if actual != strings.TrimSpace(outputDependsOnStr) { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestLoadFile_terraformBackend(t *testing.T) { - c, err := LoadFile(filepath.Join(fixtureDir, "terraform-backend.tf")) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - if c.Dir != "" { - t.Fatalf("bad: %#v", c.Dir) - } - - { - actual := terraformStr(c.Terraform) - expected := strings.TrimSpace(` -backend (s3) - foo`) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } - } -} - -func TestLoadFile_terraformBackendJSON(t *testing.T) { - c, err := LoadFile(filepath.Join(fixtureDir, "terraform-backend.tf.json")) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - if c.Dir != "" { - t.Fatalf("bad: %#v", c.Dir) - } - - { - actual := terraformStr(c.Terraform) - expected := strings.TrimSpace(` -backend (s3) - foo`) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } - } -} - -// test that the alternate, more obvious JSON format also decodes properly -func TestLoadFile_terraformBackendJSON2(t *testing.T) { - c, err := LoadFile(filepath.Join(fixtureDir, "terraform-backend-2.tf.json")) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - if c.Dir != "" { - t.Fatalf("bad: %#v", c.Dir) - } - - { - actual := terraformStr(c.Terraform) - expected := strings.TrimSpace(` -backend (s3) - foo`) - if actual != expected { - t.Fatalf("bad:\n%s", actual) - } - } -} - -func TestLoadFile_terraformBackendMulti(t *testing.T) { - _, err := LoadFile(filepath.Join(fixtureDir, "terraform-backend-multi.tf")) - if err == nil { - t.Fatal("expected error") - } - - errorStr := err.Error() - if !strings.Contains(errorStr, "only one 'backend'") { - t.Fatalf("bad: expected error has wrong text: %s", errorStr) - } -} - -func TestLoadJSONBasic(t *testing.T) { - raw, err := ioutil.ReadFile(filepath.Join(fixtureDir, "basic.tf.json")) - if err != nil { - t.Fatalf("err: %s", err) - } - - c, err := LoadJSON(raw) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - if c.Dir != "" { - t.Fatalf("bad: %#v", c.Dir) - } - - expectedAtlas := &AtlasConfig{Name: "mitchellh/foo"} - if !reflect.DeepEqual(c.Atlas, expectedAtlas) { - t.Fatalf("bad: %#v", c.Atlas) - } - - actual := variablesStr(c.Variables) - if actual != strings.TrimSpace(basicVariablesStr) { - t.Fatalf("bad:\n%s", actual) - } - - actual = providerConfigsStr(c.ProviderConfigs) - if actual != strings.TrimSpace(basicProvidersStr) { - t.Fatalf("bad:\n%s", actual) - } - - actual = resourcesStr(c.Resources) - if actual != strings.TrimSpace(basicResourcesStr) { - t.Fatalf("bad:\n%s", actual) - } - - actual = outputsStr(c.Outputs) - if actual != strings.TrimSpace(basicOutputsStr) { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestLoadJSONAmbiguous(t *testing.T) { - js := ` -{ - "variable": { - "first": { - "default": { - "key": "val" - } - }, - "second": { - "description": "Described", - "default": { - "key": "val" - } - } - } -} -` - - c, err := LoadJSON([]byte(js)) - if err != nil { - t.Fatalf("err: %s", err) - } - - if len(c.Variables) != 2 { - t.Fatal("config should have 2 variables, found", len(c.Variables)) - } - - first := &Variable{ - Name: "first", - Default: map[string]interface{}{"key": "val"}, - } - second := &Variable{ - Name: "second", - Description: "Described", - Default: map[string]interface{}{"key": "val"}, - } - - if !reflect.DeepEqual(first, c.Variables[0]) { - t.Fatalf("\nexpected: %#v\ngot: %#v", first, c.Variables[0]) - } - - if !reflect.DeepEqual(second, c.Variables[1]) { - t.Fatalf("\nexpected: %#v\ngot: %#v", second, c.Variables[1]) - } -} - -func TestLoadFileBasic_jsonNoName(t *testing.T) { - c, err := LoadFile(filepath.Join(fixtureDir, "resource-no-name.tf.json")) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - actual := resourcesStr(c.Resources) - if actual != strings.TrimSpace(basicJsonNoNameResourcesStr) { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestLoadFile_variables(t *testing.T) { - c, err := LoadFile(filepath.Join(fixtureDir, "variables.tf")) - if err != nil { - t.Fatalf("err: %s", err) - } - if c == nil { - t.Fatal("config should not be nil") - } - - if c.Dir != "" { - t.Fatalf("bad: %#v", c.Dir) - } - - actual := variablesStr(c.Variables) - if actual != strings.TrimSpace(variablesVariablesStr) { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestLoadDir_basic(t *testing.T) { - dir := filepath.Join(fixtureDir, "dir-basic") - c, err := LoadDir(dir) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - dirAbs, err := filepath.Abs(dir) - if err != nil { - t.Fatalf("err: %s", err) - } - if c.Dir != dirAbs { - t.Fatalf("bad: %#v", c.Dir) - } - - actual := variablesStr(c.Variables) - if actual != strings.TrimSpace(dirBasicVariablesStr) { - t.Fatalf("bad:\n%s", actual) - } - - actual = providerConfigsStr(c.ProviderConfigs) - if actual != strings.TrimSpace(dirBasicProvidersStr) { - t.Fatalf("bad:\n%s", actual) - } - - actual = resourcesStr(c.Resources) - if actual != strings.TrimSpace(dirBasicResourcesStr) { - t.Fatalf("bad:\n%s", actual) - } - - actual = outputsStr(c.Outputs) - if actual != strings.TrimSpace(dirBasicOutputsStr) { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestLoadDir_file(t *testing.T) { - _, err := LoadDir(filepath.Join(fixtureDir, "variables.tf")) - if err == nil { - t.Fatal("should error") - } -} - -func TestLoadDir_noConfigs(t *testing.T) { - _, err := LoadDir(filepath.Join(fixtureDir, "dir-empty")) - if err == nil { - t.Fatal("should error") - } -} - -func TestLoadDir_noMerge(t *testing.T) { - c, err := LoadDir(filepath.Join(fixtureDir, "dir-merge")) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - if err := c.Validate(); err == nil { - t.Fatal("should not be valid") - } -} - -func TestLoadDir_override(t *testing.T) { - c, err := LoadDir(filepath.Join(fixtureDir, "dir-override")) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - actual := variablesStr(c.Variables) - if actual != strings.TrimSpace(dirOverrideVariablesStr) { - t.Fatalf("bad:\n%s", actual) - } - - actual = providerConfigsStr(c.ProviderConfigs) - if actual != strings.TrimSpace(dirOverrideProvidersStr) { - t.Fatalf("bad:\n%s", actual) - } - - actual = resourcesStr(c.Resources) - if actual != strings.TrimSpace(dirOverrideResourcesStr) { - t.Fatalf("bad:\n%s", actual) - } - - actual = outputsStr(c.Outputs) - if actual != strings.TrimSpace(dirOverrideOutputsStr) { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestLoadDir_overrideVar(t *testing.T) { - c, err := LoadDir(filepath.Join(fixtureDir, "dir-override-var")) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - actual := variablesStr(c.Variables) - if actual != strings.TrimSpace(dirOverrideVarsVariablesStr) { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestLoadFile_mismatchedVariableTypes(t *testing.T) { - _, err := LoadFile(filepath.Join(fixtureDir, "variable-mismatched-type.tf")) - if err == nil { - t.Fatalf("bad: expected error") - } - - errorStr := err.Error() - if !strings.Contains(errorStr, "'not_a_map' has a default value which is not of type 'string'") { - t.Fatalf("bad: expected error has wrong text: %s", errorStr) - } -} - -func TestLoadFile_badVariableTypes(t *testing.T) { - _, err := LoadFile(filepath.Join(fixtureDir, "bad-variable-type.tf")) - if err == nil { - t.Fatalf("bad: expected error") - } - - errorStr := err.Error() - if !strings.Contains(errorStr, "'bad_type' type must be one of") { - t.Fatalf("bad: expected error has wrong text: %s", errorStr) - } -} - -func TestLoadFile_variableNoName(t *testing.T) { - _, err := LoadFile(filepath.Join(fixtureDir, "variable-no-name.tf")) - if err == nil { - t.Fatalf("bad: expected error") - } - - errorStr := err.Error() - if !strings.Contains(errorStr, `"variable" must be followed`) { - t.Fatalf("bad: expected error has wrong text: %s", errorStr) - } -} - -func TestLoadFile_provisioners(t *testing.T) { - c, err := LoadFile(filepath.Join(fixtureDir, "provisioners.tf")) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - actual := resourcesStr(c.Resources) - if actual != strings.TrimSpace(provisionerResourcesStr) { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestLoadFile_provisionersDestroy(t *testing.T) { - c, err := LoadFile(filepath.Join(fixtureDir, "provisioners-destroy.tf")) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - actual := resourcesStr(c.Resources) - if actual != strings.TrimSpace(provisionerDestroyResourcesStr) { - t.Fatalf("bad:\n%s", actual) - } -} - -func TestLoadFile_unnamedOutput(t *testing.T) { - _, err := LoadFile(filepath.Join(fixtureDir, "output-unnamed.tf")) - if err == nil { - t.Fatalf("bad: expected error") - } - - errorStr := err.Error() - if !strings.Contains(errorStr, `"output" must be followed`) { - t.Fatalf("bad: expected error has wrong text: %s", errorStr) - } -} - -func TestLoadFile_connections(t *testing.T) { - c, err := LoadFile(filepath.Join(fixtureDir, "connection.tf")) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - actual := resourcesStr(c.Resources) - if actual != strings.TrimSpace(connectionResourcesStr) { - t.Fatalf("bad:\n%s", actual) - } - - // Check for the connection info - r := c.Resources[0] - if r.Name != "web" && r.Type != "aws_instance" { - t.Fatalf("Bad: %#v", r) - } - - p1 := r.Provisioners[0] - if p1.ConnInfo == nil || len(p1.ConnInfo.Raw) != 2 { - t.Fatalf("Bad: %#v", p1.ConnInfo) - } - if p1.ConnInfo.Raw["user"] != "nobody" { - t.Fatalf("Bad: %#v", p1.ConnInfo) - } - - p2 := r.Provisioners[1] - if p2.ConnInfo == nil || len(p2.ConnInfo.Raw) != 2 { - t.Fatalf("Bad: %#v", p2.ConnInfo) - } - if p2.ConnInfo.Raw["user"] != "root" { - t.Fatalf("Bad: %#v", p2.ConnInfo) - } -} - -func TestLoadFile_createBeforeDestroy(t *testing.T) { - c, err := LoadFile(filepath.Join(fixtureDir, "create-before-destroy.tf")) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - actual := resourcesStr(c.Resources) - if actual != strings.TrimSpace(createBeforeDestroyResourcesStr) { - t.Fatalf("bad:\n%s", actual) - } - - // Check for the flag value - r := c.Resources[0] - if r.Name != "web" && r.Type != "aws_instance" { - t.Fatalf("Bad: %#v", r) - } - - // Should enable create before destroy - if !r.Lifecycle.CreateBeforeDestroy { - t.Fatalf("Bad: %#v", r) - } - - r = c.Resources[1] - if r.Name != "bar" && r.Type != "aws_instance" { - t.Fatalf("Bad: %#v", r) - } - - // Should not enable create before destroy - if r.Lifecycle.CreateBeforeDestroy { - t.Fatalf("Bad: %#v", r) - } -} - -func TestLoadFile_ignoreChanges(t *testing.T) { - c, err := LoadFile(filepath.Join(fixtureDir, "ignore-changes.tf")) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - actual := resourcesStr(c.Resources) - if actual != strings.TrimSpace(ignoreChangesResourcesStr) { - t.Fatalf("bad:\n%s", actual) - } - - // Check for the flag value - r := c.Resources[0] - if r.Name != "web" && r.Type != "aws_instance" { - t.Fatalf("Bad: %#v", r) - } - - // Should populate ignore changes - if len(r.Lifecycle.IgnoreChanges) == 0 { - t.Fatalf("Bad: %#v", r) - } - - r = c.Resources[1] - if r.Name != "bar" && r.Type != "aws_instance" { - t.Fatalf("Bad: %#v", r) - } - - // Should not populate ignore changes - if len(r.Lifecycle.IgnoreChanges) > 0 { - t.Fatalf("Bad: %#v", r) - } - - r = c.Resources[2] - if r.Name != "baz" && r.Type != "aws_instance" { - t.Fatalf("Bad: %#v", r) - } - - // Should not populate ignore changes - if len(r.Lifecycle.IgnoreChanges) > 0 { - t.Fatalf("Bad: %#v", r) - } -} - -func TestLoad_preventDestroyString(t *testing.T) { - c, err := LoadFile(filepath.Join(fixtureDir, "prevent-destroy-string.tf")) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - actual := resourcesStr(c.Resources) - if actual != strings.TrimSpace(createBeforeDestroyResourcesStr) { - t.Fatalf("bad:\n%s", actual) - } - - // Check for the flag value - r := c.Resources[0] - if r.Name != "web" && r.Type != "aws_instance" { - t.Fatalf("Bad: %#v", r) - } - - // Should enable create before destroy - if !r.Lifecycle.PreventDestroy { - t.Fatalf("Bad: %#v", r) - } - - r = c.Resources[1] - if r.Name != "bar" && r.Type != "aws_instance" { - t.Fatalf("Bad: %#v", r) - } - - // Should not enable create before destroy - if r.Lifecycle.PreventDestroy { - t.Fatalf("Bad: %#v", r) - } -} - -func TestLoad_temporary_files(t *testing.T) { - _, err := LoadDir(filepath.Join(fixtureDir, "dir-temporary-files")) - if err == nil { - t.Fatalf("Expected to see an error stating no config files found") - } -} - -func TestLoad_hclAttributes(t *testing.T) { - c, err := LoadFile(filepath.Join(fixtureDir, "attributes.tf")) - if err != nil { - t.Fatalf("Bad: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - actual := resourcesStr(c.Resources) - if actual != strings.TrimSpace(jsonAttributeStr) { - t.Fatalf("bad:\n%s", actual) - } - - r := c.Resources[0] - if r.Name != "test" && r.Type != "cloudstack_firewall" { - t.Fatalf("Bad: %#v", r) - } - - raw := r.RawConfig - if raw.Raw["ipaddress"] != "192.168.0.1" { - t.Fatalf("Bad: %s", raw.Raw["ipAddress"]) - } - - rule := raw.Raw["rule"].([]map[string]interface{})[0] - if rule["protocol"] != "tcp" { - t.Fatalf("Bad: %s", rule["protocol"]) - } - - if rule["source_cidr"] != "10.0.0.0/8" { - t.Fatalf("Bad: %s", rule["source_cidr"]) - } - - ports := rule["ports"].([]interface{}) - - if ports[0] != "80" { - t.Fatalf("Bad ports: %s", ports[0]) - } - if ports[1] != "1000-2000" { - t.Fatalf("Bad ports: %s", ports[1]) - } -} - -func TestLoad_jsonAttributes(t *testing.T) { - c, err := LoadFile(filepath.Join(fixtureDir, "attributes.tf.json")) - if err != nil { - t.Fatalf("Bad: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - actual := resourcesStr(c.Resources) - if actual != strings.TrimSpace(jsonAttributeStr) { - t.Fatalf("bad:\n%s", actual) - } - - r := c.Resources[0] - if r.Name != "test" && r.Type != "cloudstack_firewall" { - t.Fatalf("Bad: %#v", r) - } - - raw := r.RawConfig - if raw.Raw["ipaddress"] != "192.168.0.1" { - t.Fatalf("Bad: %s", raw.Raw["ipAddress"]) - } - - rule := raw.Raw["rule"].([]map[string]interface{})[0] - if rule["protocol"] != "tcp" { - t.Fatalf("Bad: %s", rule["protocol"]) - } - - if rule["source_cidr"] != "10.0.0.0/8" { - t.Fatalf("Bad: %s", rule["source_cidr"]) - } - - ports := rule["ports"].([]interface{}) - - if ports[0] != "80" { - t.Fatalf("Bad ports: %s", ports[0]) - } - if ports[1] != "1000-2000" { - t.Fatalf("Bad ports: %s", ports[1]) - } -} - -func TestLoad_onlyOverride(t *testing.T) { - c, err := LoadDir(filepath.Join(fixtureDir, "dir-only-override")) - if err != nil { - t.Fatalf("err: %s", err) - } - - if c == nil { - t.Fatal("config should not be nil") - } - - actual := variablesStr(c.Variables) - if actual != strings.TrimSpace(dirOnlyOverrideVariablesStr) { - t.Fatalf("bad:\n%s", actual) - } -} - -const jsonAttributeStr = ` -cloudstack_firewall.test (x1) - ipaddress - rule -` - -const windowsHeredocResourcesStr = ` -aws_instance.test (x1) - user_data -` - -const heredocProvidersStr = ` -aws - access_key - secret_key -` - -const heredocResourcesStr = ` -aws_iam_policy.policy (x1) - description - name - path - policy -aws_instance.heredocwithnumbers (x1) - ami - provisioners - local-exec - command -aws_instance.test (x1) - ami - provisioners - remote-exec - inline -` - -const basicOutputsStr = ` -web_id - vars - resource: aws_instance.web.id - description - The ID -web_ip - vars - resource: aws_instance.web.private_ip -` - -const basicLocalsStr = ` -literal -literal_list -literal_map -security_group_ids - vars - resource: aws_security_group.firewall.*.id -web_ip - vars - resource: aws_instance.web.private_ip -` - -const basicProvidersStr = ` -aws - access_key - secret_key -do - api_key - vars - user: var.foo -` - -const basicResourcesStr = ` -aws_instance.db (x1) - VPC - security_groups - provisioners - file - destination - source - dependsOn - aws_instance.web - vars - resource: aws_security_group.firewall.*.id -aws_instance.web (x1) - ami - network_interface - security_groups - provisioners - file - destination - source - vars - resource: aws_security_group.firewall.foo - user: var.foo -aws_security_group.firewall (x5) -data.do.depends (x1) - dependsOn - data.do.simple -data.do.simple (x1) - foo -` - -const basicVariablesStr = ` -bar (required) (string) - <> - <> -baz (map) - map[key:value] - <> -foo - bar - bar -` - -const basicJsonNoNameResourcesStr = ` -aws_security_group.allow_external_http_https (x1) - tags -` - -const dirBasicOutputsStr = ` -web_ip - vars - resource: aws_instance.web.private_ip -` - -const dirBasicProvidersStr = ` -aws - access_key - secret_key -do - api_key - vars - user: var.foo -` - -const dirBasicResourcesStr = ` -aws_instance.db (x1) - security_groups - vars - resource: aws_security_group.firewall.*.id -aws_instance.web (x1) - ami - network_interface - security_groups - vars - resource: aws_security_group.firewall.foo - user: var.foo -aws_security_group.firewall (x5) -data.do.depends (x1) - dependsOn - data.do.simple -data.do.simple (x1) - foo -` - -const dirBasicVariablesStr = ` -foo - bar - bar -` - -const dirOverrideOutputsStr = ` -web_ip - vars - resource: aws_instance.web.private_ip -` - -const dirOverrideProvidersStr = ` -aws - access_key - secret_key -do - api_key - vars - user: var.foo -` - -const dirOverrideResourcesStr = ` -aws_instance.db (x1) - ami - security_groups -aws_instance.web (x1) - ami - foo - network_interface - security_groups - vars - resource: aws_security_group.firewall.foo - user: var.foo -aws_security_group.firewall (x5) -data.do.depends (x1) - hello - dependsOn - data.do.simple -data.do.simple (x1) - foo -` - -const dirOverrideVariablesStr = ` -foo - bar - bar -` - -const dirOverrideVarsVariablesStr = ` -foo - baz - bar -` - -const dirOnlyOverrideVariablesStr = ` -foo - bar - bar -` - -const importProvidersStr = ` -aws - bar - foo -` - -const importResourcesStr = ` -aws_security_group.db (x1) -aws_security_group.web (x1) -` - -const importVariablesStr = ` -bar (required) - <> - <> -foo - bar - bar -` - -const modulesModulesStr = ` -bar - source = baz - memory -` - -const provisionerResourcesStr = ` -aws_instance.web (x1) - ami - security_groups - provisioners - shell - path - vars - resource: aws_security_group.firewall.foo - user: var.foo -` - -const provisionerDestroyResourcesStr = ` -aws_instance.web (x1) - provisioners - shell - shell (destroy) - path - shell (destroy) - on_failure = continue - path -` - -const connectionResourcesStr = ` -aws_instance.web (x1) - ami - security_groups - provisioners - shell - path - shell - path - vars - resource: aws_security_group.firewall.foo - user: var.foo -` - -const outputDependsOnStr = ` -value - dependsOn - foo -` - -const variablesVariablesStr = ` -bar - <> - <> -baz - foo - <> -foo (required) - <> - <> -` - -const createBeforeDestroyResourcesStr = ` -aws_instance.bar (x1) - ami -aws_instance.web (x1) - ami -` - -const ignoreChangesResourcesStr = ` -aws_instance.bar (x1) - ami -aws_instance.baz (x1) - ami -aws_instance.web (x1) - ami -` diff --git a/config/merge.go b/config/merge.go deleted file mode 100644 index 55fc864f7..000000000 --- a/config/merge.go +++ /dev/null @@ -1,204 +0,0 @@ -package config - -// Merge merges two configurations into a single configuration. -// -// Merge allows for the two configurations to have duplicate resources, -// because the resources will be merged. This differs from a single -// Config which must only have unique resources. -func Merge(c1, c2 *Config) (*Config, error) { - c := new(Config) - - // Merge unknown keys - unknowns := make(map[string]struct{}) - for _, k := range c1.unknownKeys { - _, present := unknowns[k] - if !present { - unknowns[k] = struct{}{} - c.unknownKeys = append(c.unknownKeys, k) - } - } - for _, k := range c2.unknownKeys { - _, present := unknowns[k] - if !present { - unknowns[k] = struct{}{} - c.unknownKeys = append(c.unknownKeys, k) - } - } - - // Merge Atlas configuration. This is a dumb one overrides the other - // sort of merge. - c.Atlas = c1.Atlas - if c2.Atlas != nil { - c.Atlas = c2.Atlas - } - - // Merge the Terraform configuration - if c1.Terraform != nil { - c.Terraform = c1.Terraform - if c2.Terraform != nil { - c.Terraform.Merge(c2.Terraform) - } - } else { - c.Terraform = c2.Terraform - } - - // NOTE: Everything below is pretty gross. Due to the lack of generics - // in Go, there is some hoop-jumping involved to make this merging a - // little more test-friendly and less repetitive. Ironically, making it - // less repetitive involves being a little repetitive, but I prefer to - // be repetitive with things that are less error prone than things that - // are more error prone (more logic). Type conversions to an interface - // are pretty low-error. - - var m1, m2, mresult []merger - - // Modules - m1 = make([]merger, 0, len(c1.Modules)) - m2 = make([]merger, 0, len(c2.Modules)) - for _, v := range c1.Modules { - m1 = append(m1, v) - } - for _, v := range c2.Modules { - m2 = append(m2, v) - } - mresult = mergeSlice(m1, m2) - if len(mresult) > 0 { - c.Modules = make([]*Module, len(mresult)) - for i, v := range mresult { - c.Modules[i] = v.(*Module) - } - } - - // Outputs - m1 = make([]merger, 0, len(c1.Outputs)) - m2 = make([]merger, 0, len(c2.Outputs)) - for _, v := range c1.Outputs { - m1 = append(m1, v) - } - for _, v := range c2.Outputs { - m2 = append(m2, v) - } - mresult = mergeSlice(m1, m2) - if len(mresult) > 0 { - c.Outputs = make([]*Output, len(mresult)) - for i, v := range mresult { - c.Outputs[i] = v.(*Output) - } - } - - // Provider Configs - m1 = make([]merger, 0, len(c1.ProviderConfigs)) - m2 = make([]merger, 0, len(c2.ProviderConfigs)) - for _, v := range c1.ProviderConfigs { - m1 = append(m1, v) - } - for _, v := range c2.ProviderConfigs { - m2 = append(m2, v) - } - mresult = mergeSlice(m1, m2) - if len(mresult) > 0 { - c.ProviderConfigs = make([]*ProviderConfig, len(mresult)) - for i, v := range mresult { - c.ProviderConfigs[i] = v.(*ProviderConfig) - } - } - - // Resources - m1 = make([]merger, 0, len(c1.Resources)) - m2 = make([]merger, 0, len(c2.Resources)) - for _, v := range c1.Resources { - m1 = append(m1, v) - } - for _, v := range c2.Resources { - m2 = append(m2, v) - } - mresult = mergeSlice(m1, m2) - if len(mresult) > 0 { - c.Resources = make([]*Resource, len(mresult)) - for i, v := range mresult { - c.Resources[i] = v.(*Resource) - } - } - - // Variables - m1 = make([]merger, 0, len(c1.Variables)) - m2 = make([]merger, 0, len(c2.Variables)) - for _, v := range c1.Variables { - m1 = append(m1, v) - } - for _, v := range c2.Variables { - m2 = append(m2, v) - } - mresult = mergeSlice(m1, m2) - if len(mresult) > 0 { - c.Variables = make([]*Variable, len(mresult)) - for i, v := range mresult { - c.Variables[i] = v.(*Variable) - } - } - - // Local Values - // These are simpler than the other config elements because they are just - // flat values and so no deep merging is required. - if localsCount := len(c1.Locals) + len(c2.Locals); localsCount != 0 { - // Explicit length check above because we want c.Locals to remain - // nil if the result would be empty. - c.Locals = make([]*Local, 0, len(c1.Locals)+len(c2.Locals)) - c.Locals = append(c.Locals, c1.Locals...) - c.Locals = append(c.Locals, c2.Locals...) - } - - return c, nil -} - -// merger is an interface that must be implemented by types that are -// merge-able. This simplifies the implementation of Merge for the various -// components of a Config. -type merger interface { - mergerName() string - mergerMerge(merger) merger -} - -// mergeSlice merges a slice of mergers. -func mergeSlice(m1, m2 []merger) []merger { - r := make([]merger, len(m1), len(m1)+len(m2)) - copy(r, m1) - - m := map[string]struct{}{} - for _, v2 := range m2 { - // If we already saw it, just append it because its a - // duplicate and invalid... - name := v2.mergerName() - if _, ok := m[name]; ok { - r = append(r, v2) - continue - } - m[name] = struct{}{} - - // Find an original to override - var original merger - originalIndex := -1 - for i, v := range m1 { - if v.mergerName() == name { - originalIndex = i - original = v - break - } - } - - var v merger - if original == nil { - v = v2 - } else { - v = original.mergerMerge(v2) - } - - if originalIndex == -1 { - r = append(r, v) - } else { - r[originalIndex] = v - } - } - - return r -} diff --git a/config/merge_test.go b/config/merge_test.go deleted file mode 100644 index ae7c7eec0..000000000 --- a/config/merge_test.go +++ /dev/null @@ -1,499 +0,0 @@ -package config - -import ( - "fmt" - "reflect" - "testing" - - "github.com/davecgh/go-spew/spew" -) - -func TestMerge(t *testing.T) { - cases := []struct { - c1, c2, result *Config - err bool - }{ - // Normal good case. - { - &Config{ - Atlas: &AtlasConfig{ - Name: "foo", - }, - Modules: []*Module{ - &Module{Name: "foo"}, - }, - Outputs: []*Output{ - &Output{Name: "foo"}, - }, - ProviderConfigs: []*ProviderConfig{ - &ProviderConfig{Name: "foo"}, - }, - Resources: []*Resource{ - &Resource{Name: "foo"}, - }, - Variables: []*Variable{ - &Variable{Name: "foo"}, - }, - Locals: []*Local{ - &Local{Name: "foo"}, - }, - - unknownKeys: []string{"foo"}, - }, - - &Config{ - Atlas: &AtlasConfig{ - Name: "bar", - }, - Modules: []*Module{ - &Module{Name: "bar"}, - }, - Outputs: []*Output{ - &Output{Name: "bar"}, - }, - ProviderConfigs: []*ProviderConfig{ - &ProviderConfig{Name: "bar"}, - }, - Resources: []*Resource{ - &Resource{Name: "bar"}, - }, - Variables: []*Variable{ - &Variable{Name: "bar"}, - }, - Locals: []*Local{ - &Local{Name: "bar"}, - }, - - unknownKeys: []string{"bar"}, - }, - - &Config{ - Atlas: &AtlasConfig{ - Name: "bar", - }, - Modules: []*Module{ - &Module{Name: "foo"}, - &Module{Name: "bar"}, - }, - Outputs: []*Output{ - &Output{Name: "foo"}, - &Output{Name: "bar"}, - }, - ProviderConfigs: []*ProviderConfig{ - &ProviderConfig{Name: "foo"}, - &ProviderConfig{Name: "bar"}, - }, - Resources: []*Resource{ - &Resource{Name: "foo"}, - &Resource{Name: "bar"}, - }, - Variables: []*Variable{ - &Variable{Name: "foo"}, - &Variable{Name: "bar"}, - }, - Locals: []*Local{ - &Local{Name: "foo"}, - &Local{Name: "bar"}, - }, - - unknownKeys: []string{"foo", "bar"}, - }, - - false, - }, - - // Test that when merging duplicates, it merges into the - // first, but keeps the duplicates so that errors still - // happen. - { - &Config{ - Outputs: []*Output{ - &Output{Name: "foo"}, - }, - ProviderConfigs: []*ProviderConfig{ - &ProviderConfig{Name: "foo"}, - }, - Resources: []*Resource{ - &Resource{Name: "foo"}, - }, - Variables: []*Variable{ - &Variable{Name: "foo", Default: "foo"}, - &Variable{Name: "foo"}, - }, - Locals: []*Local{ - &Local{Name: "foo"}, - }, - - unknownKeys: []string{"foo"}, - }, - - &Config{ - Outputs: []*Output{ - &Output{Name: "bar"}, - }, - ProviderConfigs: []*ProviderConfig{ - &ProviderConfig{Name: "bar"}, - }, - Resources: []*Resource{ - &Resource{Name: "bar"}, - }, - Variables: []*Variable{ - &Variable{Name: "foo", Default: "bar"}, - &Variable{Name: "bar"}, - }, - Locals: []*Local{ - &Local{Name: "foo"}, - }, - - unknownKeys: []string{"bar"}, - }, - - &Config{ - Outputs: []*Output{ - &Output{Name: "foo"}, - &Output{Name: "bar"}, - }, - ProviderConfigs: []*ProviderConfig{ - &ProviderConfig{Name: "foo"}, - &ProviderConfig{Name: "bar"}, - }, - Resources: []*Resource{ - &Resource{Name: "foo"}, - &Resource{Name: "bar"}, - }, - Variables: []*Variable{ - &Variable{Name: "foo", Default: "bar"}, - &Variable{Name: "foo"}, - &Variable{Name: "bar"}, - }, - Locals: []*Local{ - &Local{Name: "foo"}, - &Local{Name: "foo"}, - }, - - unknownKeys: []string{"foo", "bar"}, - }, - - false, - }, - - // Terraform block - { - &Config{ - Terraform: &Terraform{ - RequiredVersion: "A", - }, - }, - &Config{}, - &Config{ - Terraform: &Terraform{ - RequiredVersion: "A", - }, - }, - false, - }, - - { - &Config{}, - &Config{ - Terraform: &Terraform{ - RequiredVersion: "A", - }, - }, - &Config{ - Terraform: &Terraform{ - RequiredVersion: "A", - }, - }, - false, - }, - - // Provider alias - { - &Config{ - ProviderConfigs: []*ProviderConfig{ - &ProviderConfig{Alias: "foo"}, - }, - }, - &Config{}, - &Config{ - ProviderConfigs: []*ProviderConfig{ - &ProviderConfig{Alias: "foo"}, - }, - }, - false, - }, - - { - &Config{}, - &Config{ - ProviderConfigs: []*ProviderConfig{ - &ProviderConfig{Alias: "foo"}, - }, - }, - &Config{ - ProviderConfigs: []*ProviderConfig{ - &ProviderConfig{Alias: "foo"}, - }, - }, - false, - }, - - { - &Config{ - ProviderConfigs: []*ProviderConfig{ - &ProviderConfig{Alias: "bar"}, - }, - }, - &Config{ - ProviderConfigs: []*ProviderConfig{ - &ProviderConfig{Alias: "foo"}, - }, - }, - &Config{ - ProviderConfigs: []*ProviderConfig{ - &ProviderConfig{Alias: "foo"}, - }, - }, - false, - }, - - // Variable type - { - &Config{ - Variables: []*Variable{ - &Variable{DeclaredType: "foo"}, - }, - }, - &Config{}, - &Config{ - Variables: []*Variable{ - &Variable{DeclaredType: "foo"}, - }, - }, - false, - }, - - { - &Config{}, - &Config{ - Variables: []*Variable{ - &Variable{DeclaredType: "foo"}, - }, - }, - &Config{ - Variables: []*Variable{ - &Variable{DeclaredType: "foo"}, - }, - }, - false, - }, - - { - &Config{ - Variables: []*Variable{ - &Variable{DeclaredType: "bar"}, - }, - }, - &Config{ - Variables: []*Variable{ - &Variable{DeclaredType: "foo"}, - }, - }, - &Config{ - Variables: []*Variable{ - &Variable{DeclaredType: "foo"}, - }, - }, - false, - }, - - // Output description - { - &Config{ - Outputs: []*Output{ - &Output{Description: "foo"}, - }, - }, - &Config{}, - &Config{ - Outputs: []*Output{ - &Output{Description: "foo"}, - }, - }, - false, - }, - - { - &Config{}, - &Config{ - Outputs: []*Output{ - &Output{Description: "foo"}, - }, - }, - &Config{ - Outputs: []*Output{ - &Output{Description: "foo"}, - }, - }, - false, - }, - - { - &Config{ - Outputs: []*Output{ - &Output{Description: "bar"}, - }, - }, - &Config{ - Outputs: []*Output{ - &Output{Description: "foo"}, - }, - }, - &Config{ - Outputs: []*Output{ - &Output{Description: "foo"}, - }, - }, - false, - }, - - // Output depends_on - { - &Config{ - Outputs: []*Output{ - &Output{DependsOn: []string{"foo"}}, - }, - }, - &Config{}, - &Config{ - Outputs: []*Output{ - &Output{DependsOn: []string{"foo"}}, - }, - }, - false, - }, - - { - &Config{}, - &Config{ - Outputs: []*Output{ - &Output{DependsOn: []string{"foo"}}, - }, - }, - &Config{ - Outputs: []*Output{ - &Output{DependsOn: []string{"foo"}}, - }, - }, - false, - }, - - { - &Config{ - Outputs: []*Output{ - &Output{DependsOn: []string{"bar"}}, - }, - }, - &Config{ - Outputs: []*Output{ - &Output{DependsOn: []string{"foo"}}, - }, - }, - &Config{ - Outputs: []*Output{ - &Output{DependsOn: []string{"foo"}}, - }, - }, - false, - }, - - // Output sensitive - { - &Config{ - Outputs: []*Output{ - &Output{Sensitive: true}, - }, - }, - &Config{}, - &Config{ - Outputs: []*Output{ - &Output{Sensitive: true}, - }, - }, - false, - }, - - { - &Config{}, - &Config{ - Outputs: []*Output{ - &Output{Sensitive: true}, - }, - }, - &Config{ - Outputs: []*Output{ - &Output{Sensitive: true}, - }, - }, - false, - }, - - { - &Config{ - Outputs: []*Output{ - &Output{Sensitive: false}, - }, - }, - &Config{ - Outputs: []*Output{ - &Output{Sensitive: true}, - }, - }, - &Config{ - Outputs: []*Output{ - &Output{Sensitive: true}, - }, - }, - false, - }, - - // terraform blocks are merged, not overwritten - { - &Config{ - Terraform: &Terraform{ - RequiredVersion: "A", - }, - }, - &Config{ - Terraform: &Terraform{ - Backend: &Backend{ - Type: "test", - }, - }, - }, - &Config{ - Terraform: &Terraform{ - RequiredVersion: "A", - Backend: &Backend{ - Type: "test", - }, - }, - }, - false, - }, - } - - for i, tc := range cases { - t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { - actual, err := Merge(tc.c1, tc.c2) - if err != nil != tc.err { - t.Errorf("unexpected error: %s", err) - } - - if !reflect.DeepEqual(actual, tc.result) { - t.Errorf("wrong result\ngot: %swant: %s", spew.Sdump(actual), spew.Sdump(tc.result)) - } - }) - } -} diff --git a/config/providers.go b/config/providers.go deleted file mode 100644 index eeddabc32..000000000 --- a/config/providers.go +++ /dev/null @@ -1,61 +0,0 @@ -package config - -import "github.com/blang/semver" - -// ProviderVersionConstraint presents a constraint for a particular -// provider, identified by its full name. -type ProviderVersionConstraint struct { - Constraint string - ProviderType string -} - -// ProviderVersionConstraints is a map from provider full name to its associated -// ProviderVersionConstraint, as produced by Config.RequiredProviders. -type ProviderVersionConstraints map[string]ProviderVersionConstraint - -// RequiredRanges returns a semver.Range for each distinct provider type in -// the constraint map. If the same provider type appears more than once -// (e.g. because aliases are in use) then their respective constraints are -// combined such that they must *all* apply. -// -// The result of this method can be passed to the -// PluginMetaSet.ConstrainVersions method within the plugin/discovery -// package in order to filter down the available plugins to those which -// satisfy the given constraints. -// -// This function will panic if any of the constraints within cannot be -// parsed as semver ranges. This is guaranteed to never happen for a -// constraint set that was built from a configuration that passed validation. -func (cons ProviderVersionConstraints) RequiredRanges() map[string]semver.Range { - ret := make(map[string]semver.Range, len(cons)) - - for _, con := range cons { - spec := semver.MustParseRange(con.Constraint) - if existing, exists := ret[con.ProviderType]; exists { - ret[con.ProviderType] = existing.AND(spec) - } else { - ret[con.ProviderType] = spec - } - } - - return ret -} - -// ProviderConfigsByFullName returns a map from provider full names (as -// returned by ProviderConfig.FullName()) to the corresponding provider -// configs. -// -// This function returns no new information than what's already in -// c.ProviderConfigs, but returns it in a more convenient shape. If there -// is more than one provider config with the same full name then the result -// is undefined, but that is guaranteed not to happen for any config that -// has passed validation. -func (c *Config) ProviderConfigsByFullName() map[string]*ProviderConfig { - ret := make(map[string]*ProviderConfig, len(c.ProviderConfigs)) - - for _, pc := range c.ProviderConfigs { - ret[pc.FullName()] = pc - } - - return ret -} diff --git a/config/provisioner_enums.go b/config/provisioner_enums.go deleted file mode 100644 index 00fd43fce..000000000 --- a/config/provisioner_enums.go +++ /dev/null @@ -1,40 +0,0 @@ -package config - -// ProvisionerWhen is an enum for valid values for when to run provisioners. -type ProvisionerWhen int - -const ( - ProvisionerWhenInvalid ProvisionerWhen = iota - ProvisionerWhenCreate - ProvisionerWhenDestroy -) - -var provisionerWhenStrs = map[ProvisionerWhen]string{ - ProvisionerWhenInvalid: "invalid", - ProvisionerWhenCreate: "create", - ProvisionerWhenDestroy: "destroy", -} - -func (v ProvisionerWhen) String() string { - return provisionerWhenStrs[v] -} - -// ProvisionerOnFailure is an enum for valid values for on_failure options -// for provisioners. -type ProvisionerOnFailure int - -const ( - ProvisionerOnFailureInvalid ProvisionerOnFailure = iota - ProvisionerOnFailureContinue - ProvisionerOnFailureFail -) - -var provisionerOnFailureStrs = map[ProvisionerOnFailure]string{ - ProvisionerOnFailureInvalid: "invalid", - ProvisionerOnFailureContinue: "continue", - ProvisionerOnFailureFail: "fail", -} - -func (v ProvisionerOnFailure) String() string { - return provisionerOnFailureStrs[v] -} diff --git a/config/resource_mode.go b/config/resource_mode.go deleted file mode 100644 index dd915217c..000000000 --- a/config/resource_mode.go +++ /dev/null @@ -1,9 +0,0 @@ -package config - -//go:generate go run golang.org/x/tools/cmd/stringer -type=ResourceMode -output=resource_mode_string.go resource_mode.go -type ResourceMode int - -const ( - ManagedResourceMode ResourceMode = iota - DataResourceMode -) diff --git a/config/resource_mode_string.go b/config/resource_mode_string.go deleted file mode 100644 index 010527824..000000000 --- a/config/resource_mode_string.go +++ /dev/null @@ -1,24 +0,0 @@ -// Code generated by "stringer -type=ResourceMode -output=resource_mode_string.go resource_mode.go"; DO NOT EDIT. - -package config - -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/config/testdata/.gitattributes b/config/testdata/.gitattributes deleted file mode 100644 index 23c56cad5..000000000 --- a/config/testdata/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -windows-line-endings.tf eol=crlf diff --git a/config/testdata/attributes.tf b/config/testdata/attributes.tf deleted file mode 100644 index 2fe0291e0..000000000 --- a/config/testdata/attributes.tf +++ /dev/null @@ -1,15 +0,0 @@ -provider "cloudstack" { - api_url = "bla" - api_key = "bla" - secret_key = "bla" -} - -resource "cloudstack_firewall" "test" { - ipaddress = "192.168.0.1" - - rule { - source_cidr = "10.0.0.0/8" - protocol = "tcp" - ports = ["80", "1000-2000"] - } -} diff --git a/config/testdata/attributes.tf.json b/config/testdata/attributes.tf.json deleted file mode 100644 index 773274d48..000000000 --- a/config/testdata/attributes.tf.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "provider": { - "cloudstack": { - "api_url": "bla", - "api_key": "bla", - "secret_key": "bla" - } - }, - "resource": { - "cloudstack_firewall": { - "test": { - "ipaddress": "192.168.0.1", - "rule": [ - { - "source_cidr": "10.0.0.0/8", - "protocol": "tcp", - "ports": [ - "80", - "1000-2000" - ] - } - ] - } - } - } -} - diff --git a/config/testdata/backend-hash-basic/main.tf b/config/testdata/backend-hash-basic/main.tf deleted file mode 100644 index cf0fdfc1f..000000000 --- a/config/testdata/backend-hash-basic/main.tf +++ /dev/null @@ -1,7 +0,0 @@ -terraform { - backend "foo" { - foo = "bar" - bar = ["baz"] - map = { a = "b" } - } -} diff --git a/config/testdata/backend-hash-empty/main.tf b/config/testdata/backend-hash-empty/main.tf deleted file mode 100644 index 75db79290..000000000 --- a/config/testdata/backend-hash-empty/main.tf +++ /dev/null @@ -1 +0,0 @@ -terraform {} diff --git a/config/testdata/backend-hash-no-terraform/main.tf b/config/testdata/backend-hash-no-terraform/main.tf deleted file mode 100644 index b7db25411..000000000 --- a/config/testdata/backend-hash-no-terraform/main.tf +++ /dev/null @@ -1 +0,0 @@ -# Empty diff --git a/config/testdata/backend-hash-type-only/main.tf b/config/testdata/backend-hash-type-only/main.tf deleted file mode 100644 index ea3d6a3bf..000000000 --- a/config/testdata/backend-hash-type-only/main.tf +++ /dev/null @@ -1,4 +0,0 @@ -terraform { - backend "foo" { - } -} diff --git a/config/testdata/bad-variable-type.tf b/config/testdata/bad-variable-type.tf deleted file mode 100644 index e6b2aad4f..000000000 --- a/config/testdata/bad-variable-type.tf +++ /dev/null @@ -1,3 +0,0 @@ -variable "bad_type" { - type = "notatype" -} diff --git a/config/testdata/bad_type.tf.nope b/config/testdata/bad_type.tf.nope deleted file mode 100644 index 91084f7a1..000000000 --- a/config/testdata/bad_type.tf.nope +++ /dev/null @@ -1 +0,0 @@ -variable "foo" {} diff --git a/config/testdata/basic-hcl2.tf b/config/testdata/basic-hcl2.tf deleted file mode 100644 index 0bd8f5334..000000000 --- a/config/testdata/basic-hcl2.tf +++ /dev/null @@ -1,125 +0,0 @@ -#terraform:hcl2 - -terraform { - required_version = "foo" - - backend "baz" { - something = "nothing" - } -} - -variable "foo" { - default = "bar" - description = "barbar" -} - -variable "bar" { - type = "string" -} - -variable "baz" { - type = "map" - - default = { - key = "value" - } -} - -provider "aws" { - access_key = "foo" - secret_key = "bar" - version = "1.0.0" -} - -provider "do" { - api_key = var.foo - alias = "fum" -} - -data "do" "simple" { - foo = "baz" - provider = "do.foo" -} - -data "do" "depends" { - depends_on = ["data.do.simple"] -} - -resource "aws_security_group" "firewall" { - count = 5 - provider = "another" -} - -resource "aws_instance" "web" { - ami = "${var.foo}" - security_groups = [ - "foo", - aws_security_group.firewall.foo, - ] - - network_interface { - device_index = 0 - description = "Main network interface" - } - - connection { - default = true - } - - provisioner "file" { - source = "foo" - destination = "bar" - } -} - -locals { - security_group_ids = aws_security_group.firewall.*.id - web_ip = aws_instance.web.private_ip -} - -locals { - literal = 2 - literal_list = ["foo"] - literal_map = {"foo" = "bar"} -} - -resource "aws_instance" "db" { - security_groups = aws_security_group.firewall.*.id - VPC = "foo" - - tags = { - Name = "${var.bar}-database" - } - - depends_on = ["aws_instance.web"] - - provisioner "file" { - source = "here" - destination = "there" - - connection { - default = false - } - } -} - -output "web_ip" { - value = aws_instance.web.private_ip - sensitive = true -} - -output "web_id" { - description = "The ID" - value = aws_instance.web.id - depends_on = ["aws_instance.db"] -} - -atlas { - name = "example/foo" -} - -module "child" { - source = "./baz" - - toasty = true -} diff --git a/config/testdata/basic.tf b/config/testdata/basic.tf deleted file mode 100644 index a49b8ba3e..000000000 --- a/config/testdata/basic.tf +++ /dev/null @@ -1,95 +0,0 @@ -terraform { - required_version = "foo" -} - -variable "foo" { - default = "bar" - description = "bar" -} - -variable "bar" { - type = "string" -} - -variable "baz" { - type = "map" - - default = { - key = "value" - } -} - -provider "aws" { - access_key = "foo" - secret_key = "bar" -} - -provider "do" { - api_key = "${var.foo}" -} - -data "do" "simple" { - foo = "baz" -} - -data "do" "depends" { - depends_on = ["data.do.simple"] -} - -resource "aws_security_group" "firewall" { - count = 5 -} - -resource aws_instance "web" { - ami = "${var.foo}" - security_groups = [ - "foo", - "${aws_security_group.firewall.foo}" - ] - - network_interface { - device_index = 0 - description = "Main network interface" - } - - provisioner "file" { - source = "foo" - destination = "bar" - } -} - -locals { - security_group_ids = "${aws_security_group.firewall.*.id}" - web_ip = "${aws_instance.web.private_ip}" -} - -locals { - literal = 2 - literal_list = ["foo"] - literal_map = {"foo" = "bar"} -} - -resource "aws_instance" "db" { - security_groups = "${aws_security_group.firewall.*.id}" - VPC = "foo" - - depends_on = ["aws_instance.web"] - - provisioner "file" { - source = "foo" - destination = "bar" - } -} - -output "web_ip" { - value = "${aws_instance.web.private_ip}" -} - -output "web_id" { - description = "The ID" - value = "${aws_instance.web.id}" -} - -atlas { - name = "mitchellh/foo" -} diff --git a/config/testdata/basic.tf.json b/config/testdata/basic.tf.json deleted file mode 100644 index 39b9692c7..000000000 --- a/config/testdata/basic.tf.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "variable": { - "foo": { - "default": "bar", - "description": "bar" - }, - "bar": { - "type": "string" - }, - "baz": { - "type": "map", - "default": { - "key": "value" - } - } - }, - - "provider": { - "aws": { - "access_key": "foo", - "secret_key": "bar" - }, - - "do": { - "api_key": "${var.foo}" - } - }, - - "data": { - "do": { - "simple": { - "foo": "baz" - }, - "depends": { - "depends_on": ["data.do.simple"] - } - } - }, - - "resource": { - "aws_instance": { - "db": { - "security_groups": ["${aws_security_group.firewall.*.id}"], - "VPC": "foo", - "depends_on": ["aws_instance.web"], - - "provisioner": [{ - "file": { - "source": "foo", - "destination": "bar" - } - }] - }, - - "web": { - "ami": "${var.foo}", - "security_groups": [ - "foo", - "${aws_security_group.firewall.foo}" - ], - "network_interface": { - "device_index": 0, - "description": "Main network interface" - }, - - "provisioner": { - "file": { - "source": "foo", - "destination": "bar" - } - } - } - }, - - "aws_security_group": { - "firewall": { - "count": 5 - } - } - }, - - "locals": { - "security_group_ids": "${aws_security_group.firewall.*.id}", - "web_ip": "${aws_instance.web.private_ip}", - "literal": 2, - "literal_list": ["foo"], - "literal_map": {"foo": "bar"} - }, - - "output": { - "web_id": { - "description": "The ID", - "value": "${aws_instance.web.id}" - }, - "web_ip": { - "value": "${aws_instance.web.private_ip}" - } - }, - - "atlas": { - "name": "mitchellh/foo" - } -} diff --git a/config/testdata/connection.tf b/config/testdata/connection.tf deleted file mode 100644 index 25d47624b..000000000 --- a/config/testdata/connection.tf +++ /dev/null @@ -1,23 +0,0 @@ -resource "aws_instance" "web" { - ami = "${var.foo}" - security_groups = [ - "foo", - "${aws_security_group.firewall.foo}" - ] - - connection { - type = "ssh" - user = "root" - } - - provisioner "shell" { - path = "foo" - connection { - user = "nobody" - } - } - - provisioner "shell" { - path = "bar" - } -} diff --git a/config/testdata/copy-basic/main.tf b/config/testdata/copy-basic/main.tf deleted file mode 100644 index e0c0d8ddd..000000000 --- a/config/testdata/copy-basic/main.tf +++ /dev/null @@ -1,19 +0,0 @@ -variable "ref" { - default = "foo" -} - -resource "foo" "bar" { - depends_on = ["dep"] - provider = "foo-west" - count = 2 - attr = "value" - ref = "${var.ref}" - - provisioner "shell" { - inline = "echo" - } - - lifecycle { - ignore_changes = ["config"] - } -} diff --git a/config/testdata/count-int/main.tf b/config/testdata/count-int/main.tf deleted file mode 100644 index 213bb4dfa..000000000 --- a/config/testdata/count-int/main.tf +++ /dev/null @@ -1,3 +0,0 @@ -resource "foo" "bar" { - count = 5 -} diff --git a/config/testdata/count-list/main.tf b/config/testdata/count-list/main.tf deleted file mode 100644 index 3c6f1ab8f..000000000 --- a/config/testdata/count-list/main.tf +++ /dev/null @@ -1,3 +0,0 @@ -resource "foo" "bar" { - count = "${var.list}" -} diff --git a/config/testdata/count-string/main.tf b/config/testdata/count-string/main.tf deleted file mode 100644 index 6ad7191f7..000000000 --- a/config/testdata/count-string/main.tf +++ /dev/null @@ -1,3 +0,0 @@ -resource "foo" "bar" { - count = "5" -} diff --git a/config/testdata/count-var/main.tf b/config/testdata/count-var/main.tf deleted file mode 100644 index 789262030..000000000 --- a/config/testdata/count-var/main.tf +++ /dev/null @@ -1,3 +0,0 @@ -resource "foo" "bar" { - count = "${var.foo}" -} diff --git a/config/testdata/create-before-destroy.tf b/config/testdata/create-before-destroy.tf deleted file mode 100644 index a45ff5c19..000000000 --- a/config/testdata/create-before-destroy.tf +++ /dev/null @@ -1,14 +0,0 @@ - -resource "aws_instance" "web" { - ami = "foo" - lifecycle { - create_before_destroy = true - } -} - -resource "aws_instance" "bar" { - ami = "foo" - lifecycle { - create_before_destroy = false - } -} diff --git a/config/testdata/data-count/main.tf b/config/testdata/data-count/main.tf deleted file mode 100644 index e49a065df..000000000 --- a/config/testdata/data-count/main.tf +++ /dev/null @@ -1,3 +0,0 @@ -data "foo" "bar" { - count = 5 -} diff --git a/config/testdata/data-source-arity-mistake.tf b/config/testdata/data-source-arity-mistake.tf deleted file mode 100644 index 5d579a9db..000000000 --- a/config/testdata/data-source-arity-mistake.tf +++ /dev/null @@ -1,3 +0,0 @@ -# I forgot the data source name! -data "null" { -} diff --git a/config/testdata/dir-basic/README.md b/config/testdata/dir-basic/README.md deleted file mode 100644 index f33f7e9c0..000000000 --- a/config/testdata/dir-basic/README.md +++ /dev/null @@ -1,2 +0,0 @@ -This file just exists to test that LoadDir doesn't load non-Terraform -files. diff --git a/config/testdata/dir-basic/nested/nested.tf b/config/testdata/dir-basic/nested/nested.tf deleted file mode 100644 index 189c48dc0..000000000 --- a/config/testdata/dir-basic/nested/nested.tf +++ /dev/null @@ -1,3 +0,0 @@ -output "i-am-nested" { - value = "what" -} diff --git a/config/testdata/dir-basic/one.tf b/config/testdata/dir-basic/one.tf deleted file mode 100644 index 387c7b8d5..000000000 --- a/config/testdata/dir-basic/one.tf +++ /dev/null @@ -1,21 +0,0 @@ -variable "foo" { - default = "bar" - description = "bar" -} - -provider "aws" { - access_key = "foo" - secret_key = "bar" -} - -data "do" "simple" { - foo = "baz" -} - -resource "aws_instance" "db" { - security_groups = "${aws_security_group.firewall.*.id}" -} - -output "web_ip" { - value = "${aws_instance.web.private_ip}" -} diff --git a/config/testdata/dir-basic/two.tf b/config/testdata/dir-basic/two.tf deleted file mode 100644 index f64a28263..000000000 --- a/config/testdata/dir-basic/two.tf +++ /dev/null @@ -1,24 +0,0 @@ -provider "do" { - api_key = "${var.foo}" -} - -data "do" "depends" { - depends_on = ["data.do.simple"] -} - -resource "aws_security_group" "firewall" { - count = 5 -} - -resource aws_instance "web" { - ami = "${var.foo}" - security_groups = [ - "foo", - "${aws_security_group.firewall.foo}" - ] - - network_interface { - device_index = 0 - description = "Main network interface" - } -} diff --git a/config/testdata/dir-empty/.gitkeep b/config/testdata/dir-empty/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/config/testdata/dir-merge/one.tf b/config/testdata/dir-merge/one.tf deleted file mode 100644 index 57eadca8f..000000000 --- a/config/testdata/dir-merge/one.tf +++ /dev/null @@ -1,8 +0,0 @@ -variable "foo" { - default = "bar" - description = "bar" -} - -resource "aws_instance" "db" { - security_groups = "${aws_security_group.firewall.*.id}" -} diff --git a/config/testdata/dir-merge/two.tf b/config/testdata/dir-merge/two.tf deleted file mode 100644 index c0936f493..000000000 --- a/config/testdata/dir-merge/two.tf +++ /dev/null @@ -1,2 +0,0 @@ -resource "aws_instance" "db" { -} diff --git a/config/testdata/dir-only-override/main_override.tf b/config/testdata/dir-only-override/main_override.tf deleted file mode 100644 index f902801bd..000000000 --- a/config/testdata/dir-only-override/main_override.tf +++ /dev/null @@ -1,4 +0,0 @@ -variable "foo" { - default = "bar" - description = "bar" -} diff --git a/config/testdata/dir-override-var/main.tf b/config/testdata/dir-override-var/main.tf deleted file mode 100644 index f902801bd..000000000 --- a/config/testdata/dir-override-var/main.tf +++ /dev/null @@ -1,4 +0,0 @@ -variable "foo" { - default = "bar" - description = "bar" -} diff --git a/config/testdata/dir-override-var/main_override.tf b/config/testdata/dir-override-var/main_override.tf deleted file mode 100644 index a3fd68abb..000000000 --- a/config/testdata/dir-override-var/main_override.tf +++ /dev/null @@ -1,3 +0,0 @@ -variable "foo" { - default = "baz" -} diff --git a/config/testdata/dir-override/foo_override.tf.json b/config/testdata/dir-override/foo_override.tf.json deleted file mode 100644 index 4a43f09fc..000000000 --- a/config/testdata/dir-override/foo_override.tf.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "data": { - "do": { - "depends": { - "hello": "world" - } - } - }, - "resource": { - "aws_instance": { - "web": { - "foo": "bar" - } - } - } -} diff --git a/config/testdata/dir-override/one.tf b/config/testdata/dir-override/one.tf deleted file mode 100644 index 5ef1fe719..000000000 --- a/config/testdata/dir-override/one.tf +++ /dev/null @@ -1,22 +0,0 @@ -variable "foo" { - default = "bar" - description = "bar" -} - -provider "aws" { - access_key = "foo" - secret_key = "bar" -} - - -data "do" "simple" { - foo = "baz" -} - -resource "aws_instance" "db" { - security_groups = "${aws_security_group.firewall.*.id}" -} - -output "web_ip" { - value = "${aws_instance.web.private_ip}" -} diff --git a/config/testdata/dir-override/override.tf.json b/config/testdata/dir-override/override.tf.json deleted file mode 100644 index 36d04846d..000000000 --- a/config/testdata/dir-override/override.tf.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "resource": { - "aws_instance": { - "db": { - "ami": "foo", - "security_groups": "" - } - } - } -} diff --git a/config/testdata/dir-override/two.tf b/config/testdata/dir-override/two.tf deleted file mode 100644 index f64a28263..000000000 --- a/config/testdata/dir-override/two.tf +++ /dev/null @@ -1,24 +0,0 @@ -provider "do" { - api_key = "${var.foo}" -} - -data "do" "depends" { - depends_on = ["data.do.simple"] -} - -resource "aws_security_group" "firewall" { - count = 5 -} - -resource aws_instance "web" { - ami = "${var.foo}" - security_groups = [ - "foo", - "${aws_security_group.firewall.foo}" - ] - - network_interface { - device_index = 0 - description = "Main network interface" - } -} diff --git a/config/testdata/dir-temporary-files/#emacs-two.tf# b/config/testdata/dir-temporary-files/#emacs-two.tf# deleted file mode 100644 index acbb4f2f9..000000000 --- a/config/testdata/dir-temporary-files/#emacs-two.tf# +++ /dev/null @@ -1,20 +0,0 @@ -provider "do" { - api_key = "${var.foo}" -} - -resource "aws_security_group" "firewall" { - count = 5 -} - -resource aws_instance "web" { - ami = "${var.foo}" - security_groups = [ - "foo", - "${aws_security_group.firewall.foo}" - ] - - network_interface { - device_index = 0 - description = "Main network interface" - } -} diff --git a/config/testdata/dir-temporary-files/.#emacs-one.tf b/config/testdata/dir-temporary-files/.#emacs-one.tf deleted file mode 100644 index 1e049a87f..000000000 --- a/config/testdata/dir-temporary-files/.#emacs-one.tf +++ /dev/null @@ -1,17 +0,0 @@ -variable "foo" { - default = "bar" - description = "bar" -} - -provider "aws" { - access_key = "foo" - secret_key = "bar" -} - -resource "aws_instance" "db" { - security_groups = "${aws_security_group.firewall.*.id}" -} - -output "web_ip" { - value = "${aws_instance.web.private_ip}" -} diff --git a/config/testdata/dir-temporary-files/.hidden.tf b/config/testdata/dir-temporary-files/.hidden.tf deleted file mode 100644 index e69de29bb..000000000 diff --git a/config/testdata/dir-temporary-files/vim-one.tf~ b/config/testdata/dir-temporary-files/vim-one.tf~ deleted file mode 100644 index 1e049a87f..000000000 --- a/config/testdata/dir-temporary-files/vim-one.tf~ +++ /dev/null @@ -1,17 +0,0 @@ -variable "foo" { - default = "bar" - description = "bar" -} - -provider "aws" { - access_key = "foo" - secret_key = "bar" -} - -resource "aws_instance" "db" { - security_groups = "${aws_security_group.firewall.*.id}" -} - -output "web_ip" { - value = "${aws_instance.web.private_ip}" -} diff --git a/config/testdata/empty-collections/main.tf b/config/testdata/empty-collections/main.tf deleted file mode 100644 index bd7a631a9..000000000 --- a/config/testdata/empty-collections/main.tf +++ /dev/null @@ -1,11 +0,0 @@ -variable "empty_string" { - default = "" -} - -variable "empty_list" { - default = [] -} - -variable "empty_map" { - default = {} -} diff --git a/config/testdata/empty.tf b/config/testdata/empty.tf deleted file mode 100644 index e69de29bb..000000000 diff --git a/config/testdata/escapedquotes.tf b/config/testdata/escapedquotes.tf deleted file mode 100644 index 4fe9a020b..000000000 --- a/config/testdata/escapedquotes.tf +++ /dev/null @@ -1,7 +0,0 @@ -variable "ami" { - default = [ "ami", "abc123" ] -} - -resource "aws_instance" "quotes" { - ami = "${join(\",\", var.ami)}" -} diff --git a/config/testdata/git-crypt.tf b/config/testdata/git-crypt.tf deleted file mode 100644 index ecd90886a88b4f7654ce0c6f45160a95597191c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13 UcmZQ@_Y83kiVO&0IMl}l02men(EtDd diff --git a/config/testdata/hcl2-experiment-switch/not-eligible.tf.json b/config/testdata/hcl2-experiment-switch/not-eligible.tf.json deleted file mode 100644 index 3b3481276..000000000 --- a/config/testdata/hcl2-experiment-switch/not-eligible.tf.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "locals": { - "foo": "baz" - } -} diff --git a/config/testdata/hcl2-experiment-switch/not-opted-in.tf b/config/testdata/hcl2-experiment-switch/not-opted-in.tf deleted file mode 100644 index cff740755..000000000 --- a/config/testdata/hcl2-experiment-switch/not-opted-in.tf +++ /dev/null @@ -1,7 +0,0 @@ - -# The use of an equals to assign "locals" is something that would be rejected -# by the HCL2 parser (equals is reserved for attributes only) and so we can -# use it to verify that the old HCL parser was used. -locals { - foo = "bar" -} diff --git a/config/testdata/hcl2-experiment-switch/opted-in.tf b/config/testdata/hcl2-experiment-switch/opted-in.tf deleted file mode 100644 index bf1473064..000000000 --- a/config/testdata/hcl2-experiment-switch/opted-in.tf +++ /dev/null @@ -1,7 +0,0 @@ -#terraform:hcl2 - -locals { - # This direct expression is something that would be rejected by the old HCL - # parser, so we can use it as a marker that the HCL2 parser was used. - foo = 1 + 2 -} diff --git a/config/testdata/heredoc.tf b/config/testdata/heredoc.tf deleted file mode 100644 index c43fd0810..000000000 --- a/config/testdata/heredoc.tf +++ /dev/null @@ -1,51 +0,0 @@ -provider "aws" { - access_key = "foo" - secret_key = "bar" -} - -resource "aws_iam_policy" "policy" { - name = "test_policy" - path = "/" - description = "My test policy" - policy = <