diff --git a/internal/terraform/context_import_test.go b/internal/terraform/context_import_test.go index 82d48d00df..51da8ef9d1 100644 --- a/internal/terraform/context_import_test.go +++ b/internal/terraform/context_import_test.go @@ -1031,6 +1031,47 @@ func TestContextImport_33572(t *testing.T) { } } +// Missing import target should produce an error +func TestContextImport_missingModuleImport(t *testing.T) { + p := testProvider("test") + m := testModuleInline(t, map[string]string{ + "main.tf": ` +locals { + xs = toset(["foo"]) +} + +module "a" { + for_each = local.xs + source = "./a" +} + +import { + to = module.WRONG.test_resource.x + id = "id" +} +`, + "a/main.tf": ` +resource "test_resource" "x" { + value = "z" +} +`, + }) + + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + + _, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts) + if !diags.HasErrors() { + t.Fatal("expected missing import target error") + } + if !strings.Contains(diags.Err().Error(), "Configuration for import target does not exist") { + t.Fatalf("incorrect error for missing import target: %s\n", diags.Err()) + } +} + const testImportStr = ` aws_instance.foo: ID = foo diff --git a/internal/terraform/transform_config.go b/internal/terraform/transform_config.go index ead1adb53d..b09f63cb4f 100644 --- a/internal/terraform/transform_config.go +++ b/internal/terraform/transform_config.go @@ -62,6 +62,10 @@ func (t *ConfigTransformer) Transform(g *Graph) error { return nil } + if err := t.validateImportTargets(); err != nil { + return err + } + // Start the transformation process return t.transform(g, t.Config) } @@ -173,13 +177,6 @@ func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config) er // If any import targets were not claimed by resources and we are // generating configuration, then let's add them into the graph now. - - // TODO: use diagnostics to collect detailed errors for now, even though we - // can only return an error from here. This gives the user more immediate - // feedback, rather than waiting an unknown amount of time for the plan to - // fail. - var diags tfdiags.Diagnostics - for _, i := range importTargets { if path.IsRoot() { // If we have a single instance import target in the root module, we @@ -212,8 +209,49 @@ func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config) er g.Add(node) continue } + } + } + return nil +} + +// validateImportTargets ensures that the import target resources exist in the +// configuration. We do this here rather than statically during config loading +// to have any CLI imports included, and to provide feedback about possible +// config generation. +func (t *ConfigTransformer) validateImportTargets() error { + var diags tfdiags.Diagnostics + + for _, i := range t.importTargets { + var toResource addrs.ConfigResource + switch { + case i.Config != nil: + toResource = i.Config.ToResource + default: + toResource = i.LegacyAddr.ConfigResource() + } + + moduleCfg := t.Config.Root.Descendent(toResource.Module) + if moduleCfg != nil { + res := moduleCfg.Module.ResourceByAddr(toResource.Resource) + if res != nil { + // the target config exists, which is all we're looking for at this point. + continue + } + } + + if toResource.Module.IsRoot() { + var toDiags tfdiags.Diagnostics + traversal, hd := hcl.AbsTraversalForExpr(i.Config.To) + toDiags = toDiags.Append(hd) + to, td := addrs.ParseAbsResourceInstance(traversal) + toDiags = toDiags.Append(td) + canGenerate := !toDiags.HasErrors() && to.Resource.Key == addrs.NoKey + + if t.generateConfigPathForImportTargets != "" { + if canGenerate { + continue + } - if t.generateConfigPathForImportTargets != "" && !canGenerate { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Cannot generate configuration",