From 42c6d04e07609fd6d029b8a1b153fafbf18eea6b Mon Sep 17 00:00:00 2001 From: James Bardin Date: Tue, 11 Jun 2024 15:27:08 -0400 Subject: [PATCH] Missing import target in module should error When grouping imports by module for validation, imports with no associated module were left out and would silently fail. Change the config transformer to check all import targets together to make sure they are all valid within a configuration. --- internal/terraform/context_import_test.go | 41 +++++++++++++++++ internal/terraform/transform_config.go | 54 +++++++++++++++++++---- 2 files changed, 87 insertions(+), 8 deletions(-) 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",