diff --git a/internal/terraform/context_plan_actions_test.go b/internal/terraform/context_plan_actions_test.go index e3f0c50cb1c4..75691d234f42 100644 --- a/internal/terraform/context_plan_actions_test.go +++ b/internal/terraform/context_plan_actions_test.go @@ -4088,8 +4088,41 @@ resource "test_object" "a" { if !diags.HasErrors() { t.Fatal("expected errors, got success!") } - if diags.Err().Error() != "Configuration for triggered action does not exist: The configuration for the given action action.test_action.hello does not exist. All triggered actions must have an associated configuration." { - t.Fatal("wrong error!") + expectedErr := "action_trigger actions references non-existent action: The lifecycle action_trigger actions list contains a reference to the action \"action.test_action.hello\" that does not exist in the configuration of this module." + if diags.Err().Error() != expectedErr { + t.Fatalf("wrong error!, got %q, expected %q", diags.Err().Error(), expectedErr) + } +} + +func TestContextPlan_validateActionInTriggerExistsWithSimilarAction(t *testing.T) { + // this validation occurs during TransformConfig + module := ` +action "test_action" "hello_word" {} + +resource "test_object" "a" { + lifecycle { + action_trigger { + events = [after_create] + actions = [action.test_action.hello_world] + } + } +} +` + m := testModuleInline(t, map[string]string{"main.tf": module}) + p := simpleMockProvider() + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + + _, diags := ctx.Plan(m, nil, DefaultPlanOpts) + if !diags.HasErrors() { + t.Fatal("expected errors, got success!") + } + expectedErr := "action_trigger actions references non-existent action: The lifecycle action_trigger actions list contains a reference to the action \"action.test_action.hello_world\" that does not exist in the configuration of this module. Did you mean \"action.test_action.hello_word\"?" + if diags.Err().Error() != expectedErr { + t.Fatalf("wrong error!, got %q, expected %q", diags.Err().Error(), expectedErr) } } diff --git a/internal/terraform/transform_config.go b/internal/terraform/transform_config.go index 8198faad96c2..ddaecbe14d95 100644 --- a/internal/terraform/transform_config.go +++ b/internal/terraform/transform_config.go @@ -6,12 +6,15 @@ package terraform import ( "fmt" "log" + "maps" + "slices" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/dag" + "github.com/hashicorp/terraform/internal/didyoumean" "github.com/hashicorp/terraform/internal/lang/langrefs" "github.com/hashicorp/terraform/internal/tfdiags" ) @@ -164,7 +167,7 @@ func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config) er // Verify that any actions referenced in the resource's ActionTriggers exist in this module var diags tfdiags.Diagnostics if r.Managed != nil && r.Managed.ActionTriggers != nil { - for i, at := range r.Managed.ActionTriggers { + for _, at := range r.Managed.ActionTriggers { for _, action := range at.Actions { refs, parseRefDiags := langrefs.ReferencesInExpr(addrs.ParseRef, action.Expr) @@ -190,11 +193,17 @@ func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config) er _, ok := allConfigActions[configAction.String()] if !ok { + suggestion := didyoumean.NameSuggestion(configAction.String(), slices.Collect(maps.Keys(allConfigActions))) + if suggestion != "" { + suggestion = fmt.Sprintf(" Did you mean %q?", suggestion) + } + diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, - Summary: "Configuration for triggered action does not exist", - Detail: fmt.Sprintf("The configuration for the given action %s does not exist. All triggered actions must have an associated configuration.", configAction.String()), - Subject: &r.Managed.ActionTriggers[i].DeclRange, + Summary: "action_trigger actions references non-existent action", + Detail: fmt.Sprintf("The lifecycle action_trigger actions list contains a reference to the action %q that does not exist in the configuration of this module.%s", configAction.String(), suggestion), + Subject: action.Expr.Range().Ptr(), + Context: r.DeclRange.Ptr(), }) } }