diff --git a/.changes/v1.15/BUG FIXES-20260105-170648.yaml b/.changes/v1.15/BUG FIXES-20260105-170648.yaml new file mode 100644 index 0000000000..d0764cfb66 --- /dev/null +++ b/.changes/v1.15/BUG FIXES-20260105-170648.yaml @@ -0,0 +1,5 @@ +kind: BUG FIXES +body: 'stacks: send progress events if the plan fails for better UI integration' +time: 2026-01-05T17:06:48.252069+01:00 +custom: + Issue: "38039" diff --git a/internal/rpcapi/stacks_test.go b/internal/rpcapi/stacks_test.go index cd094b98f8..f12e259cb3 100644 --- a/internal/rpcapi/stacks_test.go +++ b/internal/rpcapi/stacks_test.go @@ -810,6 +810,69 @@ func TestStackChangeProgress(t *testing.T) { }, }, }, + "invalid": { + source: "git::https://example.com/invalid.git", + store: stacks_testing_provider.NewResourceStoreBuilder(). + AddResource("resource", cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("resource"), + "value": cty.NullVal(cty.String), + })). + Build(), + state: []stackstate.AppliedChange{ + &stackstate.AppliedChangeComponentInstance{ + ComponentAddr: mustAbsComponent(t, "component.self"), + ComponentInstanceAddr: mustAbsComponentInstance(t, "component.self"), + }, + &stackstate.AppliedChangeResourceInstanceObject{ + ResourceInstanceObjectAddr: mustAbsResourceInstanceObject(t, "component.self.testing_resource.resource"), + NewStateSrc: &states.ResourceInstanceObjectSrc{ + AttrsJSON: mustMarshalJSONAttrs(map[string]interface{}{ + "id": "resource", + "value": nil, + }), + Status: states.ObjectReady, + }, + ProviderConfigAddr: mustDefaultRootProvider("testing"), + Schema: stacks_testing_provider.TestingResourceSchema, + }, + }, + want: []*stacks.StackChangeProgress{ + { + Event: &stacks.StackChangeProgress_ResourceInstanceStatus_{ + ResourceInstanceStatus: &stacks.StackChangeProgress_ResourceInstanceStatus{ + Addr: &stacks.ResourceInstanceObjectInStackAddr{ + ComponentInstanceAddr: "component.self", + ResourceInstanceAddr: "testing_resource.resource", + }, + Status: stacks.StackChangeProgress_ResourceInstanceStatus_ERRORED, + }, + }, + }, + { + Event: &stacks.StackChangeProgress_ComponentInstanceStatus_{ + ComponentInstanceStatus: &stacks.StackChangeProgress_ComponentInstanceStatus{ + Addr: &stacks.ComponentInstanceInStackAddr{ + ComponentAddr: "component.self", + ComponentInstanceAddr: "component.self", + }, + Status: stacks.StackChangeProgress_ComponentInstanceStatus_ERRORED, + }, + }, + }, + }, + diagnostics: []*terraform1.Diagnostic{ + { + Severity: terraform1.Diagnostic_ERROR, + Summary: "invalid configuration", + Detail: "configure_error attribute was set", + }, + { + Severity: terraform1.Diagnostic_ERROR, + Summary: "Provider configuration is invalid", + Detail: "Cannot decode the prior state for this resource instance because its provider configuration is invalid.", + }, + }, + }, } for name, tc := range tcs { diff --git a/internal/rpcapi/testdata/sourcebundle/invalid/invalid.tf b/internal/rpcapi/testdata/sourcebundle/invalid/invalid.tf new file mode 100644 index 0000000000..d787664fa8 --- /dev/null +++ b/internal/rpcapi/testdata/sourcebundle/invalid/invalid.tf @@ -0,0 +1,10 @@ +terraform { + required_providers { + testing = { + source = "hashicorp/testing" + version = "0.1.0" + } + } +} + +resource "testing_resource" "resource" {} diff --git a/internal/rpcapi/testdata/sourcebundle/invalid/invalid.tfcomponent.hcl b/internal/rpcapi/testdata/sourcebundle/invalid/invalid.tfcomponent.hcl new file mode 100644 index 0000000000..88aadd6ee6 --- /dev/null +++ b/internal/rpcapi/testdata/sourcebundle/invalid/invalid.tfcomponent.hcl @@ -0,0 +1,21 @@ +required_providers { + testing = { + source = "hashicorp/testing" + version = "0.1.0" + } +} + +provider "testing" "default" { + config { + // This provider is going to fail to configure. + configure_error = "invalid configuration" + } +} + +component "self" { + source = "./" + + providers = { + testing = provider.testing.default + } +} diff --git a/internal/rpcapi/testdata/sourcebundle/terraform-sources.json b/internal/rpcapi/testdata/sourcebundle/terraform-sources.json index aba8a33459..7d48c58bf4 100644 --- a/internal/rpcapi/testdata/sourcebundle/terraform-sources.json +++ b/internal/rpcapi/testdata/sourcebundle/terraform-sources.json @@ -30,6 +30,11 @@ "source": "git::https://example.com/removed.git", "local": "removed", "meta": {} + }, + { + "source": "git::https://example.com/invalid.git", + "local": "invalid", + "meta": {} } ] } diff --git a/internal/stacks/stackruntime/internal/stackeval/component_instance.go b/internal/stacks/stackruntime/internal/stackeval/component_instance.go index 2c330e9635..6fcf25cbe2 100644 --- a/internal/stacks/stackruntime/internal/stackeval/component_instance.go +++ b/internal/stacks/stackruntime/internal/stackeval/component_instance.go @@ -351,6 +351,8 @@ func (c *ComponentInstance) CheckModuleTreePlan(ctx context.Context) (*plans.Pla ReportComponentInstance(ctx, plan, h, seq, c) if plan.Complete { hookMore(ctx, seq, h.EndComponentInstancePlan, c.Addr()) + } else if plan.Errored { + hookMore(ctx, seq, h.ErrorComponentInstancePlan, c.Addr()) } else { hookMore(ctx, seq, h.DeferComponentInstancePlan, c.Addr()) }