command/show: improvements to json output (#20139)

* command/show: add support for -json output for state

* command/jsonconfig: do not marshal empty count/for each expressions

* command/jsonstate: continue gracefully if the terraform version is somehow missing from state
This commit is contained in:
Kristin Laemmert 2019-01-28 15:53:53 -08:00 committed by GitHub
parent 50e2b1856e
commit a2ac491cde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 28 additions and 42 deletions

View File

@ -41,8 +41,8 @@ type module struct {
type moduleCall struct { type moduleCall struct {
ResolvedSource string `json:"resolved_source,omitempty"` ResolvedSource string `json:"resolved_source,omitempty"`
Expressions map[string]interface{} `json:"expressions,omitempty"` Expressions map[string]interface{} `json:"expressions,omitempty"`
CountExpression expression `json:"count_expression,omitempty"` CountExpression *expression `json:"count_expression,omitempty"`
ForEachExpression expression `json:"for_each_expression,omitempty"` ForEachExpression *expression `json:"for_each_expression,omitempty"`
Module module `json:"module,omitempty"` Module module `json:"module,omitempty"`
} }
@ -76,8 +76,8 @@ type resource struct {
// CountExpression and ForEachExpression describe the expressions given for // CountExpression and ForEachExpression describe the expressions given for
// the corresponding meta-arguments in the resource configuration block. // the corresponding meta-arguments in the resource configuration block.
// These are omitted if the corresponding argument isn't set. // These are omitted if the corresponding argument isn't set.
CountExpression expression `json:"count_expression,omitempty"` CountExpression *expression `json:"count_expression,omitempty"`
ForEachExpression expression `json:"for_each_expression,omitempty"` ForEachExpression *expression `json:"for_each_expression,omitempty"`
} }
type configOutput struct { type configOutput struct {
@ -169,11 +169,11 @@ func marshalModuleCalls(c *configs.Config, schemas *terraform.Schemas) []moduleC
} }
cExp := marshalExpression(v.Count) cExp := marshalExpression(v.Count)
if !cExp.Empty() { if !cExp.Empty() {
mc.CountExpression = cExp mc.CountExpression = &cExp
} else { } else {
fExp := marshalExpression(v.ForEach) fExp := marshalExpression(v.ForEach)
if !fExp.Empty() { if !fExp.Empty() {
mc.ForEachExpression = fExp mc.ForEachExpression = &fExp
} }
} }
@ -219,11 +219,11 @@ func marshalResources(resources map[string]*configs.Resource, schemas *terraform
cExp := marshalExpression(v.Count) cExp := marshalExpression(v.Count)
if !cExp.Empty() { if !cExp.Empty() {
r.CountExpression = cExp r.CountExpression = &cExp
} else { } else {
fExp := marshalExpression(v.ForEach) fExp := marshalExpression(v.ForEach)
if !fExp.Empty() { if !fExp.Empty() {
r.ForEachExpression = fExp r.ForEachExpression = &fExp
} }
} }

View File

@ -119,8 +119,9 @@ func Marshal(sf *statefile.File, schemas *terraform.Schemas) ([]byte, error) {
} }
output := newState() output := newState()
output.TerraformVersion = sf.TerraformVersion.String() if sf.TerraformVersion != nil {
output.TerraformVersion = sf.TerraformVersion.String()
}
// output.StateValues // output.StateValues
err := output.marshalStateValues(sf.State, schemas) err := output.marshalStateValues(sf.State, schemas)
if err != nil { if err != nil {

View File

@ -8,6 +8,7 @@ import (
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/format" "github.com/hashicorp/terraform/command/format"
"github.com/hashicorp/terraform/command/jsonplan" "github.com/hashicorp/terraform/command/jsonplan"
"github.com/hashicorp/terraform/command/jsonstate"
"github.com/hashicorp/terraform/plans" "github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/plans/planfile" "github.com/hashicorp/terraform/plans/planfile"
"github.com/hashicorp/terraform/states/statefile" "github.com/hashicorp/terraform/states/statefile"
@ -29,7 +30,7 @@ func (c *ShowCommand) Run(args []string) int {
cmdFlags := c.Meta.defaultFlagSet("show") cmdFlags := c.Meta.defaultFlagSet("show")
var jsonOutput bool var jsonOutput bool
cmdFlags.BoolVar(&jsonOutput, "json", false, "produce JSON output (only available when showing a planfile)") cmdFlags.BoolVar(&jsonOutput, "json", false, "produce JSON output")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil { if err := cmdFlags.Parse(args); err != nil {
return 1 return 1
@ -110,11 +111,6 @@ func (c *ShowCommand) Run(args []string) int {
path := args[0] path := args[0]
plan, planErr = getPlanFromPath(path) plan, planErr = getPlanFromPath(path)
if planErr != nil { if planErr != nil {
// json output is only supported for plans
if jsonOutput == true {
c.Ui.Error("Error: JSON output not available for state")
return 1
}
stateFile, stateErr = getStateFromPath(path) stateFile, stateErr = getStateFromPath(path)
if stateErr != nil { if stateErr != nil {
c.Ui.Error(fmt.Sprintf( c.Ui.Error(fmt.Sprintf(
@ -162,11 +158,21 @@ func (c *ShowCommand) Run(args []string) int {
return 0 return 0
} }
c.Ui.Output(format.State(&format.StateOpts{ if jsonOutput == true {
State: stateFile.State, jsonState, err := jsonstate.Marshal(stateFile, schemas)
Color: c.Colorize(), if err != nil {
Schemas: schemas, c.Ui.Error(fmt.Sprintf("Failed to marshal state to json: %s", err))
})) return 1
}
c.Ui.Output(string(jsonState))
} else {
c.Ui.Output(format.State(&format.StateOpts{
State: stateFile.State,
Color: c.Colorize(),
Schemas: schemas,
}))
}
return 0 return 0
} }

View File

@ -36,27 +36,6 @@ func TestShow(t *testing.T) {
} }
} }
func TestShow_JSONStateNotImplemented(t *testing.T) {
// Create the default state
statePath := testStateFile(t, testState())
defer testChdir(t, filepath.Dir(statePath))()
ui := new(cli.MockUi)
c := &ShowCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui,
},
}
args := []string{
"-json",
statePath,
}
if code := c.Run(args); code != 1 {
t.Fatalf("bad: \n%s", ui.OutputWriter.String())
}
}
func TestShow_noArgs(t *testing.T) { func TestShow_noArgs(t *testing.T) {
// Create the default state // Create the default state
statePath := testStateFile(t, testState()) statePath := testStateFile(t, testState())