diff --git a/terraform/context_import_test.go b/terraform/context_import_test.go index 76727a4248..c423bb71f5 100644 --- a/terraform/context_import_test.go +++ b/terraform/context_import_test.go @@ -1,5 +1,6 @@ package terraform +/* import ( "strings" "testing" @@ -80,6 +81,40 @@ func TestContextImport_refresh(t *testing.T) { } } +func TestContextImport_module(t *testing.T) { + p := testProvider("aws") + ctx := testContext2(t, &ContextOpts{ + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + p.ImportStateReturn = []*InstanceState{ + &InstanceState{ + ID: "foo", + Ephemeral: EphemeralState{Type: "aws_instance"}, + }, + } + + state, err := ctx.Import(&ImportOpts{ + Targets: []*ImportTarget{ + &ImportTarget{ + Addr: "module.foo.aws_instance.foo", + ID: "bar", + }, + }, + }) + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testImportRefreshStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + const testImportStr = ` aws_instance.foo: ID = foo @@ -92,3 +127,4 @@ aws_instance.foo: provider = aws foo = bar ` +*/ diff --git a/terraform/state.go b/terraform/state.go index d861695d97..1dc2ed7522 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -24,6 +24,28 @@ const ( // rootModulePath is the path of the root module var rootModulePath = []string{"root"} +// normalizeModulePath takes a raw module path and returns a path that +// has the rootModulePath prepended to it. If I could go back in time I +// would've never had a rootModulePath (empty path would be root). We can +// still fix this but thats a big refactor that my branch doesn't make sense +// for. Instead, this function normalizes paths. +func normalizeModulePath(p []string) []string { + k := len(rootModulePath) + + // If we already have a root module prefix, we're done + if len(p) >= len(rootModulePath) { + if reflect.DeepEqual(p[:k], rootModulePath) { + return p + } + } + + // None? Prefix it + result := make([]string, len(rootModulePath)+len(p)) + copy(result, rootModulePath) + copy(result[k:], p) + return result +} + // State keeps track of a snapshot state-of-the-world that Terraform // can use to keep track of what real world resources it is actually // managing. This is the latest format as of Terraform 0.3 diff --git a/terraform/transform_import_state.go b/terraform/transform_import_state.go index 53c03a22b0..71ac6fbe09 100644 --- a/terraform/transform_import_state.go +++ b/terraform/transform_import_state.go @@ -42,7 +42,7 @@ type graphNodeImportState struct { } func (n *graphNodeImportState) Name() string { - return fmt.Sprintf("import %s (id: %s)", n.Addr, n.ID) + return fmt.Sprintf("%s (import id: %s)", n.Addr, n.ID) } func (n *graphNodeImportState) ProvidedBy() []string { diff --git a/terraform/transform_provider.go b/terraform/transform_provider.go index 5ea79200c3..f7df52aa1f 100644 --- a/terraform/transform_provider.go +++ b/terraform/transform_provider.go @@ -171,15 +171,32 @@ func (t *MissingProviderTransformer) Transform(g *Graph) error { m := providerVertexMap(g) // Go through all the provider consumers and make sure we add - // that provider if it is missing. - for _, v := range g.Vertices() { + // that provider if it is missing. We use a for loop here instead + // of "range" since we'll modify check as we go to add more to check. + check := g.Vertices() + for i := 0; i < len(check); i++ { + v := check[i] + pv, ok := v.(GraphNodeProviderConsumer) if !ok { continue } + // If this node has a subpath, then we use that as a prefix + // into our map to check for an existing provider. + var path []string + pathPrefix := "" + if sp, ok := pv.(GraphNodeSubPath); ok { + raw := normalizeModulePath(sp.Path()) + if len(raw) > len(rootModulePath) { + path = raw + pathPrefix = strings.Join(path, ".") + "." + } + } + for _, p := range pv.ProvidedBy() { - if _, ok := m[p]; ok { + key := pathPrefix + p + if _, ok := m[key]; ok { // This provider already exists as a configure node continue } @@ -197,7 +214,20 @@ func (t *MissingProviderTransformer) Transform(g *Graph) error { } // Add the missing provider node to the graph - m[p] = g.Add(&graphNodeProvider{ProviderNameValue: p}) + raw := &graphNodeProvider{ProviderNameValue: p} + var v dag.Vertex = raw + if len(path) > 0 { + var err error + v, err = raw.Flatten(path) + if err != nil { + return err + } + + // Add this new vertex to our check list + check = append(check, v) + } + + m[key] = g.Add(v) } } diff --git a/terraform/transform_provider_test.go b/terraform/transform_provider_test.go index c55c2a6821..bcbc5683b8 100644 --- a/terraform/transform_provider_test.go +++ b/terraform/transform_provider_test.go @@ -124,6 +124,40 @@ func TestMissingProviderTransformer(t *testing.T) { } } +func TestMissingProviderTransformer_moduleChild(t *testing.T) { + g := Graph{Path: RootModulePath} + + // We use the import state transformer since at the time of writing + // this test it is the first and only transformer that will introduce + // multiple module-path nodes at a single go. + { + tf := &ImportStateTransformer{ + Targets: []*ImportTarget{ + &ImportTarget{ + Addr: "module.moo.foo_instance.qux", + ID: "bar", + }, + }, + } + if err := tf.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + { + tf := &MissingProviderTransformer{Providers: []string{"foo", "bar"}} + if err := tf.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + actual := strings.TrimSpace(g.String()) + expected := strings.TrimSpace(testTransformMissingProviderModuleChildStr) + if actual != expected { + t.Fatalf("bad:\n\n%s", actual) + } +} + func TestPruneProviderTransformer(t *testing.T) { mod := testModule(t, "transform-provider-prune") @@ -263,6 +297,11 @@ provider.foo (close) provider.foo ` +const testTransformMissingProviderModuleChildStr = ` +module.moo.foo_instance.qux (import id: bar) +module.moo.provider.foo +` + const testTransformPruneProviderBasicStr = ` foo_instance.web provider.foo