json-output: Config-driven move support in JSON UI
Add previous address information to the `planned_change` and `resource_drift` messages for the streaming JSON UI output of plan and apply operations. Here we also add a "move" action value to the `change` object of these messages, to represent a move-only operation. As part of this work we also simplify this code to use the plan's DriftedResources values instead of recomputing the drift from state.
This commit is contained in:
parent
78c4a8c461
commit
b59b057591
|
@ -12,14 +12,22 @@ func NewResourceInstanceChange(change *plans.ResourceInstanceChangeSrc) *Resourc
|
|||
Action: changeAction(change.Action),
|
||||
Reason: changeReason(change.ActionReason),
|
||||
}
|
||||
if !change.Addr.Equal(change.PrevRunAddr) {
|
||||
if c.Action == ActionNoOp {
|
||||
c.Action = ActionMove
|
||||
}
|
||||
pr := newResourceAddr(change.PrevRunAddr)
|
||||
c.PreviousResource = &pr
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
type ResourceInstanceChange struct {
|
||||
Resource ResourceAddr `json:"resource"`
|
||||
Action ChangeAction `json:"action"`
|
||||
Reason ChangeReason `json:"reason,omitempty"`
|
||||
Resource ResourceAddr `json:"resource"`
|
||||
PreviousResource *ResourceAddr `json:"previous_resource,omitempty"`
|
||||
Action ChangeAction `json:"action"`
|
||||
Reason ChangeReason `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
func (c *ResourceInstanceChange) String() string {
|
||||
|
@ -30,6 +38,7 @@ type ChangeAction string
|
|||
|
||||
const (
|
||||
ActionNoOp ChangeAction = "noop"
|
||||
ActionMove ChangeAction = "move"
|
||||
ActionCreate ChangeAction = "create"
|
||||
ActionRead ChangeAction = "read"
|
||||
ActionUpdate ChangeAction = "update"
|
||||
|
|
|
@ -111,7 +111,8 @@ func TestJSONView_PlannedChange(t *testing.T) {
|
|||
}
|
||||
managed := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "bar"}
|
||||
cs := &plans.ResourceInstanceChangeSrc{
|
||||
Addr: managed.Instance(addrs.StringKey("boop")).Absolute(foo),
|
||||
Addr: managed.Instance(addrs.StringKey("boop")).Absolute(foo),
|
||||
PrevRunAddr: managed.Instance(addrs.StringKey("boop")).Absolute(foo),
|
||||
ChangeSrc: plans.ChangeSrc{
|
||||
Action: plans.Create,
|
||||
},
|
||||
|
@ -151,7 +152,8 @@ func TestJSONView_ResourceDrift(t *testing.T) {
|
|||
}
|
||||
managed := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "bar"}
|
||||
cs := &plans.ResourceInstanceChangeSrc{
|
||||
Addr: managed.Instance(addrs.StringKey("boop")).Absolute(foo),
|
||||
Addr: managed.Instance(addrs.StringKey("boop")).Absolute(foo),
|
||||
PrevRunAddr: managed.Instance(addrs.StringKey("boop")).Absolute(foo),
|
||||
ChangeSrc: plans.ChangeSrc{
|
||||
Action: plans.Update,
|
||||
},
|
||||
|
|
|
@ -3,7 +3,6 @@ package views
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
|
@ -11,11 +10,9 @@ import (
|
|||
"github.com/hashicorp/terraform/internal/command/format"
|
||||
"github.com/hashicorp/terraform/internal/command/views/json"
|
||||
"github.com/hashicorp/terraform/internal/plans"
|
||||
"github.com/hashicorp/terraform/internal/states"
|
||||
"github.com/hashicorp/terraform/internal/states/statefile"
|
||||
"github.com/hashicorp/terraform/internal/terraform"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
type Operation interface {
|
||||
|
@ -163,10 +160,14 @@ func (v *OperationJSON) EmergencyDumpState(stateFile *statefile.File) error {
|
|||
// Log a change summary and a series of "planned" messages for the changes in
|
||||
// the plan.
|
||||
func (v *OperationJSON) Plan(plan *plans.Plan, schemas *terraform.Schemas) {
|
||||
if err := v.resourceDrift(plan.PrevRunState, plan.PriorState, schemas); err != nil {
|
||||
var diags tfdiags.Diagnostics
|
||||
diags = diags.Append(err)
|
||||
v.Diagnostics(diags)
|
||||
for _, dr := range plan.DriftedResources {
|
||||
// In refresh-only mode, we output all resources marked as drifted,
|
||||
// including those which have moved without other changes. In other plan
|
||||
// modes, move-only changes will be included in the planned changes, so
|
||||
// we skip them here.
|
||||
if dr.Action != plans.NoOp || plan.UIMode == plans.RefreshOnlyMode {
|
||||
v.view.ResourceDrift(json.NewResourceInstanceChange(dr))
|
||||
}
|
||||
}
|
||||
|
||||
cs := &json.ChangeSummary{
|
||||
|
@ -189,7 +190,7 @@ func (v *OperationJSON) Plan(plan *plans.Plan, schemas *terraform.Schemas) {
|
|||
cs.Remove++
|
||||
}
|
||||
|
||||
if change.Action != plans.NoOp {
|
||||
if change.Action != plans.NoOp || !change.Addr.Equal(change.PrevRunAddr) {
|
||||
v.view.PlannedChange(json.NewResourceInstanceChange(change))
|
||||
}
|
||||
}
|
||||
|
@ -208,92 +209,6 @@ func (v *OperationJSON) Plan(plan *plans.Plan, schemas *terraform.Schemas) {
|
|||
}
|
||||
}
|
||||
|
||||
func (v *OperationJSON) resourceDrift(oldState, newState *states.State, schemas *terraform.Schemas) error {
|
||||
if newState.ManagedResourcesEqual(oldState) {
|
||||
// Nothing to do, because we only detect and report drift for managed
|
||||
// resource instances.
|
||||
return nil
|
||||
}
|
||||
var changes []*json.ResourceInstanceChange
|
||||
for _, ms := range oldState.Modules {
|
||||
for _, rs := range ms.Resources {
|
||||
if rs.Addr.Resource.Mode != addrs.ManagedResourceMode {
|
||||
// Drift reporting is only for managed resources
|
||||
continue
|
||||
}
|
||||
|
||||
provider := rs.ProviderConfig.Provider
|
||||
for key, oldIS := range rs.Instances {
|
||||
if oldIS.Current == nil {
|
||||
// Not interested in instances that only have deposed objects
|
||||
continue
|
||||
}
|
||||
addr := rs.Addr.Instance(key)
|
||||
newIS := newState.ResourceInstance(addr)
|
||||
|
||||
schema, _ := schemas.ResourceTypeConfig(
|
||||
provider,
|
||||
addr.Resource.Resource.Mode,
|
||||
addr.Resource.Resource.Type,
|
||||
)
|
||||
if schema == nil {
|
||||
return fmt.Errorf("no schema found for %s (in provider %s)", addr, provider)
|
||||
}
|
||||
ty := schema.ImpliedType()
|
||||
|
||||
oldObj, err := oldIS.Current.Decode(ty)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode previous run data for %s: %s", addr, err)
|
||||
}
|
||||
|
||||
var newObj *states.ResourceInstanceObject
|
||||
if newIS != nil && newIS.Current != nil {
|
||||
newObj, err = newIS.Current.Decode(ty)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode refreshed data for %s: %s", addr, err)
|
||||
}
|
||||
}
|
||||
|
||||
var oldVal, newVal cty.Value
|
||||
oldVal = oldObj.Value
|
||||
if newObj != nil {
|
||||
newVal = newObj.Value
|
||||
} else {
|
||||
newVal = cty.NullVal(ty)
|
||||
}
|
||||
|
||||
if oldVal.RawEquals(newVal) {
|
||||
// No drift if the two values are semantically equivalent
|
||||
continue
|
||||
}
|
||||
|
||||
// We can only detect updates and deletes as drift.
|
||||
action := plans.Update
|
||||
if newVal.IsNull() {
|
||||
action = plans.Delete
|
||||
}
|
||||
|
||||
change := &plans.ResourceInstanceChangeSrc{
|
||||
Addr: addr,
|
||||
ChangeSrc: plans.ChangeSrc{
|
||||
Action: action,
|
||||
},
|
||||
}
|
||||
changes = append(changes, json.NewResourceInstanceChange(change))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the change structs lexically by address to give stable output
|
||||
sort.Slice(changes, func(i, j int) bool { return changes[i].Resource.Addr < changes[j].Resource.Addr })
|
||||
|
||||
for _, change := range changes {
|
||||
v.view.ResourceDrift(change)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -479,29 +479,35 @@ func TestOperationJSON_plan(t *testing.T) {
|
|||
Changes: &plans.Changes{
|
||||
Resources: []*plans.ResourceInstanceChangeSrc{
|
||||
{
|
||||
Addr: boop.Instance(addrs.IntKey(0)).Absolute(root),
|
||||
ChangeSrc: plans.ChangeSrc{Action: plans.CreateThenDelete},
|
||||
Addr: boop.Instance(addrs.IntKey(0)).Absolute(root),
|
||||
PrevRunAddr: boop.Instance(addrs.IntKey(0)).Absolute(root),
|
||||
ChangeSrc: plans.ChangeSrc{Action: plans.CreateThenDelete},
|
||||
},
|
||||
{
|
||||
Addr: boop.Instance(addrs.IntKey(1)).Absolute(root),
|
||||
ChangeSrc: plans.ChangeSrc{Action: plans.Create},
|
||||
Addr: boop.Instance(addrs.IntKey(1)).Absolute(root),
|
||||
PrevRunAddr: boop.Instance(addrs.IntKey(1)).Absolute(root),
|
||||
ChangeSrc: plans.ChangeSrc{Action: plans.Create},
|
||||
},
|
||||
{
|
||||
Addr: boop.Instance(addrs.IntKey(0)).Absolute(vpc),
|
||||
ChangeSrc: plans.ChangeSrc{Action: plans.Delete},
|
||||
Addr: boop.Instance(addrs.IntKey(0)).Absolute(vpc),
|
||||
PrevRunAddr: boop.Instance(addrs.IntKey(0)).Absolute(vpc),
|
||||
ChangeSrc: plans.ChangeSrc{Action: plans.Delete},
|
||||
},
|
||||
{
|
||||
Addr: beep.Instance(addrs.NoKey).Absolute(root),
|
||||
ChangeSrc: plans.ChangeSrc{Action: plans.DeleteThenCreate},
|
||||
Addr: beep.Instance(addrs.NoKey).Absolute(root),
|
||||
PrevRunAddr: beep.Instance(addrs.NoKey).Absolute(root),
|
||||
ChangeSrc: plans.ChangeSrc{Action: plans.DeleteThenCreate},
|
||||
},
|
||||
{
|
||||
Addr: beep.Instance(addrs.NoKey).Absolute(vpc),
|
||||
ChangeSrc: plans.ChangeSrc{Action: plans.Update},
|
||||
Addr: beep.Instance(addrs.NoKey).Absolute(vpc),
|
||||
PrevRunAddr: beep.Instance(addrs.NoKey).Absolute(vpc),
|
||||
ChangeSrc: plans.ChangeSrc{Action: plans.Update},
|
||||
},
|
||||
// Data source deletion should not show up in the logs
|
||||
{
|
||||
Addr: derp.Instance(addrs.NoKey).Absolute(root),
|
||||
ChangeSrc: plans.ChangeSrc{Action: plans.Delete},
|
||||
Addr: derp.Instance(addrs.NoKey).Absolute(root),
|
||||
PrevRunAddr: derp.Instance(addrs.NoKey).Absolute(root),
|
||||
ChangeSrc: plans.ChangeSrc{Action: plans.Delete},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -623,74 +629,175 @@ func TestOperationJSON_plan(t *testing.T) {
|
|||
testJSONViewOutputEquals(t, done(t).Stdout(), want)
|
||||
}
|
||||
|
||||
func TestOperationJSON_planDrift(t *testing.T) {
|
||||
func TestOperationJSON_planDriftWithMove(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_resource", Name: "boop"}
|
||||
beep := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_resource", Name: "beep"}
|
||||
derp := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_resource", Name: "derp"}
|
||||
blep := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_resource", Name: "blep"}
|
||||
honk := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_resource", Name: "honk"}
|
||||
|
||||
plan := &plans.Plan{
|
||||
UIMode: plans.NormalMode,
|
||||
Changes: &plans.Changes{
|
||||
Resources: []*plans.ResourceInstanceChangeSrc{
|
||||
{
|
||||
Addr: honk.Instance(addrs.StringKey("bonk")).Absolute(root),
|
||||
PrevRunAddr: honk.Instance(addrs.IntKey(0)).Absolute(root),
|
||||
ChangeSrc: plans.ChangeSrc{Action: plans.NoOp},
|
||||
},
|
||||
},
|
||||
},
|
||||
DriftedResources: []*plans.ResourceInstanceChangeSrc{
|
||||
{
|
||||
Addr: beep.Instance(addrs.NoKey).Absolute(root),
|
||||
PrevRunAddr: beep.Instance(addrs.NoKey).Absolute(root),
|
||||
ChangeSrc: plans.ChangeSrc{Action: plans.Delete},
|
||||
},
|
||||
{
|
||||
Addr: boop.Instance(addrs.NoKey).Absolute(root),
|
||||
PrevRunAddr: blep.Instance(addrs.NoKey).Absolute(root),
|
||||
ChangeSrc: plans.ChangeSrc{Action: plans.Update},
|
||||
},
|
||||
// Move-only resource drift should not be present in normal mode plans
|
||||
{
|
||||
Addr: honk.Instance(addrs.StringKey("bonk")).Absolute(root),
|
||||
PrevRunAddr: honk.Instance(addrs.IntKey(0)).Absolute(root),
|
||||
ChangeSrc: plans.ChangeSrc{Action: plans.NoOp},
|
||||
},
|
||||
},
|
||||
}
|
||||
v.Plan(plan, testSchemas())
|
||||
|
||||
want := []map[string]interface{}{
|
||||
// Drift detected: delete
|
||||
{
|
||||
"@level": "info",
|
||||
"@message": "test_resource.beep: Drift detected (delete)",
|
||||
"@module": "terraform.ui",
|
||||
"type": "resource_drift",
|
||||
"change": map[string]interface{}{
|
||||
"action": "delete",
|
||||
"resource": map[string]interface{}{
|
||||
"addr": "test_resource.beep",
|
||||
"implied_provider": "test",
|
||||
"module": "",
|
||||
"resource": "test_resource.beep",
|
||||
"resource_key": nil,
|
||||
"resource_name": "beep",
|
||||
"resource_type": "test_resource",
|
||||
},
|
||||
},
|
||||
},
|
||||
// Drift detected: update with move
|
||||
{
|
||||
"@level": "info",
|
||||
"@message": "test_resource.boop: Drift detected (update)",
|
||||
"@module": "terraform.ui",
|
||||
"type": "resource_drift",
|
||||
"change": map[string]interface{}{
|
||||
"action": "update",
|
||||
"resource": map[string]interface{}{
|
||||
"addr": "test_resource.boop",
|
||||
"implied_provider": "test",
|
||||
"module": "",
|
||||
"resource": "test_resource.boop",
|
||||
"resource_key": nil,
|
||||
"resource_name": "boop",
|
||||
"resource_type": "test_resource",
|
||||
},
|
||||
"previous_resource": map[string]interface{}{
|
||||
"addr": "test_resource.blep",
|
||||
"implied_provider": "test",
|
||||
"module": "",
|
||||
"resource": "test_resource.blep",
|
||||
"resource_key": nil,
|
||||
"resource_name": "blep",
|
||||
"resource_type": "test_resource",
|
||||
},
|
||||
},
|
||||
},
|
||||
// Move-only change
|
||||
{
|
||||
"@level": "info",
|
||||
"@message": `test_resource.honk["bonk"]: Plan to move`,
|
||||
"@module": "terraform.ui",
|
||||
"type": "planned_change",
|
||||
"change": map[string]interface{}{
|
||||
"action": "move",
|
||||
"resource": map[string]interface{}{
|
||||
"addr": `test_resource.honk["bonk"]`,
|
||||
"implied_provider": "test",
|
||||
"module": "",
|
||||
"resource": `test_resource.honk["bonk"]`,
|
||||
"resource_key": "bonk",
|
||||
"resource_name": "honk",
|
||||
"resource_type": "test_resource",
|
||||
},
|
||||
"previous_resource": map[string]interface{}{
|
||||
"addr": `test_resource.honk[0]`,
|
||||
"implied_provider": "test",
|
||||
"module": "",
|
||||
"resource": `test_resource.honk[0]`,
|
||||
"resource_key": float64(0),
|
||||
"resource_name": "honk",
|
||||
"resource_type": "test_resource",
|
||||
},
|
||||
},
|
||||
},
|
||||
// No changes
|
||||
{
|
||||
"@level": "info",
|
||||
"@message": "Plan: 0 to add, 0 to change, 0 to destroy.",
|
||||
"@module": "terraform.ui",
|
||||
"type": "change_summary",
|
||||
"changes": map[string]interface{}{
|
||||
"operation": "plan",
|
||||
"add": float64(0),
|
||||
"change": float64(0),
|
||||
"remove": float64(0),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testJSONViewOutputEquals(t, done(t).Stdout(), want)
|
||||
}
|
||||
|
||||
func TestOperationJSON_planDriftWithMoveRefreshOnly(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_resource", Name: "boop"}
|
||||
beep := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_resource", Name: "beep"}
|
||||
blep := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_resource", Name: "blep"}
|
||||
honk := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_resource", Name: "honk"}
|
||||
|
||||
plan := &plans.Plan{
|
||||
UIMode: plans.RefreshOnlyMode,
|
||||
Changes: &plans.Changes{
|
||||
Resources: []*plans.ResourceInstanceChangeSrc{},
|
||||
},
|
||||
PrevRunState: states.BuildState(func(state *states.SyncState) {
|
||||
// Update
|
||||
state.SetResourceInstanceCurrent(
|
||||
boop.Instance(addrs.NoKey).Absolute(root),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"foo":"bar"}`),
|
||||
},
|
||||
root.ProviderConfigDefault(addrs.NewDefaultProvider("test")),
|
||||
)
|
||||
// Delete
|
||||
state.SetResourceInstanceCurrent(
|
||||
beep.Instance(addrs.NoKey).Absolute(root),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"foo":"boop"}`),
|
||||
},
|
||||
root.ProviderConfigDefault(addrs.NewDefaultProvider("test")),
|
||||
)
|
||||
// No-op
|
||||
state.SetResourceInstanceCurrent(
|
||||
derp.Instance(addrs.NoKey).Absolute(root),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"foo":"boop"}`),
|
||||
},
|
||||
root.ProviderConfigDefault(addrs.NewDefaultProvider("test")),
|
||||
)
|
||||
}),
|
||||
PriorState: states.BuildState(func(state *states.SyncState) {
|
||||
// Update
|
||||
state.SetResourceInstanceCurrent(
|
||||
boop.Instance(addrs.NoKey).Absolute(root),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"foo":"baz"}`),
|
||||
},
|
||||
root.ProviderConfigDefault(addrs.NewDefaultProvider("test")),
|
||||
)
|
||||
// Delete
|
||||
state.SetResourceInstanceCurrent(
|
||||
beep.Instance(addrs.NoKey).Absolute(root),
|
||||
nil,
|
||||
root.ProviderConfigDefault(addrs.NewDefaultProvider("test")),
|
||||
)
|
||||
// No-op
|
||||
state.SetResourceInstanceCurrent(
|
||||
derp.Instance(addrs.NoKey).Absolute(root),
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"foo":"boop"}`),
|
||||
},
|
||||
root.ProviderConfigDefault(addrs.NewDefaultProvider("test")),
|
||||
)
|
||||
}),
|
||||
DriftedResources: []*plans.ResourceInstanceChangeSrc{
|
||||
{
|
||||
Addr: beep.Instance(addrs.NoKey).Absolute(root),
|
||||
PrevRunAddr: beep.Instance(addrs.NoKey).Absolute(root),
|
||||
ChangeSrc: plans.ChangeSrc{Action: plans.Delete},
|
||||
},
|
||||
{
|
||||
Addr: boop.Instance(addrs.NoKey).Absolute(root),
|
||||
PrevRunAddr: blep.Instance(addrs.NoKey).Absolute(root),
|
||||
ChangeSrc: plans.ChangeSrc{Action: plans.Update},
|
||||
},
|
||||
// Move-only resource drift should be present in refresh-only plans
|
||||
{
|
||||
Addr: honk.Instance(addrs.StringKey("bonk")).Absolute(root),
|
||||
PrevRunAddr: honk.Instance(addrs.IntKey(0)).Absolute(root),
|
||||
ChangeSrc: plans.ChangeSrc{Action: plans.NoOp},
|
||||
},
|
||||
},
|
||||
}
|
||||
v.Plan(plan, testSchemas())
|
||||
|
||||
|
@ -731,6 +838,43 @@ func TestOperationJSON_planDrift(t *testing.T) {
|
|||
"resource_name": "boop",
|
||||
"resource_type": "test_resource",
|
||||
},
|
||||
"previous_resource": map[string]interface{}{
|
||||
"addr": "test_resource.blep",
|
||||
"implied_provider": "test",
|
||||
"module": "",
|
||||
"resource": "test_resource.blep",
|
||||
"resource_key": nil,
|
||||
"resource_name": "blep",
|
||||
"resource_type": "test_resource",
|
||||
},
|
||||
},
|
||||
},
|
||||
// Drift detected: Move-only change
|
||||
{
|
||||
"@level": "info",
|
||||
"@message": `test_resource.honk["bonk"]: Drift detected (move)`,
|
||||
"@module": "terraform.ui",
|
||||
"type": "resource_drift",
|
||||
"change": map[string]interface{}{
|
||||
"action": "move",
|
||||
"resource": map[string]interface{}{
|
||||
"addr": `test_resource.honk["bonk"]`,
|
||||
"implied_provider": "test",
|
||||
"module": "",
|
||||
"resource": `test_resource.honk["bonk"]`,
|
||||
"resource_key": "bonk",
|
||||
"resource_name": "honk",
|
||||
"resource_type": "test_resource",
|
||||
},
|
||||
"previous_resource": map[string]interface{}{
|
||||
"addr": `test_resource.honk[0]`,
|
||||
"implied_provider": "test",
|
||||
"module": "",
|
||||
"resource": `test_resource.honk[0]`,
|
||||
"resource_key": float64(0),
|
||||
"resource_name": "honk",
|
||||
"resource_type": "test_resource",
|
||||
},
|
||||
},
|
||||
},
|
||||
// No changes
|
||||
|
@ -846,20 +990,23 @@ func TestOperationJSON_plannedChange(t *testing.T) {
|
|||
// 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),
|
||||
ChangeSrc: plans.ChangeSrc{Action: plans.Create},
|
||||
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),
|
||||
ChangeSrc: plans.ChangeSrc{Action: plans.Delete},
|
||||
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
|
||||
|
|
|
@ -124,7 +124,8 @@ This message does not include details about the exact changes which caused the c
|
|||
At the end of a plan or before an apply, Terraform will emit a `planned_change` message for each resource which has changes to apply. This message has an embedded `change` object with the following keys:
|
||||
|
||||
- `resource`: object describing the address of the resource to be changed; see [resource object](#resource-object) below for details
|
||||
- `action`: the action planned to be taken for the resource. Values: `noop`, `create`, `read`, `update`, `replace`, `delete`.
|
||||
- `previous_resource`: object describing the previous address of the resource, if this change includes a configuration-driven move
|
||||
- `action`: the action planned to be taken for the resource. Values: `noop`, `create`, `read`, `update`, `replace`, `delete`, `move`.
|
||||
- `reason`: an optional reason for the change, currently only used when the action is `replace`. Values:
|
||||
- `tainted`: resource was marked as tainted
|
||||
- `requested`: user requested that the resource be replaced, for example via the `-replace` plan flag
|
||||
|
|
Loading…
Reference in New Issue