diff --git a/terraform/transform_provider.go b/terraform/transform_provider.go index 1658e396d..7a6220df0 100644 --- a/terraform/transform_provider.go +++ b/terraform/transform_provider.go @@ -7,22 +7,29 @@ import ( "github.com/hashicorp/terraform/dag" ) +// GraphNodeProvider is an interface that nodes that can be a provider +// must implement. The ProviderName returned is the name of the provider +// they satisfy. +type GraphNodeProvider interface { + ProviderName() string +} + +// GraphNodeProviderConsumer is an interface that nodes that require +// a provider must implement. ProvidedBy must return the name of the provider +// to use. +type GraphNodeProviderConsumer interface { + ProvidedBy() string +} + // 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. type ProviderTransformer struct{} func (t *ProviderTransformer) Transform(g *Graph) error { - // First, build a map of the providers - m := make(map[string]dag.Vertex) - for _, v := range g.Vertices() { - if pv, ok := v.(GraphNodeProvider); ok { - m[pv.ProviderName()] = v - } - } - // Go through the other nodes and match them to providers they need var err error + m := providerVertexMap(g) for _, v := range g.Vertices() { if pv, ok := v.(GraphNodeProviderConsumer); ok { target := m[pv.ProvidedBy()] @@ -40,16 +47,49 @@ func (t *ProviderTransformer) Transform(g *Graph) error { return err } -// GraphNodeProvider is an interface that nodes that can be a provider -// must implement. The ProviderName returned is the name of the provider -// they satisfy. -type GraphNodeProvider interface { - ProviderName() string +// MissingProviderTransformer is a GraphTransformer that adds nodes +// for missing providers into the graph. Specifically, it creates provider +// configuration nodes for all the providers that we support. These are +// pruned later during an optimization pass. +type MissingProviderTransformer struct { + // Providers is the list of providers we support. + Providers []string } -// GraphNodeProviderConsumer is an interface that nodes that require -// a provider must implement. ProvidedBy must return the name of the provider -// to use. -type GraphNodeProviderConsumer interface { - ProvidedBy() string +func (t *MissingProviderTransformer) Transform(g *Graph) error { + m := providerVertexMap(g) + for _, p := range t.Providers { + if _, ok := m[p]; ok { + // This provider already exists as a configured node + continue + } + + // Add our own missing provider node to the graph + g.Add(&graphNodeMissingProvider{ProviderNameValue: p}) + } + + return nil +} + +type graphNodeMissingProvider struct { + ProviderNameValue string +} + +func (n *graphNodeMissingProvider) Name() string { + return fmt.Sprintf("provider.%s", n.ProviderNameValue) +} + +func (n *graphNodeMissingProvider) ProviderName() string { + return n.ProviderNameValue +} + +func providerVertexMap(g *Graph) map[string]dag.Vertex { + m := make(map[string]dag.Vertex) + for _, v := range g.Vertices() { + if pv, ok := v.(GraphNodeProvider); ok { + m[pv.ProviderName()] = v + } + } + + return m } diff --git a/terraform/transform_provider_test.go b/terraform/transform_provider_test.go index 254d4a38d..2389a9e58 100644 --- a/terraform/transform_provider_test.go +++ b/terraform/transform_provider_test.go @@ -3,6 +3,8 @@ package terraform import ( "strings" "testing" + + "github.com/hashicorp/terraform/dag" ) func TestProviderTransformer(t *testing.T) { @@ -28,8 +30,50 @@ func TestProviderTransformer(t *testing.T) { } } +func TestMissingProviderTransformer(t *testing.T) { + mod := testModule(t, "transform-provider-basic") + + g := Graph{Path: RootModulePath} + { + tf := &ConfigTransformer{Module: mod} + if err := tf.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + transform := &MissingProviderTransformer{Providers: []string{"foo"}} + if err := transform.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(g.String()) + expected := strings.TrimSpace(testTransformMissingProviderBasicStr) + 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) + var _ GraphNodeProvider = new(graphNodeMissingProvider) +} + +func TestGraphNodeMissingProvider_ProviderName(t *testing.T) { + n := &graphNodeMissingProvider{ProviderNameValue: "foo"} + if v := n.ProviderName(); v != "foo" { + t.Fatalf("bad: %#v", v) + } +} + const testTransformProviderBasicStr = ` aws_instance.web provider.aws provider.aws ` + +const testTransformMissingProviderBasicStr = ` +aws_instance.web +provider.aws +provider.foo +`