| package configs |
| |
| import ( |
| "strings" |
| "testing" |
| |
| "github.com/hashicorp/terraform/internal/addrs" |
| "github.com/zclconf/go-cty/cty" |
| ) |
| |
| // TestNewModule_provider_fqns exercises module.gatherProviderLocalNames() |
| func TestNewModule_provider_local_name(t *testing.T) { |
| mod, diags := testModuleFromDir("testdata/providers-explicit-fqn") |
| if diags.HasErrors() { |
| t.Fatal(diags.Error()) |
| } |
| |
| p := addrs.NewProvider(addrs.DefaultProviderRegistryHost, "foo", "test") |
| if name, exists := mod.ProviderLocalNames[p]; !exists { |
| t.Fatal("provider FQN foo/test not found") |
| } else { |
| if name != "foo-test" { |
| t.Fatalf("provider localname mismatch: got %s, want foo-test", name) |
| } |
| } |
| |
| // ensure the reverse lookup (fqn to local name) works as well |
| localName := mod.LocalNameForProvider(p) |
| if localName != "foo-test" { |
| t.Fatal("provider local name not found") |
| } |
| |
| // if there is not a local name for a provider, it should return the type name |
| localName = mod.LocalNameForProvider(addrs.NewDefaultProvider("nonexist")) |
| if localName != "nonexist" { |
| t.Error("wrong local name returned for a non-local provider") |
| } |
| |
| // can also look up the "terraform" provider and see that it sources is |
| // allowed to be overridden, even though there is a builtin provider |
| // called "terraform". |
| p = addrs.NewProvider(addrs.DefaultProviderRegistryHost, "not-builtin", "not-terraform") |
| if name, exists := mod.ProviderLocalNames[p]; !exists { |
| t.Fatal("provider FQN not-builtin/not-terraform not found") |
| } else { |
| if name != "terraform" { |
| t.Fatalf("provider localname mismatch: got %s, want terraform", name) |
| } |
| } |
| } |
| |
| // This test validates the provider FQNs set in each Resource |
| func TestNewModule_resource_providers(t *testing.T) { |
| cfg, diags := testNestedModuleConfigFromDir(t, "testdata/valid-modules/nested-providers-fqns") |
| if diags.HasErrors() { |
| t.Fatal(diags.Error()) |
| } |
| |
| // both the root and child module have two resources, one which should use |
| // the default implied provider and one explicitly using a provider set in |
| // required_providers |
| wantImplicit := addrs.NewDefaultProvider("test") |
| wantFoo := addrs.NewProvider(addrs.DefaultProviderRegistryHost, "foo", "test") |
| wantBar := addrs.NewProvider(addrs.DefaultProviderRegistryHost, "bar", "test") |
| |
| // root module |
| if !cfg.Module.ManagedResources["test_instance.explicit"].Provider.Equals(wantFoo) { |
| t.Fatalf("wrong provider for \"test_instance.explicit\"\ngot: %s\nwant: %s", |
| cfg.Module.ManagedResources["test_instance.explicit"].Provider, |
| wantFoo, |
| ) |
| } |
| if !cfg.Module.ManagedResources["test_instance.implicit"].Provider.Equals(wantImplicit) { |
| t.Fatalf("wrong provider for \"test_instance.implicit\"\ngot: %s\nwant: %s", |
| cfg.Module.ManagedResources["test_instance.implicit"].Provider, |
| wantImplicit, |
| ) |
| } |
| |
| // a data source |
| if !cfg.Module.DataResources["data.test_resource.explicit"].Provider.Equals(wantFoo) { |
| t.Fatalf("wrong provider for \"module.child.test_instance.explicit\"\ngot: %s\nwant: %s", |
| cfg.Module.ManagedResources["test_instance.explicit"].Provider, |
| wantBar, |
| ) |
| } |
| |
| // child module |
| cm := cfg.Children["child"].Module |
| if !cm.ManagedResources["test_instance.explicit"].Provider.Equals(wantBar) { |
| t.Fatalf("wrong provider for \"module.child.test_instance.explicit\"\ngot: %s\nwant: %s", |
| cfg.Module.ManagedResources["test_instance.explicit"].Provider, |
| wantBar, |
| ) |
| } |
| if !cm.ManagedResources["test_instance.implicit"].Provider.Equals(wantImplicit) { |
| t.Fatalf("wrong provider for \"module.child.test_instance.implicit\"\ngot: %s\nwant: %s", |
| cfg.Module.ManagedResources["test_instance.implicit"].Provider, |
| wantImplicit, |
| ) |
| } |
| } |
| |
| func TestProviderForLocalConfig(t *testing.T) { |
| mod, diags := testModuleFromDir("testdata/providers-explicit-fqn") |
| if diags.HasErrors() { |
| t.Fatal(diags.Error()) |
| } |
| lc := addrs.LocalProviderConfig{LocalName: "foo-test"} |
| got := mod.ProviderForLocalConfig(lc) |
| want := addrs.NewProvider(addrs.DefaultProviderRegistryHost, "foo", "test") |
| if !got.Equals(want) { |
| t.Fatalf("wrong result! got %#v, want %#v\n", got, want) |
| } |
| } |
| |
| // At most one required_providers block per module is permitted. |
| func TestModule_required_providers_multiple(t *testing.T) { |
| _, diags := testModuleFromDir("testdata/invalid-modules/multiple-required-providers") |
| if !diags.HasErrors() { |
| t.Fatal("module should have error diags, but does not") |
| } |
| |
| want := `Duplicate required providers configuration` |
| if got := diags.Error(); !strings.Contains(got, want) { |
| t.Fatalf("expected error to contain %q\nerror was:\n%s", want, got) |
| } |
| } |
| |
| // A module may have required_providers configured in files loaded later than |
| // resources. These provider settings should still be reflected in the |
| // resources' configuration. |
| func TestModule_required_providers_after_resource(t *testing.T) { |
| mod, diags := testModuleFromDir("testdata/valid-modules/required-providers-after-resource") |
| if diags.HasErrors() { |
| t.Fatal(diags.Error()) |
| } |
| |
| want := addrs.NewProvider(addrs.DefaultProviderRegistryHost, "foo", "test") |
| |
| req, exists := mod.ProviderRequirements.RequiredProviders["test"] |
| if !exists { |
| t.Fatal("no provider requirements found for \"test\"") |
| } |
| if req.Type != want { |
| t.Errorf("wrong provider addr for \"test\"\ngot: %s\nwant: %s", |
| req.Type, want, |
| ) |
| } |
| |
| if got := mod.ManagedResources["test_instance.my-instance"].Provider; !got.Equals(want) { |
| t.Errorf("wrong provider addr for \"test_instance.my-instance\"\ngot: %s\nwant: %s", |
| got, want, |
| ) |
| } |
| } |
| |
| // We support overrides for required_providers blocks, which should replace the |
| // entire block for each provider localname, leaving other blocks unaffected. |
| // This should also be reflected in any resources in the module using this |
| // provider. |
| func TestModule_required_provider_overrides(t *testing.T) { |
| mod, diags := testModuleFromDir("testdata/valid-modules/required-providers-overrides") |
| if diags.HasErrors() { |
| t.Fatal(diags.Error()) |
| } |
| |
| // The foo provider and resource should be unaffected |
| want := addrs.NewProvider(addrs.DefaultProviderRegistryHost, "acme", "foo") |
| req, exists := mod.ProviderRequirements.RequiredProviders["foo"] |
| if !exists { |
| t.Fatal("no provider requirements found for \"foo\"") |
| } |
| if req.Type != want { |
| t.Errorf("wrong provider addr for \"foo\"\ngot: %s\nwant: %s", |
| req.Type, want, |
| ) |
| } |
| if got := mod.ManagedResources["foo_thing.ft"].Provider; !got.Equals(want) { |
| t.Errorf("wrong provider addr for \"foo_thing.ft\"\ngot: %s\nwant: %s", |
| got, want, |
| ) |
| } |
| |
| // The bar provider and resource should be using the override config |
| want = addrs.NewProvider(addrs.DefaultProviderRegistryHost, "blorp", "bar") |
| req, exists = mod.ProviderRequirements.RequiredProviders["bar"] |
| if !exists { |
| t.Fatal("no provider requirements found for \"bar\"") |
| } |
| if req.Type != want { |
| t.Errorf("wrong provider addr for \"bar\"\ngot: %s\nwant: %s", |
| req.Type, want, |
| ) |
| } |
| if gotVer, wantVer := req.Requirement.Required.String(), "~>2.0.0"; gotVer != wantVer { |
| t.Errorf("wrong provider version constraint for \"bar\"\ngot: %s\nwant: %s", |
| gotVer, wantVer, |
| ) |
| } |
| if got := mod.ManagedResources["bar_thing.bt"].Provider; !got.Equals(want) { |
| t.Errorf("wrong provider addr for \"bar_thing.bt\"\ngot: %s\nwant: %s", |
| got, want, |
| ) |
| } |
| } |
| |
| // Resources without explicit provider configuration are assigned a provider |
| // implied based on the resource type. For example, this resource: |
| // |
| // resource "foo_instance" "test" {} |
| // |
| // ...is assigned to whichever provider has local name "foo" in the current |
| // module. |
| // |
| // To find the correct provider, we first look in the module's provider |
| // requirements map for a local name matching the resource type, and fall back |
| // to a default provider if none is found. This applies to both managed and |
| // data resources. |
| func TestModule_implied_provider(t *testing.T) { |
| mod, diags := testModuleFromDir("testdata/valid-modules/implied-providers") |
| if diags.HasErrors() { |
| t.Fatal(diags.Error()) |
| } |
| |
| // The three providers used in the config resources |
| foo := addrs.NewProvider("registry.acme.corp", "acme", "foo") |
| whatever := addrs.NewProvider(addrs.DefaultProviderRegistryHost, "acme", "something") |
| bar := addrs.NewDefaultProvider("bar") |
| |
| // Verify that the registry.acme.corp/acme/foo provider is defined in the |
| // module provider requirements with local name "foo" |
| req, exists := mod.ProviderRequirements.RequiredProviders["foo"] |
| if !exists { |
| t.Fatal("no provider requirements found for \"foo\"") |
| } |
| if req.Type != foo { |
| t.Errorf("wrong provider addr for \"foo\"\ngot: %s\nwant: %s", |
| req.Type, foo, |
| ) |
| } |
| |
| // Verify that the acme/something provider is defined in the |
| // module provider requirements with local name "whatever" |
| req, exists = mod.ProviderRequirements.RequiredProviders["whatever"] |
| if !exists { |
| t.Fatal("no provider requirements found for \"foo\"") |
| } |
| if req.Type != whatever { |
| t.Errorf("wrong provider addr for \"whatever\"\ngot: %s\nwant: %s", |
| req.Type, whatever, |
| ) |
| } |
| |
| // Check that resources are assigned the correct providers: foo_* resources |
| // should have the custom foo provider, bar_* resources the default bar |
| // provider. |
| tests := []struct { |
| Address string |
| Provider addrs.Provider |
| }{ |
| {"foo_resource.a", foo}, |
| {"data.foo_resource.b", foo}, |
| {"bar_resource.c", bar}, |
| {"data.bar_resource.d", bar}, |
| {"whatever_resource.e", whatever}, |
| {"data.whatever_resource.f", whatever}, |
| } |
| for _, test := range tests { |
| resources := mod.ManagedResources |
| if strings.HasPrefix(test.Address, "data.") { |
| resources = mod.DataResources |
| } |
| resource, exists := resources[test.Address] |
| if !exists { |
| t.Errorf("could not find resource %q in %#v", test.Address, resources) |
| continue |
| } |
| if got := resource.Provider; !got.Equals(test.Provider) { |
| t.Errorf("wrong provider addr for %q\ngot: %s\nwant: %s", |
| test.Address, got, test.Provider, |
| ) |
| } |
| } |
| } |
| |
| func TestImpliedProviderForUnqualifiedType(t *testing.T) { |
| mod, diags := testModuleFromDir("testdata/valid-modules/implied-providers") |
| if diags.HasErrors() { |
| t.Fatal(diags.Error()) |
| } |
| |
| foo := addrs.NewProvider("registry.acme.corp", "acme", "foo") |
| whatever := addrs.NewProvider(addrs.DefaultProviderRegistryHost, "acme", "something") |
| bar := addrs.NewDefaultProvider("bar") |
| tf := addrs.NewBuiltInProvider("terraform") |
| |
| tests := []struct { |
| Type string |
| Provider addrs.Provider |
| }{ |
| {"foo", foo}, |
| {"whatever", whatever}, |
| {"bar", bar}, |
| {"terraform", tf}, |
| } |
| for _, test := range tests { |
| got := mod.ImpliedProviderForUnqualifiedType(test.Type) |
| if !got.Equals(test.Provider) { |
| t.Errorf("wrong result for %q: got %#v, want %#v\n", test.Type, got, test.Provider) |
| } |
| } |
| } |
| |
| func TestModule_backend_override(t *testing.T) { |
| mod, diags := testModuleFromDir("testdata/valid-modules/override-backend") |
| if diags.HasErrors() { |
| t.Fatal(diags.Error()) |
| } |
| |
| gotType := mod.Backend.Type |
| wantType := "bar" |
| |
| if gotType != wantType { |
| t.Errorf("wrong result for backend type: got %#v, want %#v\n", gotType, wantType) |
| } |
| |
| attrs, _ := mod.Backend.Config.JustAttributes() |
| |
| gotAttr, diags := attrs["path"].Expr.Value(nil) |
| if diags.HasErrors() { |
| t.Fatal(diags.Error()) |
| } |
| |
| wantAttr := cty.StringVal("CHANGED/relative/path/to/terraform.tfstate") |
| |
| if !gotAttr.RawEquals(wantAttr) { |
| t.Errorf("wrong result for backend 'path': got %#v, want %#v\n", gotAttr, wantAttr) |
| } |
| } |
| |
| // Unlike most other overrides, backend blocks do not require a base configuration in a primary |
| // configuration file, as an omitted backend there implies the local backend. |
| func TestModule_backend_override_no_base(t *testing.T) { |
| mod, diags := testModuleFromDir("testdata/valid-modules/override-backend-no-base") |
| if diags.HasErrors() { |
| t.Fatal(diags.Error()) |
| } |
| |
| if mod.Backend == nil { |
| t.Errorf("expected module Backend not to be nil") |
| } |
| } |
| |
| func TestModule_cloud_override_backend(t *testing.T) { |
| mod, diags := testModuleFromDir("testdata/valid-modules/override-backend-with-cloud") |
| if diags.HasErrors() { |
| t.Fatal(diags.Error()) |
| } |
| |
| if mod.Backend != nil { |
| t.Errorf("expected module Backend to be nil") |
| } |
| |
| if mod.CloudConfig == nil { |
| t.Errorf("expected module CloudConfig not to be nil") |
| } |
| } |
| |
| // Unlike most other overrides, cloud blocks do not require a base configuration in a primary |
| // configuration file, as an omitted backend there implies the local backend and cloud blocks |
| // override backends. |
| func TestModule_cloud_override_no_base(t *testing.T) { |
| mod, diags := testModuleFromDir("testdata/valid-modules/override-cloud-no-base") |
| if diags.HasErrors() { |
| t.Fatal(diags.Error()) |
| } |
| |
| if mod.CloudConfig == nil { |
| t.Errorf("expected module CloudConfig not to be nil") |
| } |
| } |
| |
| func TestModule_cloud_override(t *testing.T) { |
| mod, diags := testModuleFromDir("testdata/valid-modules/override-cloud") |
| if diags.HasErrors() { |
| t.Fatal(diags.Error()) |
| } |
| |
| attrs, _ := mod.CloudConfig.Config.JustAttributes() |
| |
| gotAttr, diags := attrs["organization"].Expr.Value(nil) |
| if diags.HasErrors() { |
| t.Fatal(diags.Error()) |
| } |
| |
| wantAttr := cty.StringVal("CHANGED") |
| |
| if !gotAttr.RawEquals(wantAttr) { |
| t.Errorf("wrong result for Cloud 'organization': got %#v, want %#v\n", gotAttr, wantAttr) |
| } |
| |
| // The override should have completely replaced the cloud block in the primary file, no merging |
| if attrs["should_not_be_present_with_override"] != nil { |
| t.Errorf("expected 'should_not_be_present_with_override' attribute to be nil") |
| } |
| } |
| |
| func TestModule_cloud_duplicate_overrides(t *testing.T) { |
| _, diags := testModuleFromDir("testdata/invalid-modules/override-cloud-duplicates") |
| want := `Duplicate Terraform Cloud configurations` |
| if got := diags.Error(); !strings.Contains(got, want) { |
| t.Fatalf("expected module error to contain %q\nerror was:\n%s", want, got) |
| } |
| } |