diff --git a/internal/backend/local/backend_apply.go b/internal/backend/local/backend_apply.go index 4b9765d1b348..7950f782f4f0 100644 --- a/internal/backend/local/backend_apply.go +++ b/internal/backend/local/backend_apply.go @@ -224,10 +224,10 @@ func (b *Local) opApply( op.ReportResult(runningOp, diags) return } - for _, change := range plan.Changes.Resources { - if change.Action != plans.NoOp { - op.View.PlannedChange(change) - } + if _, ok := op.View.(*views.OperationJSON); ok { + // We don't display the plan for OperationHuman because the output of + // Plan already includes the change details for all resource instances. + op.View.Plan(plan, schemas) } } diff --git a/internal/command/views/operation.go b/internal/command/views/operation.go index 6f1e04b6c066..d00ad4380341 100644 --- a/internal/command/views/operation.go +++ b/internal/command/views/operation.go @@ -29,7 +29,6 @@ type Operation interface { EmergencyDumpState(stateFile *statefile.File) error - PlannedChange(change *plans.ResourceInstanceChangeSrc) Plan(plan *plans.Plan, schemas *terraform.Schemas) PlanNextStep(planPath string, genConfigPath string) @@ -130,13 +129,6 @@ func (v *OperationHuman) Plan(plan *plans.Plan, schemas *terraform.Schemas) { renderer.RenderHumanPlan(jplan, plan.UIMode, opts...) } -func (v *OperationHuman) PlannedChange(change *plans.ResourceInstanceChangeSrc) { - // PlannedChange is primarily for machine-readable output in order to - // get a per-resource-instance change description. We don't use it - // with OperationHuman because the output of Plan already includes the - // change details for all resource instances. -} - // PlanNextStep gives the user some next-steps, unless we're running in an // automation tool which is presumed to provide its own UI for further actions. func (v *OperationHuman) PlanNextStep(planPath string, genConfigPath string) { @@ -268,14 +260,6 @@ func (v *OperationJSON) Plan(plan *plans.Plan, schemas *terraform.Schemas) { } } -func (v *OperationJSON) PlannedChange(change *plans.ResourceInstanceChangeSrc) { - if change.Action == plans.Delete && change.Addr.Resource.Resource.Mode == addrs.DataResourceMode { - // Avoid rendering data sources on deletion - return - } - v.view.PlannedChange(json.NewResourceInstanceChange(change)) -} - // PlanNextStep does nothing for the JSON view as it is a hook for user-facing // output only applicable to human-readable UI. func (v *OperationJSON) PlanNextStep(planPath string, genConfigPath string) { diff --git a/internal/command/views/operation_test.go b/internal/command/views/operation_test.go index e63a2fd04fc1..a66dd186c05c 100644 --- a/internal/command/views/operation_test.go +++ b/internal/command/views/operation_test.go @@ -1253,77 +1253,3 @@ func TestOperationJSON_planOutputChanges(t *testing.T) { testJSONViewOutputEquals(t, done(t).Stdout(), want) } - -func TestOperationJSON_plannedChange(t *testing.T) { - streams, done := terminal.StreamsForTesting(t) - v := &OperationJSON{view: NewJSONView(NewView(streams))} - - root := addrs.RootModuleInstance - boop := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "boop"} - derp := addrs.Resource{Mode: addrs.DataResourceMode, Type: "test_source", Name: "derp"} - - // Replace requested by user - v.PlannedChange(&plans.ResourceInstanceChangeSrc{ - Addr: boop.Instance(addrs.IntKey(0)).Absolute(root), - PrevRunAddr: boop.Instance(addrs.IntKey(0)).Absolute(root), - ChangeSrc: plans.ChangeSrc{Action: plans.DeleteThenCreate}, - ActionReason: plans.ResourceInstanceReplaceByRequest, - }) - - // Simple create - v.PlannedChange(&plans.ResourceInstanceChangeSrc{ - Addr: boop.Instance(addrs.IntKey(1)).Absolute(root), - PrevRunAddr: boop.Instance(addrs.IntKey(1)).Absolute(root), - ChangeSrc: plans.ChangeSrc{Action: plans.Create}, - }) - - // Data source deletion - v.PlannedChange(&plans.ResourceInstanceChangeSrc{ - Addr: derp.Instance(addrs.NoKey).Absolute(root), - PrevRunAddr: derp.Instance(addrs.NoKey).Absolute(root), - ChangeSrc: plans.ChangeSrc{Action: plans.Delete}, - }) - - // Expect only two messages, as the data source deletion should be a no-op - want := []map[string]interface{}{ - { - "@level": "info", - "@message": "test_instance.boop[0]: Plan to replace", - "@module": "terraform.ui", - "type": "planned_change", - "change": map[string]interface{}{ - "action": "replace", - "reason": "requested", - "resource": map[string]interface{}{ - "addr": `test_instance.boop[0]`, - "implied_provider": "test", - "module": "", - "resource": `test_instance.boop[0]`, - "resource_key": float64(0), - "resource_name": "boop", - "resource_type": "test_instance", - }, - }, - }, - { - "@level": "info", - "@message": "test_instance.boop[1]: Plan to create", - "@module": "terraform.ui", - "type": "planned_change", - "change": map[string]interface{}{ - "action": "create", - "resource": map[string]interface{}{ - "addr": `test_instance.boop[1]`, - "implied_provider": "test", - "module": "", - "resource": `test_instance.boop[1]`, - "resource_key": float64(1), - "resource_name": "boop", - "resource_type": "test_instance", - }, - }, - }, - } - - testJSONViewOutputEquals(t, done(t).Stdout(), want) -}