diff --git a/terraform/context_test.go b/terraform/context_test.go index e2a7d6597..beb257e3c 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -2258,6 +2258,29 @@ func TestContext2Validate_moduleProviderVar(t *testing.T) { } } +func TestContext2Validate_moduleProviderInheritUnused(t *testing.T) { + m := testModule(t, "validate-module-pc-inherit-unused") + p := testProvider("aws") + c := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + p.ValidateFn = func(c *ResourceConfig) ([]string, []error) { + return nil, c.CheckSet([]string{"foo"}) + } + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) > 0 { + t.Fatalf("bad: %s", e) + } +} + func TestContext2Validate_orphans(t *testing.T) { p := testProvider("aws") m := testModule(t, "validate-good") diff --git a/terraform/eval_context.go b/terraform/eval_context.go index 120cf71e7..4f6d7c2e7 100644 --- a/terraform/eval_context.go +++ b/terraform/eval_context.go @@ -33,6 +33,7 @@ type EvalContext interface { // is used to store the provider configuration for inheritance lookups // with ParentProviderConfig(). ConfigureProvider(string, *ResourceConfig) error + SetProviderConfig(string, *ResourceConfig) error ParentProviderConfig(string) *ResourceConfig // ProviderInput and SetProviderInput are used to configure providers diff --git a/terraform/eval_context_builtin.go b/terraform/eval_context_builtin.go index 15acb01eb..d25ea76ff 100644 --- a/terraform/eval_context_builtin.go +++ b/terraform/eval_context_builtin.go @@ -106,6 +106,15 @@ func (ctx *BuiltinEvalContext) ConfigureProvider( return fmt.Errorf("Provider '%s' not initialized", n) } + if err := ctx.SetProviderConfig(n, cfg); err != nil { + return nil + } + + return p.Configure(cfg) +} + +func (ctx *BuiltinEvalContext) SetProviderConfig( + n string, cfg *ResourceConfig) error { providerPath := make([]string, len(ctx.Path())+1) copy(providerPath, ctx.Path()) providerPath[len(providerPath)-1] = n @@ -115,7 +124,7 @@ func (ctx *BuiltinEvalContext) ConfigureProvider( ctx.ProviderConfigCache[PathCacheKey(providerPath)] = cfg ctx.ProviderLock.Unlock() - return p.Configure(cfg) + return nil } func (ctx *BuiltinEvalContext) ProviderInput(n string) map[string]interface{} { diff --git a/terraform/eval_context_mock.go b/terraform/eval_context_mock.go index 3190f680a..27a98c2d5 100644 --- a/terraform/eval_context_mock.go +++ b/terraform/eval_context_mock.go @@ -38,6 +38,10 @@ type MockEvalContext struct { ConfigureProviderConfig *ResourceConfig ConfigureProviderError error + SetProviderConfigCalled bool + SetProviderConfigName string + SetProviderConfigConfig *ResourceConfig + ParentProviderConfigCalled bool ParentProviderConfigName string ParentProviderConfigConfig *ResourceConfig @@ -107,6 +111,14 @@ func (c *MockEvalContext) ConfigureProvider(n string, cfg *ResourceConfig) error return c.ConfigureProviderError } +func (c *MockEvalContext) SetProviderConfig( + n string, cfg *ResourceConfig) error { + c.SetProviderConfigCalled = true + c.SetProviderConfigName = n + c.SetProviderConfigConfig = cfg + return nil +} + func (c *MockEvalContext) ParentProviderConfig(n string) *ResourceConfig { c.ParentProviderConfigCalled = true c.ParentProviderConfigName = n diff --git a/terraform/eval_provider.go b/terraform/eval_provider.go index f8d61b1f1..56bbf94a9 100644 --- a/terraform/eval_provider.go +++ b/terraform/eval_provider.go @@ -6,6 +6,17 @@ import ( "github.com/hashicorp/terraform/config" ) +// EvalSetProviderConfig sets the parent configuration for a provider +// without configuring that provider, validating it, etc. +type EvalSetProviderConfig struct { + Provider string + Config **ResourceConfig +} + +func (n *EvalSetProviderConfig) Eval(ctx EvalContext) (interface{}, error) { + return nil, ctx.SetProviderConfig(n.Provider, *n.Config) +} + // EvalBuildProviderConfig outputs a *ResourceConfig that is properly // merged with parents and inputs on top of what is configured in the file. type EvalBuildProviderConfig struct { diff --git a/terraform/graph_config_node.go b/terraform/graph_config_node.go index cc525d2d5..cd3a08aae 100644 --- a/terraform/graph_config_node.go +++ b/terraform/graph_config_node.go @@ -209,6 +209,11 @@ func (n *GraphNodeConfigProvider) ProviderName() string { return n.Provider.Name } +// GraphNodeProvider implementation +func (n *GraphNodeConfigProvider) ProviderConfig() *config.RawConfig { + return n.Provider.RawConfig +} + // GraphNodeDotter impl. func (n *GraphNodeConfigProvider) Dot(name string) string { return fmt.Sprintf( diff --git a/terraform/test-fixtures/validate-module-pc-inherit-unused/child/main.tf b/terraform/test-fixtures/validate-module-pc-inherit-unused/child/main.tf new file mode 100644 index 000000000..919f140bb --- /dev/null +++ b/terraform/test-fixtures/validate-module-pc-inherit-unused/child/main.tf @@ -0,0 +1 @@ +resource "aws_instance" "foo" {} diff --git a/terraform/test-fixtures/validate-module-pc-inherit-unused/main.tf b/terraform/test-fixtures/validate-module-pc-inherit-unused/main.tf new file mode 100644 index 000000000..32c8a38f1 --- /dev/null +++ b/terraform/test-fixtures/validate-module-pc-inherit-unused/main.tf @@ -0,0 +1,7 @@ +module "child" { + source = "./child" +} + +provider "aws" { + foo = "set" +} diff --git a/terraform/transform_provider.go b/terraform/transform_provider.go index ea2b67c86..7011da36b 100644 --- a/terraform/transform_provider.go +++ b/terraform/transform_provider.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/hashicorp/go-multierror" + "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/dag" ) @@ -12,6 +13,7 @@ import ( // they satisfy. type GraphNodeProvider interface { ProviderName() string + ProviderConfig() *config.RawConfig } // GraphNodeProviderConsumer is an interface that nodes that require @@ -28,7 +30,8 @@ type DisableProviderTransformer struct{} func (t *DisableProviderTransformer) Transform(g *Graph) error { for _, v := range g.Vertices() { // We only care about providers - if _, ok := v.(GraphNodeProvider); !ok { + pn, ok := v.(GraphNodeProvider) + if !ok { continue } @@ -54,8 +57,13 @@ func (t *DisableProviderTransformer) Transform(g *Graph) error { continue } - // Disable the provider by removing it from the graph. - g.Remove(v) + // Disable the provider by replacing it with a "disabled" provider + disabled := &graphNodeDisabledProvider{GraphNodeProvider: pn} + if !g.Replace(v, disabled) { + panic(fmt.Sprintf( + "vertex disappeared from under us: %s", + dag.VertexName(v))) + } } return nil @@ -134,6 +142,36 @@ func (t *PruneProviderTransformer) Transform(g *Graph) error { return nil } +type graphNodeDisabledProvider struct { + GraphNodeProvider +} + +// GraphNodeEvalable impl. +func (n *graphNodeDisabledProvider) EvalTree() EvalNode { + var resourceConfig *ResourceConfig + + return &EvalOpFilter{ + Ops: []walkOperation{walkValidate, walkRefresh, walkPlan, walkApply}, + Node: &EvalSequence{ + Nodes: []EvalNode{ + &EvalInterpolate{ + Config: n.ProviderConfig(), + Output: &resourceConfig, + }, + &EvalBuildProviderConfig{ + Provider: n.ProviderName(), + Config: &resourceConfig, + Output: &resourceConfig, + }, + &EvalSetProviderConfig{ + Provider: n.ProviderName(), + Config: &resourceConfig, + }, + }, + }, + } +} + type graphNodeMissingProvider struct { ProviderNameValue string } @@ -151,6 +189,10 @@ func (n *graphNodeMissingProvider) ProviderName() string { return n.ProviderNameValue } +func (n *graphNodeMissingProvider) ProviderConfig() *config.RawConfig { + return nil +} + // GraphNodeDotter impl. func (n *graphNodeMissingProvider) Dot(name string) string { return fmt.Sprintf(