json-output: Previous address for resource changes
Configuration-driven moves are represented in the plan file by setting the resource's `PrevRunAddr` to a different value than its `Addr`. For JSON plan output, we here add a new field to resource changes, `previous_address`, which is present and non-empty only if the resource is planned to be moved. Like the CLI UI, refresh-only plans will include move-only changes in the resource drift JSON output. In normal plan mode, these are elided to avoid redundancy with planned changes.
This commit is contained in:
parent
78705f4f10
commit
78c4a8c461
|
@ -130,9 +130,25 @@ func Marshal(
|
||||||
}
|
}
|
||||||
|
|
||||||
// output.ResourceDrift
|
// output.ResourceDrift
|
||||||
output.ResourceDrift, err = output.marshalResourceChanges(p.DriftedResources, schemas)
|
if len(p.DriftedResources) > 0 {
|
||||||
if err != nil {
|
// In refresh-only mode, we render all resources marked as drifted,
|
||||||
return nil, fmt.Errorf("error in marshaling resource drift: %s", err)
|
// 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.
|
||||||
|
var driftedResources []*plans.ResourceInstanceChangeSrc
|
||||||
|
if p.UIMode == plans.RefreshOnlyMode {
|
||||||
|
driftedResources = p.DriftedResources
|
||||||
|
} else {
|
||||||
|
for _, dr := range p.DriftedResources {
|
||||||
|
if dr.Action != plans.NoOp {
|
||||||
|
driftedResources = append(driftedResources, dr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output.ResourceDrift, err = output.marshalResourceChanges(driftedResources, schemas)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error in marshaling resource drift: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// output.ResourceChanges
|
// output.ResourceChanges
|
||||||
|
@ -197,6 +213,9 @@ func (p *plan) marshalResourceChanges(resources []*plans.ResourceInstanceChangeS
|
||||||
var r resourceChange
|
var r resourceChange
|
||||||
addr := rc.Addr
|
addr := rc.Addr
|
||||||
r.Address = addr.String()
|
r.Address = addr.String()
|
||||||
|
if !addr.Equal(rc.PrevRunAddr) {
|
||||||
|
r.PreviousAddress = rc.PrevRunAddr.String()
|
||||||
|
}
|
||||||
|
|
||||||
dataSource := addr.Resource.Resource.Mode == addrs.DataResourceMode
|
dataSource := addr.Resource.Resource.Mode == addrs.DataResourceMode
|
||||||
// We create "delete" actions for data resources so we can clean up
|
// We create "delete" actions for data resources so we can clean up
|
||||||
|
|
|
@ -48,6 +48,18 @@ type resourceChange struct {
|
||||||
// Address is the absolute resource address
|
// Address is the absolute resource address
|
||||||
Address string `json:"address,omitempty"`
|
Address string `json:"address,omitempty"`
|
||||||
|
|
||||||
|
// PreviousAddress is the absolute address that this resource instance had
|
||||||
|
// at the conclusion of a previous run.
|
||||||
|
//
|
||||||
|
// This will typically be omitted, but will be present if the previous
|
||||||
|
// resource instance was subject to a "moved" block that we handled in the
|
||||||
|
// process of creating this plan.
|
||||||
|
//
|
||||||
|
// Note that this behavior diverges from the internal plan data structure,
|
||||||
|
// where the previous address is set equal to the current address in the
|
||||||
|
// common case, rather than being omitted.
|
||||||
|
PreviousAddress string `json:"previous_address,omitempty"`
|
||||||
|
|
||||||
// ModuleAddress is the module portion of the above address. Omitted if the
|
// ModuleAddress is the module portion of the above address. Omitted if the
|
||||||
// instance is in the root module.
|
// instance is in the root module.
|
||||||
ModuleAddress string `json:"module_address,omitempty"`
|
ModuleAddress string `json:"module_address,omitempty"`
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# In state with `ami = "foo"`, so this should be a regular update. The provider
|
||||||
|
# should not detect changes on refresh.
|
||||||
|
resource "test_instance" "no_refresh" {
|
||||||
|
ami = "bar"
|
||||||
|
}
|
||||||
|
|
||||||
|
# In state with `ami = "refresh-me"`, but the provider will return
|
||||||
|
# `"refreshed"` after the refresh phase. The plan should show the drift
|
||||||
|
# (`"refresh-me"` to `"refreshed"`) and plan the update (`"refreshed"` to
|
||||||
|
# `"baz"`).
|
||||||
|
resource "test_instance" "should_refresh_with_move" {
|
||||||
|
ami = "baz"
|
||||||
|
}
|
||||||
|
|
||||||
|
terraform {
|
||||||
|
experiments = [ config_driven_move ]
|
||||||
|
}
|
||||||
|
|
||||||
|
moved {
|
||||||
|
from = test_instance.should_refresh
|
||||||
|
to = test_instance.should_refresh_with_move
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
{
|
||||||
|
"format_version": "0.2",
|
||||||
|
"planned_values": {
|
||||||
|
"root_module": {
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"address": "test_instance.no_refresh",
|
||||||
|
"mode": "managed",
|
||||||
|
"type": "test_instance",
|
||||||
|
"name": "no_refresh",
|
||||||
|
"provider_name": "registry.terraform.io/hashicorp/test",
|
||||||
|
"schema_version": 0,
|
||||||
|
"values": {
|
||||||
|
"ami": "bar",
|
||||||
|
"id": "placeholder"
|
||||||
|
},
|
||||||
|
"sensitive_values": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "test_instance.should_refresh_with_move",
|
||||||
|
"mode": "managed",
|
||||||
|
"type": "test_instance",
|
||||||
|
"name": "should_refresh_with_move",
|
||||||
|
"provider_name": "registry.terraform.io/hashicorp/test",
|
||||||
|
"schema_version": 0,
|
||||||
|
"values": {
|
||||||
|
"ami": "baz",
|
||||||
|
"id": "placeholder"
|
||||||
|
},
|
||||||
|
"sensitive_values": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resource_drift": [
|
||||||
|
{
|
||||||
|
"address": "test_instance.should_refresh_with_move",
|
||||||
|
"mode": "managed",
|
||||||
|
"type": "test_instance",
|
||||||
|
"previous_address": "test_instance.should_refresh",
|
||||||
|
"provider_name": "registry.terraform.io/hashicorp/test",
|
||||||
|
"name": "should_refresh_with_move",
|
||||||
|
"change": {
|
||||||
|
"actions": [
|
||||||
|
"update"
|
||||||
|
],
|
||||||
|
"before": {
|
||||||
|
"ami": "refresh-me",
|
||||||
|
"id": "placeholder"
|
||||||
|
},
|
||||||
|
"after": {
|
||||||
|
"ami": "refreshed",
|
||||||
|
"id": "placeholder"
|
||||||
|
},
|
||||||
|
"after_sensitive": {},
|
||||||
|
"after_unknown": {},
|
||||||
|
"before_sensitive": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"resource_changes": [
|
||||||
|
{
|
||||||
|
"address": "test_instance.no_refresh",
|
||||||
|
"mode": "managed",
|
||||||
|
"type": "test_instance",
|
||||||
|
"provider_name": "registry.terraform.io/hashicorp/test",
|
||||||
|
"name": "no_refresh",
|
||||||
|
"change": {
|
||||||
|
"actions": [
|
||||||
|
"update"
|
||||||
|
],
|
||||||
|
"before": {
|
||||||
|
"ami": "foo",
|
||||||
|
"id": "placeholder"
|
||||||
|
},
|
||||||
|
"after": {
|
||||||
|
"ami": "bar",
|
||||||
|
"id": "placeholder"
|
||||||
|
},
|
||||||
|
"after_unknown": {},
|
||||||
|
"after_sensitive": {},
|
||||||
|
"before_sensitive": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "test_instance.should_refresh_with_move",
|
||||||
|
"mode": "managed",
|
||||||
|
"type": "test_instance",
|
||||||
|
"previous_address": "test_instance.should_refresh",
|
||||||
|
"provider_name": "registry.terraform.io/hashicorp/test",
|
||||||
|
"name": "should_refresh_with_move",
|
||||||
|
"change": {
|
||||||
|
"actions": [
|
||||||
|
"update"
|
||||||
|
],
|
||||||
|
"before": {
|
||||||
|
"ami": "refreshed",
|
||||||
|
"id": "placeholder"
|
||||||
|
},
|
||||||
|
"after": {
|
||||||
|
"ami": "baz",
|
||||||
|
"id": "placeholder"
|
||||||
|
},
|
||||||
|
"after_unknown": {},
|
||||||
|
"after_sensitive": {},
|
||||||
|
"before_sensitive": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"prior_state": {
|
||||||
|
"format_version": "0.2",
|
||||||
|
"values": {
|
||||||
|
"root_module": {
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"address": "test_instance.no_refresh",
|
||||||
|
"mode": "managed",
|
||||||
|
"type": "test_instance",
|
||||||
|
"name": "no_refresh",
|
||||||
|
"schema_version": 0,
|
||||||
|
"provider_name": "registry.terraform.io/hashicorp/test",
|
||||||
|
"values": {
|
||||||
|
"ami": "foo",
|
||||||
|
"id": "placeholder"
|
||||||
|
},
|
||||||
|
"sensitive_values": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "test_instance.should_refresh_with_move",
|
||||||
|
"mode": "managed",
|
||||||
|
"type": "test_instance",
|
||||||
|
"name": "should_refresh_with_move",
|
||||||
|
"schema_version": 0,
|
||||||
|
"provider_name": "registry.terraform.io/hashicorp/test",
|
||||||
|
"values": {
|
||||||
|
"ami": "refreshed",
|
||||||
|
"id": "placeholder"
|
||||||
|
},
|
||||||
|
"sensitive_values": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"configuration": {
|
||||||
|
"root_module": {
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"address": "test_instance.no_refresh",
|
||||||
|
"mode": "managed",
|
||||||
|
"type": "test_instance",
|
||||||
|
"name": "no_refresh",
|
||||||
|
"provider_config_key": "test",
|
||||||
|
"schema_version": 0,
|
||||||
|
"expressions": {
|
||||||
|
"ami": {
|
||||||
|
"constant_value": "bar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "test_instance.should_refresh_with_move",
|
||||||
|
"mode": "managed",
|
||||||
|
"type": "test_instance",
|
||||||
|
"name": "should_refresh_with_move",
|
||||||
|
"provider_config_key": "test",
|
||||||
|
"schema_version": 0,
|
||||||
|
"expressions": {
|
||||||
|
"ami": {
|
||||||
|
"constant_value": "baz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"terraform_version": "0.12.0",
|
||||||
|
"serial": 7,
|
||||||
|
"lineage": "configuredUnchanged",
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"mode": "managed",
|
||||||
|
"type": "test_instance",
|
||||||
|
"name": "no_refresh",
|
||||||
|
"provider": "provider[\"registry.terraform.io/hashicorp/test\"]",
|
||||||
|
"instances": [
|
||||||
|
{
|
||||||
|
"schema_version": 0,
|
||||||
|
"attributes": {
|
||||||
|
"ami": "foo",
|
||||||
|
"id": "placeholder"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "managed",
|
||||||
|
"type": "test_instance",
|
||||||
|
"name": "should_refresh",
|
||||||
|
"provider": "provider[\"registry.terraform.io/hashicorp/test\"]",
|
||||||
|
"instances": [
|
||||||
|
{
|
||||||
|
"schema_version": 0,
|
||||||
|
"attributes": {
|
||||||
|
"ami": "refresh-me",
|
||||||
|
"id": "placeholder"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
resource "test_instance" "baz" {
|
||||||
|
ami = "baz"
|
||||||
|
}
|
||||||
|
|
||||||
|
terraform {
|
||||||
|
experiments = [ config_driven_move ]
|
||||||
|
}
|
||||||
|
|
||||||
|
moved {
|
||||||
|
from = test_instance.foo
|
||||||
|
to = test_instance.baz
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
{
|
||||||
|
"format_version": "0.2",
|
||||||
|
"planned_values": {
|
||||||
|
"root_module": {
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"address": "test_instance.baz",
|
||||||
|
"mode": "managed",
|
||||||
|
"type": "test_instance",
|
||||||
|
"name": "baz",
|
||||||
|
"provider_name": "registry.terraform.io/hashicorp/test",
|
||||||
|
"schema_version": 0,
|
||||||
|
"values": {
|
||||||
|
"ami": "baz",
|
||||||
|
"id": "placeholder"
|
||||||
|
},
|
||||||
|
"sensitive_values": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resource_changes": [
|
||||||
|
{
|
||||||
|
"address": "test_instance.baz",
|
||||||
|
"mode": "managed",
|
||||||
|
"type": "test_instance",
|
||||||
|
"previous_address": "test_instance.foo",
|
||||||
|
"provider_name": "registry.terraform.io/hashicorp/test",
|
||||||
|
"name": "baz",
|
||||||
|
"change": {
|
||||||
|
"actions": [
|
||||||
|
"update"
|
||||||
|
],
|
||||||
|
"before": {
|
||||||
|
"ami": "foo",
|
||||||
|
"id": "placeholder"
|
||||||
|
},
|
||||||
|
"after": {
|
||||||
|
"ami": "baz",
|
||||||
|
"id": "placeholder"
|
||||||
|
},
|
||||||
|
"after_unknown": {},
|
||||||
|
"after_sensitive": {},
|
||||||
|
"before_sensitive": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"prior_state": {
|
||||||
|
"format_version": "0.2",
|
||||||
|
"values": {
|
||||||
|
"root_module": {
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"address": "test_instance.baz",
|
||||||
|
"mode": "managed",
|
||||||
|
"type": "test_instance",
|
||||||
|
"name": "baz",
|
||||||
|
"schema_version": 0,
|
||||||
|
"provider_name": "registry.terraform.io/hashicorp/test",
|
||||||
|
"values": {
|
||||||
|
"ami": "foo",
|
||||||
|
"id": "placeholder"
|
||||||
|
},
|
||||||
|
"sensitive_values": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"configuration": {
|
||||||
|
"root_module": {
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"address": "test_instance.baz",
|
||||||
|
"mode": "managed",
|
||||||
|
"type": "test_instance",
|
||||||
|
"name": "baz",
|
||||||
|
"provider_config_key": "test",
|
||||||
|
"schema_version": 0,
|
||||||
|
"expressions": {
|
||||||
|
"ami": {
|
||||||
|
"constant_value": "baz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"terraform_version": "0.12.0",
|
||||||
|
"serial": 7,
|
||||||
|
"lineage": "configuredUnchanged",
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"mode": "managed",
|
||||||
|
"type": "test_instance",
|
||||||
|
"name": "foo",
|
||||||
|
"provider": "provider[\"registry.terraform.io/hashicorp/test\"]",
|
||||||
|
"instances": [
|
||||||
|
{
|
||||||
|
"schema_version": 0,
|
||||||
|
"attributes": {
|
||||||
|
"ami": "foo",
|
||||||
|
"id": "placeholder"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -45,32 +45,6 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"resource_drift": [
|
|
||||||
{
|
|
||||||
"address": "test_instance.test[0]",
|
|
||||||
"mode": "managed",
|
|
||||||
"type": "test_instance",
|
|
||||||
"provider_name": "registry.terraform.io/hashicorp/test",
|
|
||||||
"name": "test",
|
|
||||||
"index": 0,
|
|
||||||
"change": {
|
|
||||||
"actions": [
|
|
||||||
"no-op"
|
|
||||||
],
|
|
||||||
"before": {
|
|
||||||
"ami": "bar",
|
|
||||||
"id": "placeholder"
|
|
||||||
},
|
|
||||||
"after": {
|
|
||||||
"ami": "bar",
|
|
||||||
"id": "placeholder"
|
|
||||||
},
|
|
||||||
"before_sensitive": {},
|
|
||||||
"after_sensitive": {},
|
|
||||||
"after_unknown": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"resource_changes": [
|
"resource_changes": [
|
||||||
{
|
{
|
||||||
"address": "test_instance.test[0]",
|
"address": "test_instance.test[0]",
|
||||||
|
@ -78,6 +52,7 @@
|
||||||
"type": "test_instance",
|
"type": "test_instance",
|
||||||
"name": "test",
|
"name": "test",
|
||||||
"index": 0,
|
"index": 0,
|
||||||
|
"previous_address": "test_instance.test",
|
||||||
"provider_name": "registry.terraform.io/hashicorp/test",
|
"provider_name": "registry.terraform.io/hashicorp/test",
|
||||||
"change": {
|
"change": {
|
||||||
"actions": [
|
"actions": [
|
||||||
|
|
|
@ -98,9 +98,15 @@ For ease of consumption by callers, the plan representation includes a partial r
|
||||||
{
|
{
|
||||||
// "address" is the full absolute address of the resource instance this
|
// "address" is the full absolute address of the resource instance this
|
||||||
// change applies to, in the same format as addresses in a value
|
// change applies to, in the same format as addresses in a value
|
||||||
// representation
|
// representation.
|
||||||
"address": "module.child.aws_instance.foo[0]",
|
"address": "module.child.aws_instance.foo[0]",
|
||||||
|
|
||||||
|
// "previous_address" is the full absolute address of this resource
|
||||||
|
// instance as it was known after the previous Terraform run.
|
||||||
|
// Included only if the address has changed, e.g. by handling
|
||||||
|
// a "moved" block in the configuration.
|
||||||
|
"previous_address": "module.instances.aws_instance.foo[0]",
|
||||||
|
|
||||||
// "module_address", if set, is the module portion of the above address.
|
// "module_address", if set, is the module portion of the above address.
|
||||||
// Omitted if the instance is in the root module.
|
// Omitted if the instance is in the root module.
|
||||||
"module_address": "module.child",
|
"module_address": "module.child",
|
||||||
|
|
Loading…
Reference in New Issue