diff --git a/command/views/json/change.go b/command/views/json/change.go index 12c8aed5c..4b5760716 100644 --- a/command/views/json/change.go +++ b/command/views/json/change.go @@ -10,6 +10,7 @@ func NewResourceInstanceChange(change *plans.ResourceInstanceChangeSrc) *Resourc c := &ResourceInstanceChange{ Resource: newResourceAddr(change.Addr), Action: changeAction(change.Action), + Reason: changeReason(change.ActionReason), } return c @@ -18,6 +19,7 @@ func NewResourceInstanceChange(change *plans.ResourceInstanceChangeSrc) *Resourc type ResourceInstanceChange struct { Resource ResourceAddr `json:"resource"` Action ChangeAction `json:"action"` + Reason ChangeReason `json:"reason,omitempty"` } func (c *ResourceInstanceChange) String() string { @@ -53,3 +55,31 @@ func changeAction(action plans.Action) ChangeAction { return ActionNoOp } } + +type ChangeReason string + +const ( + ReasonNone ChangeReason = "" + ReasonTainted ChangeReason = "tainted" + ReasonRequested ChangeReason = "requested" + ReasonCannotUpdate ChangeReason = "cannot_update" + ReasonUnknown ChangeReason = "unknown" +) + +func changeReason(reason plans.ResourceInstanceChangeActionReason) ChangeReason { + switch reason { + case plans.ResourceInstanceChangeNoReason: + return ReasonNone + case plans.ResourceInstanceReplaceBecauseTainted: + return ReasonTainted + case plans.ResourceInstanceReplaceByRequest: + return ReasonRequested + case plans.ResourceInstanceReplaceBecauseCannotUpdate: + return ReasonCannotUpdate + default: + // This should never happen, but there's no good way to guarantee + // exhaustive handling of the enum, so a generic fall back is better + // than a misleading result or a panic + return ReasonUnknown + } +} diff --git a/command/views/operation_test.go b/command/views/operation_test.go index 600abaa68..fbd56e0cb 100644 --- a/command/views/operation_test.go +++ b/command/views/operation_test.go @@ -433,9 +433,16 @@ func TestOperationJSON_plannedChange(t *testing.T) { 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), + ChangeSrc: plans.ChangeSrc{Action: plans.DeleteThenCreate}, + ActionReason: plans.ResourceInstanceReplaceByRequest, + }) + // Simple create v.PlannedChange(&plans.ResourceInstanceChangeSrc{ - Addr: boop.Instance(addrs.IntKey(0)).Absolute(root), + Addr: boop.Instance(addrs.IntKey(1)).Absolute(root), ChangeSrc: plans.ChangeSrc{Action: plans.Create}, }) @@ -445,15 +452,16 @@ func TestOperationJSON_plannedChange(t *testing.T) { ChangeSrc: plans.ChangeSrc{Action: plans.Delete}, }) - // Expect one message only, as the data source deletion should be a no-op + // 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 create", + "@message": "test_instance.boop[0]: Plan to replace", "@module": "terraform.ui", "type": "planned_change", "change": map[string]interface{}{ - "action": "create", + "action": "replace", + "reason": "requested", "resource": map[string]interface{}{ "addr": `test_instance.boop[0]`, "implied_provider": "test", @@ -465,6 +473,24 @@ func TestOperationJSON_plannedChange(t *testing.T) { }, }, }, + { + "@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)