command/show (json): marshal the state snapshot included with the plan file (#21597)

* command/show: marshal the state snapshot from the planfile

The planfile contains a state snapshot with certain resources updated
(outputs and datasources). Previously `terraform show -json PLANFILE`
was using the current state instead of the state inside the plan as
intended.

This caused an issue when the state included a terraform_remote_state
datasource. The datasource's state gets refreshed - and therefore
upgraded to the current state version - during plan, but that won't
persist to state until apply.

* update comment to reflect new return
This commit is contained in:
Kristin Laemmert 2019-06-05 07:29:02 -04:00 committed by GitHub
parent 2f893f2b95
commit b9f114aa25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 55 additions and 43 deletions

View File

@ -91,12 +91,7 @@ func Marshal(
p *plans.Plan,
sf *statefile.File,
schemas *terraform.Schemas,
stateSchemas *terraform.Schemas,
) ([]byte, error) {
if stateSchemas == nil {
stateSchemas = schemas
}
output := newPlan()
output.TerraformVersion = version.String()
@ -125,7 +120,7 @@ func Marshal(
// output.PriorState
if sf != nil && !sf.State.Empty() {
output.PriorState, err = jsonstate.Marshal(sf, stateSchemas)
output.PriorState, err = jsonstate.Marshal(sf, schemas)
if err != nil {
return nil, fmt.Errorf("error marshaling prior state: %s", err)
}

View File

@ -115,7 +115,7 @@ func (c *ShowCommand) Run(args []string) int {
// if that fails, try to read the cli argument as a path to a statefile
if len(args) > 0 {
path := args[0]
plan, planErr = getPlanFromPath(path)
plan, stateFile, planErr = getPlanFromPath(path)
if planErr != nil {
stateFile, stateErr = getStateFromPath(path)
if stateErr != nil {
@ -129,9 +129,7 @@ func (c *ShowCommand) Run(args []string) int {
return 1
}
}
}
if stateFile == nil {
} else {
env := c.Workspace()
stateFile, stateErr = getStateFromEnv(b, env)
if err != nil {
@ -143,29 +141,7 @@ func (c *ShowCommand) Run(args []string) int {
if plan != nil {
if jsonOutput == true {
config := ctx.Config()
var err error
var jsonPlan []byte
// If there is no prior state, we have all the schemas needed.
if stateFile == nil {
jsonPlan, err = jsonplan.Marshal(config, plan, stateFile, schemas, nil)
} else {
// If there is state, we need the state-specific schemas, which
// may differ from the schemas loaded from the plan.
// This occurs if there is a data_source in the state that was
// removed from the configuration, because terraform core does
// not need to load the schema to remove a data source.
opReq.PlanFile = nil
ctx, _, ctxDiags := local.Context(opReq)
diags = diags.Append(ctxDiags)
if ctxDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
stateSchemas := ctx.Schemas()
jsonPlan, err = jsonplan.Marshal(config, plan, stateFile, schemas, stateSchemas)
}
jsonPlan, err := jsonplan.Marshal(config, plan, stateFile, schemas)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to marshal plan to json: %s", err))
@ -224,19 +200,21 @@ func (c *ShowCommand) Synopsis() string {
return "Inspect Terraform state or plan"
}
// getPlanFromPath returns a plan if the user-supplied path points to a planfile.
// If both plan and error are nil, the path is likely a directory.
// An error could suggest that the given path points to a statefile.
func getPlanFromPath(path string) (*plans.Plan, error) {
// getPlanFromPath returns a plan and statefile if the user-supplied path points
// to a planfile. If both plan and error are nil, the path is likely a
// directory. An error could suggest that the given path points to a statefile.
func getPlanFromPath(path string) (*plans.Plan, *statefile.File, error) {
pr, err := planfile.Open(path)
if err != nil {
return nil, err
return nil, nil, err
}
plan, err := pr.ReadPlan()
if err != nil {
return nil, err
return nil, nil, err
}
return plan, nil
stateFile, err := pr.ReadStateFile()
return plan, stateFile, nil
}
// getStateFromPath returns a statefile if the user-supplied path points to a statefile.

View File

@ -412,6 +412,11 @@ type plan struct {
PlannedValues map[string]interface{} `json:"planned_values,omitempty"`
ResourceChanges []interface{} `json:"resource_changes,omitempty"`
OutputChanges map[string]interface{} `json:"output_changes,omitempty"`
PriorState map[string]interface{} `json:"prior_state,omitempty"`
PriorState priorState `json:"prior_state,omitempty"`
Config map[string]interface{} `json:"configuration,omitempty"`
}
type priorState struct {
FormatVersion string `json:"format_version,omitempty"`
Values map[string]interface{} `json:"values,omitempty"`
}

View File

@ -53,6 +53,18 @@
]
}
},
"prior_state": {
"format_version": "0.1",
"values": {
"outputs": {
"test": {
"sensitive": false,
"value": "bar"
}
},
"root_module": {}
}
},
"resource_changes": [
{
"address": "test_instance.test[0]",

View File

@ -82,8 +82,13 @@
},
"prior_state": {
"format_version": "0.1",
"terraform_version": "0.12.0",
"values": {
"outputs": {
"test": {
"sensitive": false,
"value": "bar"
}
},
"root_module": {
"resources": [
{

View File

@ -64,8 +64,13 @@
},
"prior_state": {
"format_version": "0.1",
"terraform_version": "0.12.0",
"values": {
"outputs": {
"test": {
"sensitive": false,
"value": "bar"
}
},
"root_module": {
"resources": [
{

View File

@ -69,6 +69,18 @@
]
}
},
"prior_state": {
"format_version": "0.1",
"values": {
"outputs": {
"test": {
"sensitive": false,
"value": "baz"
}
},
"root_module": {}
}
},
"resource_changes": [
{
"address": "module.module_test_bar.test_instance.test",