diff --git a/terraform/context_test.go b/terraform/context_test.go index b9de8d79d..e2a7d6597 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -2232,6 +2232,32 @@ func TestContext2Validate_moduleProviderInherit(t *testing.T) { } } +func TestContext2Validate_moduleProviderVar(t *testing.T) { + m := testModule(t, "validate-module-pc-vars") + p := testProvider("aws") + c := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Variables: map[string]string{ + "provider_var": "bar", + }, + }) + + 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/graph_builder.go b/terraform/graph_builder.go index 03c59f958..f08e20f16 100644 --- a/terraform/graph_builder.go +++ b/terraform/graph_builder.go @@ -99,6 +99,7 @@ func (b *BuiltinGraphBuilder) Steps() []GraphTransformer { &MissingProviderTransformer{Providers: b.Providers}, &ProviderTransformer{}, &PruneProviderTransformer{}, + &DisableProviderTransformer{}, // Provisioner-related transformations &MissingProvisionerTransformer{Provisioners: b.Provisioners}, diff --git a/terraform/graph_config_node.go b/terraform/graph_config_node.go index ddb96da2c..cc525d2d5 100644 --- a/terraform/graph_config_node.go +++ b/terraform/graph_config_node.go @@ -19,6 +19,10 @@ type graphNodeConfig interface { // be depended on. GraphNodeDependable GraphNodeDependent + + // ConfigType returns the type of thing in the configuration that + // this node represents, such as a resource, module, etc. + ConfigType() GraphNodeConfigType } // GraphNodeAddressable is an interface that all graph nodes for the @@ -48,6 +52,10 @@ type GraphNodeConfigModule struct { Tree *module.Tree } +func (n *GraphNodeConfigModule) ConfigType() GraphNodeConfigType { + return GraphNodeConfigTypeModule +} + func (n *GraphNodeConfigModule) DependableName() []string { return []string{n.Name()} } @@ -125,6 +133,10 @@ func (n *GraphNodeConfigOutput) Name() string { return fmt.Sprintf("output.%s", n.Output.Name) } +func (n *GraphNodeConfigOutput) ConfigType() GraphNodeConfigType { + return GraphNodeConfigTypeOutput +} + func (n *GraphNodeConfigOutput) DependableName() []string { return []string{n.Name()} } @@ -167,6 +179,10 @@ func (n *GraphNodeConfigProvider) Name() string { return fmt.Sprintf("provider.%s", n.Provider.Name) } +func (n *GraphNodeConfigProvider) ConfigType() GraphNodeConfigType { + return GraphNodeConfigTypeProvider +} + func (n *GraphNodeConfigProvider) DependableName() []string { return []string{n.Name()} } @@ -216,6 +232,10 @@ type GraphNodeConfigResource struct { Targets []ResourceAddress } +func (n *GraphNodeConfigResource) ConfigType() GraphNodeConfigType { + return GraphNodeConfigTypeResource +} + func (n *GraphNodeConfigResource) DependableName() []string { return []string{n.Resource.Id()} } @@ -545,6 +565,10 @@ func (n *graphNodeModuleExpanded) Name() string { return fmt.Sprintf("%s (expanded)", dag.VertexName(n.Original)) } +func (n *graphNodeModuleExpanded) ConfigType() GraphNodeConfigType { + return GraphNodeConfigTypeModule +} + // GraphNodeDotter impl. func (n *graphNodeModuleExpanded) Dot(name string) string { return fmt.Sprintf( diff --git a/terraform/graph_config_node_type.go b/terraform/graph_config_node_type.go new file mode 100644 index 000000000..f0196096f --- /dev/null +++ b/terraform/graph_config_node_type.go @@ -0,0 +1,15 @@ +package terraform + +//go:generate stringer -type=GraphNodeConfigType graph_config_node_type.go + +// GraphNodeConfigType is an enum for the type of thing that a graph +// node represents from the configuration. +type GraphNodeConfigType int + +const ( + GraphNodeConfigTypeInvalid GraphNodeConfigType = 0 + GraphNodeConfigTypeResource GraphNodeConfigType = iota + GraphNodeConfigTypeProvider + GraphNodeConfigTypeModule + GraphNodeConfigTypeOutput +) diff --git a/terraform/graphnodeconfigtype_string.go b/terraform/graphnodeconfigtype_string.go new file mode 100644 index 000000000..d0748979e --- /dev/null +++ b/terraform/graphnodeconfigtype_string.go @@ -0,0 +1,16 @@ +// generated by stringer -type=GraphNodeConfigType graph_config_node_type.go; DO NOT EDIT + +package terraform + +import "fmt" + +const _GraphNodeConfigType_name = "GraphNodeConfigTypeInvalidGraphNodeConfigTypeResourceGraphNodeConfigTypeProviderGraphNodeConfigTypeModuleGraphNodeConfigTypeOutput" + +var _GraphNodeConfigType_index = [...]uint8{0, 26, 53, 80, 105, 130} + +func (i GraphNodeConfigType) String() string { + if i < 0 || i+1 >= GraphNodeConfigType(len(_GraphNodeConfigType_index)) { + return fmt.Sprintf("GraphNodeConfigType(%d)", i) + } + return _GraphNodeConfigType_name[_GraphNodeConfigType_index[i]:_GraphNodeConfigType_index[i+1]] +} diff --git a/terraform/test-fixtures/plan-module-provider-defaults-var/main.tf b/terraform/test-fixtures/plan-module-provider-defaults-var/main.tf index 83b241154..e6e3f1c29 100644 --- a/terraform/test-fixtures/plan-module-provider-defaults-var/main.tf +++ b/terraform/test-fixtures/plan-module-provider-defaults-var/main.tf @@ -5,3 +5,5 @@ module "child" { provider "aws" { from = "${var.foo}" } + +resource "aws_instance" "foo" {} diff --git a/terraform/test-fixtures/transform-provider-disable-keep/child/main.tf b/terraform/test-fixtures/transform-provider-disable-keep/child/main.tf new file mode 100644 index 000000000..9d02c162c --- /dev/null +++ b/terraform/test-fixtures/transform-provider-disable-keep/child/main.tf @@ -0,0 +1,7 @@ +variable "value" {} + +provider "aws" { + value = "${var.value}" +} + +resource "aws_instance" "foo" {} diff --git a/terraform/test-fixtures/transform-provider-disable-keep/main.tf b/terraform/test-fixtures/transform-provider-disable-keep/main.tf new file mode 100644 index 000000000..7f9aa3f9f --- /dev/null +++ b/terraform/test-fixtures/transform-provider-disable-keep/main.tf @@ -0,0 +1,9 @@ +variable "foo" {} + +module "child" { + source = "./child" + + value = "${var.foo}" +} + +resource "aws_instance" "foo" {} diff --git a/terraform/test-fixtures/transform-provider-disable/child/main.tf b/terraform/test-fixtures/transform-provider-disable/child/main.tf new file mode 100644 index 000000000..9d02c162c --- /dev/null +++ b/terraform/test-fixtures/transform-provider-disable/child/main.tf @@ -0,0 +1,7 @@ +variable "value" {} + +provider "aws" { + value = "${var.value}" +} + +resource "aws_instance" "foo" {} diff --git a/terraform/test-fixtures/transform-provider-disable/main.tf b/terraform/test-fixtures/transform-provider-disable/main.tf new file mode 100644 index 000000000..a405f9895 --- /dev/null +++ b/terraform/test-fixtures/transform-provider-disable/main.tf @@ -0,0 +1,7 @@ +variable "foo" {} + +module "child" { + source = "./child" + + value = "${var.foo}" +} diff --git a/terraform/test-fixtures/validate-module-pc-vars/child/main.tf b/terraform/test-fixtures/validate-module-pc-vars/child/main.tf new file mode 100644 index 000000000..3b4e15483 --- /dev/null +++ b/terraform/test-fixtures/validate-module-pc-vars/child/main.tf @@ -0,0 +1,7 @@ +variable "value" {} + +provider "aws" { + foo = "${var.value}" +} + +resource "aws_instance" "foo" {} diff --git a/terraform/test-fixtures/validate-module-pc-vars/main.tf b/terraform/test-fixtures/validate-module-pc-vars/main.tf new file mode 100644 index 000000000..7d2d03e14 --- /dev/null +++ b/terraform/test-fixtures/validate-module-pc-vars/main.tf @@ -0,0 +1,7 @@ +variable "provider_var" {} + +module "child" { + source = "./child" + + value = "${var.provider_var}" +} diff --git a/terraform/transform_provider.go b/terraform/transform_provider.go index f6c566b26..ea2b67c86 100644 --- a/terraform/transform_provider.go +++ b/terraform/transform_provider.go @@ -21,6 +21,46 @@ type GraphNodeProviderConsumer interface { ProvidedBy() []string } +// DisableProviderTransformer "disables" any providers that are only +// depended on by modules. +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 { + continue + } + + // Go through all the up-edges (things that depend on this + // provider) and if any is not a module, then ignore this node. + nonModule := false + for _, sourceRaw := range g.UpEdges(v).List() { + source := sourceRaw.(dag.Vertex) + cn, ok := source.(graphNodeConfig) + if !ok { + nonModule = true + break + } + + if cn.ConfigType() != GraphNodeConfigTypeModule { + nonModule = true + break + } + } + if nonModule { + // We found something that depends on this provider that + // isn't a module, so skip it. + continue + } + + // Disable the provider by removing it from the graph. + g.Remove(v) + } + + return nil +} + // ProviderTransformer is a GraphTransformer that maps resources to // providers within the graph. This will error if there are any resources // that don't map to proper resources. diff --git a/terraform/transform_provider_test.go b/terraform/transform_provider_test.go index fdf25b7a4..d4d753456 100644 --- a/terraform/transform_provider_test.go +++ b/terraform/transform_provider_test.go @@ -92,6 +92,98 @@ func TestPruneProviderTransformer(t *testing.T) { } } +func TestDisableProviderTransformer(t *testing.T) { + mod := testModule(t, "transform-provider-disable") + + g := Graph{Path: RootModulePath} + { + tf := &ConfigTransformer{Module: mod} + if err := tf.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + { + transform := &MissingProviderTransformer{Providers: []string{"aws"}} + if err := transform.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + { + transform := &ProviderTransformer{} + if err := transform.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + { + transform := &PruneProviderTransformer{} + if err := transform.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + { + transform := &DisableProviderTransformer{} + if err := transform.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + actual := strings.TrimSpace(g.String()) + expected := strings.TrimSpace(testTransformDisableProviderBasicStr) + if actual != expected { + t.Fatalf("bad:\n\n%s", actual) + } +} + +func TestDisableProviderTransformer_keep(t *testing.T) { + mod := testModule(t, "transform-provider-disable-keep") + + g := Graph{Path: RootModulePath} + { + tf := &ConfigTransformer{Module: mod} + if err := tf.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + { + transform := &MissingProviderTransformer{Providers: []string{"aws"}} + if err := transform.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + { + transform := &ProviderTransformer{} + if err := transform.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + { + transform := &PruneProviderTransformer{} + if err := transform.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + { + transform := &DisableProviderTransformer{} + if err := transform.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + actual := strings.TrimSpace(g.String()) + expected := strings.TrimSpace(testTransformDisableProviderKeepStr) + if actual != expected { + t.Fatalf("bad:\n\n%s", actual) + } +} + func TestGraphNodeMissingProvider_impl(t *testing.T) { var _ dag.Vertex = new(graphNodeMissingProvider) var _ dag.NamedVertex = new(graphNodeMissingProvider) @@ -122,3 +214,15 @@ foo_instance.web provider.foo provider.foo ` + +const testTransformDisableProviderBasicStr = ` +module.child +` + +const testTransformDisableProviderKeepStr = ` +aws_instance.foo + provider.aws +module.child + provider.aws +provider.aws +` diff --git a/terraform/transform_resource.go b/terraform/transform_resource.go index 21774e953..91ef13196 100644 --- a/terraform/transform_resource.go +++ b/terraform/transform_resource.go @@ -119,6 +119,11 @@ func (n *graphNodeExpandedResource) ResourceAddress() *ResourceAddress { } } +// graphNodeConfig impl. +func (n *graphNodeExpandedResource) ConfigType() GraphNodeConfigType { + return GraphNodeConfigTypeResource +} + // GraphNodeDependable impl. func (n *graphNodeExpandedResource) DependableName() []string { return []string{ @@ -509,6 +514,11 @@ func (n *graphNodeExpandedResourceDestroy) Name() string { return fmt.Sprintf("%s (destroy)", n.graphNodeExpandedResource.Name()) } +// graphNodeConfig impl. +func (n *graphNodeExpandedResourceDestroy) ConfigType() GraphNodeConfigType { + return GraphNodeConfigTypeResource +} + // GraphNodeEvalable impl. func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode { info := n.instanceInfo()