diff --git a/config/config.go b/config/config.go index a3b21c4f0..05a25216d 100644 --- a/config/config.go +++ b/config/config.go @@ -239,6 +239,20 @@ func (c *Config) Validate() error { } } + // Check that providers aren't declared multiple times. + providerSet := make(map[string]struct{}) + for _, p := range c.ProviderConfigs { + name := p.FullName() + if _, ok := providerSet[name]; ok { + errs = append(errs, fmt.Errorf( + "provider.%s: declared multiple times, you can only declare a provider once", + name)) + continue + } + + providerSet[name] = struct{}{} + } + // Check that all references to modules are valid modules := make(map[string]*Module) dupped := make(map[string]struct{}) @@ -416,6 +430,15 @@ func (c *Config) Validate() error { } } + // Verify provider points to a provider that is configured + if r.Provider != "" { + if _, ok := providerSet[r.Provider]; !ok { + errs = append(errs, fmt.Errorf( + "%s: resource depends on non-configured provider '%s'", + n, r.Provider)) + } + } + // Verify provisioners don't contain any splats for _, p := range r.Provisioners { // This validation checks that there are now splat variables @@ -651,6 +674,14 @@ func (o *Output) mergerMerge(m merger) merger { return &result } +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 } diff --git a/config/config_test.go b/config/config_test.go index 3f67bdbf6..64b6d6cef 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -203,6 +203,34 @@ func TestConfigValidate_pathVarInvalid(t *testing.T) { } } +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_providerMultiRefBad(t *testing.T) { + c := testConfig(t, "validate-provider-multi-ref-bad") + if err := c.Validate(); err == nil { + t.Fatal("should not be valid") + } +} + func TestConfigValidate_provConnSplatOther(t *testing.T) { c := testConfig(t, "validate-prov-conn-splat-other") if err := c.Validate(); err != nil { diff --git a/config/test-fixtures/validate-provider-multi-good/main.tf b/config/test-fixtures/validate-provider-multi-good/main.tf new file mode 100644 index 000000000..c02464be9 --- /dev/null +++ b/config/test-fixtures/validate-provider-multi-good/main.tf @@ -0,0 +1,7 @@ +provider "aws" { + alias = "bar" +} + +provider "aws" { + alias = "foo" +} diff --git a/config/test-fixtures/validate-provider-multi-ref-bad/main.tf b/config/test-fixtures/validate-provider-multi-ref-bad/main.tf new file mode 100644 index 000000000..dba674bc6 --- /dev/null +++ b/config/test-fixtures/validate-provider-multi-ref-bad/main.tf @@ -0,0 +1,7 @@ +provider "aws" { + alias = "bar" +} + +resource "aws_instance" "foo" { + provider = "aws.baz" +} diff --git a/config/test-fixtures/validate-provider-multi-ref-good/main.tf b/config/test-fixtures/validate-provider-multi-ref-good/main.tf new file mode 100644 index 000000000..bd814edf1 --- /dev/null +++ b/config/test-fixtures/validate-provider-multi-ref-good/main.tf @@ -0,0 +1,7 @@ +provider "aws" { + alias = "bar" +} + +resource "aws_instance" "foo" { + provider = "aws.bar" +} diff --git a/config/test-fixtures/validate-provider-multi/main.tf b/config/test-fixtures/validate-provider-multi/main.tf new file mode 100644 index 000000000..cfb0a1229 --- /dev/null +++ b/config/test-fixtures/validate-provider-multi/main.tf @@ -0,0 +1,7 @@ +provider "aws" { + alias = "foo" +} + +provider "aws" { + alias = "foo" +} diff --git a/terraform/context_test.go b/terraform/context_test.go index eba7e93d2..caee0f761 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -610,6 +610,43 @@ func TestContext2Plan_preventDestroy_destroyPlan(t *testing.T) { } } +func TestContext2Plan_providerAliasMissing(t *testing.T) { + m := testModule(t, "apply-provider-alias-missing") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.foo": &ResourceState{ + Type: "aws_instance", + Provider: "aws.foo", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "require_new": "abc", + }, + }, + }, + }, + }, + }, + } + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: state, + }) + + if _, err := ctx.Plan(); err == nil { + t.Fatal("should err") + } +} + func TestContext2Plan_computed(t *testing.T) { m := testModule(t, "plan-computed") p := testProvider("aws") @@ -2947,6 +2984,52 @@ func TestContext2Input_provider(t *testing.T) { } } +func TestContext2Input_providerMulti(t *testing.T) { + m := testModule(t, "input-provider-multi") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + var actual []interface{} + var lock sync.Mutex + p.InputFn = func(i UIInput, c *ResourceConfig) (*ResourceConfig, error) { + c.Config["foo"] = "bar" + return c, nil + } + p.ConfigureFn = func(c *ResourceConfig) error { + lock.Lock() + defer lock.Unlock() + actual = append(actual, c.Config["foo"]) + return nil + } + p.ValidateFn = func(c *ResourceConfig) ([]string, []error) { + return nil, c.CheckSet([]string{"foo"}) + } + + if err := ctx.Input(InputModeStd); err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := ctx.Plan(); err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := ctx.Apply(); err != nil { + t.Fatalf("err: %s", err) + } + + expected := []interface{}{"bar", "bar"} + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad: %#v", actual) + } +} + func TestContext2Input_providerOnce(t *testing.T) { m := testModule(t, "input-provider-once") p := testProvider("aws") diff --git a/terraform/test-fixtures/apply-provider-alias-missing/main.tf b/terraform/test-fixtures/apply-provider-alias-missing/main.tf new file mode 100644 index 000000000..ea50084fd --- /dev/null +++ b/terraform/test-fixtures/apply-provider-alias-missing/main.tf @@ -0,0 +1,8 @@ +provider "aws" { + alias = "bar" +} + +resource "aws_instance" "bar" { + foo = "bar" + provider = "aws.bar" +} diff --git a/terraform/test-fixtures/input-provider-multi/main.tf b/terraform/test-fixtures/input-provider-multi/main.tf new file mode 100644 index 000000000..f746e41a9 --- /dev/null +++ b/terraform/test-fixtures/input-provider-multi/main.tf @@ -0,0 +1,9 @@ +provider "aws" { + alias = "east" +} + +resource "aws_instance" "foo" { + alias = "east" +} + +resource "aws_instance" "bar" {}