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:
parent
2f893f2b95
commit
b9f114aa25
|
@ -91,12 +91,7 @@ func Marshal(
|
||||||
p *plans.Plan,
|
p *plans.Plan,
|
||||||
sf *statefile.File,
|
sf *statefile.File,
|
||||||
schemas *terraform.Schemas,
|
schemas *terraform.Schemas,
|
||||||
stateSchemas *terraform.Schemas,
|
|
||||||
) ([]byte, error) {
|
) ([]byte, error) {
|
||||||
if stateSchemas == nil {
|
|
||||||
stateSchemas = schemas
|
|
||||||
}
|
|
||||||
|
|
||||||
output := newPlan()
|
output := newPlan()
|
||||||
output.TerraformVersion = version.String()
|
output.TerraformVersion = version.String()
|
||||||
|
|
||||||
|
@ -125,7 +120,7 @@ func Marshal(
|
||||||
|
|
||||||
// output.PriorState
|
// output.PriorState
|
||||||
if sf != nil && !sf.State.Empty() {
|
if sf != nil && !sf.State.Empty() {
|
||||||
output.PriorState, err = jsonstate.Marshal(sf, stateSchemas)
|
output.PriorState, err = jsonstate.Marshal(sf, schemas)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error marshaling prior state: %s", err)
|
return nil, fmt.Errorf("error marshaling prior state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 that fails, try to read the cli argument as a path to a statefile
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
path := args[0]
|
path := args[0]
|
||||||
plan, planErr = getPlanFromPath(path)
|
plan, stateFile, planErr = getPlanFromPath(path)
|
||||||
if planErr != nil {
|
if planErr != nil {
|
||||||
stateFile, stateErr = getStateFromPath(path)
|
stateFile, stateErr = getStateFromPath(path)
|
||||||
if stateErr != nil {
|
if stateErr != nil {
|
||||||
|
@ -129,9 +129,7 @@ func (c *ShowCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
|
||||||
if stateFile == nil {
|
|
||||||
env := c.Workspace()
|
env := c.Workspace()
|
||||||
stateFile, stateErr = getStateFromEnv(b, env)
|
stateFile, stateErr = getStateFromEnv(b, env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -143,29 +141,7 @@ func (c *ShowCommand) Run(args []string) int {
|
||||||
if plan != nil {
|
if plan != nil {
|
||||||
if jsonOutput == true {
|
if jsonOutput == true {
|
||||||
config := ctx.Config()
|
config := ctx.Config()
|
||||||
|
jsonPlan, err := jsonplan.Marshal(config, plan, stateFile, schemas)
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to marshal plan to json: %s", err))
|
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"
|
return "Inspect Terraform state or plan"
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPlanFromPath returns a plan if the user-supplied path points to a planfile.
|
// getPlanFromPath returns a plan and statefile if the user-supplied path points
|
||||||
// If both plan and error are nil, the path is likely a directory.
|
// to a planfile. If both plan and error are nil, the path is likely a
|
||||||
// An error could suggest that the given path points to a statefile.
|
// directory. An error could suggest that the given path points to a statefile.
|
||||||
func getPlanFromPath(path string) (*plans.Plan, error) {
|
func getPlanFromPath(path string) (*plans.Plan, *statefile.File, error) {
|
||||||
pr, err := planfile.Open(path)
|
pr, err := planfile.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
plan, err := pr.ReadPlan()
|
plan, err := pr.ReadPlan()
|
||||||
if err != nil {
|
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.
|
// getStateFromPath returns a statefile if the user-supplied path points to a statefile.
|
||||||
|
|
|
@ -412,6 +412,11 @@ type plan struct {
|
||||||
PlannedValues map[string]interface{} `json:"planned_values,omitempty"`
|
PlannedValues map[string]interface{} `json:"planned_values,omitempty"`
|
||||||
ResourceChanges []interface{} `json:"resource_changes,omitempty"`
|
ResourceChanges []interface{} `json:"resource_changes,omitempty"`
|
||||||
OutputChanges map[string]interface{} `json:"output_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"`
|
Config map[string]interface{} `json:"configuration,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type priorState struct {
|
||||||
|
FormatVersion string `json:"format_version,omitempty"`
|
||||||
|
Values map[string]interface{} `json:"values,omitempty"`
|
||||||
|
}
|
||||||
|
|
|
@ -53,6 +53,18 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"prior_state": {
|
||||||
|
"format_version": "0.1",
|
||||||
|
"values": {
|
||||||
|
"outputs": {
|
||||||
|
"test": {
|
||||||
|
"sensitive": false,
|
||||||
|
"value": "bar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root_module": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
"resource_changes": [
|
"resource_changes": [
|
||||||
{
|
{
|
||||||
"address": "test_instance.test[0]",
|
"address": "test_instance.test[0]",
|
||||||
|
|
|
@ -82,8 +82,13 @@
|
||||||
},
|
},
|
||||||
"prior_state": {
|
"prior_state": {
|
||||||
"format_version": "0.1",
|
"format_version": "0.1",
|
||||||
"terraform_version": "0.12.0",
|
|
||||||
"values": {
|
"values": {
|
||||||
|
"outputs": {
|
||||||
|
"test": {
|
||||||
|
"sensitive": false,
|
||||||
|
"value": "bar"
|
||||||
|
}
|
||||||
|
},
|
||||||
"root_module": {
|
"root_module": {
|
||||||
"resources": [
|
"resources": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -64,8 +64,13 @@
|
||||||
},
|
},
|
||||||
"prior_state": {
|
"prior_state": {
|
||||||
"format_version": "0.1",
|
"format_version": "0.1",
|
||||||
"terraform_version": "0.12.0",
|
|
||||||
"values": {
|
"values": {
|
||||||
|
"outputs": {
|
||||||
|
"test": {
|
||||||
|
"sensitive": false,
|
||||||
|
"value": "bar"
|
||||||
|
}
|
||||||
|
},
|
||||||
"root_module": {
|
"root_module": {
|
||||||
"resources": [
|
"resources": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -69,6 +69,18 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"prior_state": {
|
||||||
|
"format_version": "0.1",
|
||||||
|
"values": {
|
||||||
|
"outputs": {
|
||||||
|
"test": {
|
||||||
|
"sensitive": false,
|
||||||
|
"value": "baz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root_module": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
"resource_changes": [
|
"resource_changes": [
|
||||||
{
|
{
|
||||||
"address": "module.module_test_bar.test_instance.test",
|
"address": "module.module_test_bar.test_instance.test",
|
||||||
|
|
Loading…
Reference in New Issue