Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

command: terraform show -format=json #10665

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions command/format_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package command

import (
"bytes"
"encoding/json"
"fmt"
"sort"
"strings"
Expand Down Expand Up @@ -229,3 +230,21 @@ func formatPlanModuleSingle(
len(m.Resources)))
buf.WriteString(opts.Color.Color("[reset]\n"))
}

func FormatPlanJSON(plan *terraform.Plan) string {
err := plan.State.PrepareForWrite()
if err != nil {
// should never happen
panic(err)
}

dst := bytes.NewBuffer(make([]byte, 0, 64))
enc := json.NewEncoder(dst)
enc.SetIndent("", " ")
err = enc.Encode(plan)
if err != nil {
// should never happen
panic(err)
}
return dst.String()
}
13 changes: 13 additions & 0 deletions command/format_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package command

import (
"bytes"
"encoding/json"
"fmt"
"sort"
"strings"
Expand Down Expand Up @@ -150,3 +151,15 @@ func formatStateModuleSingle(
// Now just write how many resources are in here.
buf.WriteString(fmt.Sprintf(" %d resource(s)\n", len(m.Resources)))
}

func FormatStateJSON(state *terraform.State) string {
dst := bytes.NewBuffer(make([]byte, 0, 64))
enc := json.NewEncoder(dst)
enc.SetIndent("", " ")
err := enc.Encode(state)
if err != nil {
// should never happen
panic(err)
}
return dst.String()
}
52 changes: 42 additions & 10 deletions command/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ func (c *ShowCommand) Run(args []string) int {

args = c.Meta.process(args, false)

var format string

cmdFlags := flag.NewFlagSet("show", flag.ContinueOnError)
c.addModuleDepthFlag(cmdFlags, &moduleDepth)
cmdFlags.StringVar(&format, "format", "ui", "format-mode")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
return 1
Expand Down Expand Up @@ -91,21 +94,37 @@ func (c *ShowCommand) Run(args []string) int {
return 1
}

if plan != nil {
c.Ui.Output(FormatPlan(&FormatPlanOpts{
Plan: plan,
switch format {
case "ui": // the default, if no -format option is specified
if plan != nil {
c.Ui.Output(FormatPlan(&FormatPlanOpts{
Plan: plan,
Color: c.Colorize(),
ModuleDepth: moduleDepth,
}))
return 0
}

c.Ui.Output(FormatState(&FormatStateOpts{
State: state,
Color: c.Colorize(),
ModuleDepth: moduleDepth,
}))
return 0
}

c.Ui.Output(FormatState(&FormatStateOpts{
State: state,
Color: c.Colorize(),
ModuleDepth: moduleDepth,
}))
return 0
case "json":
if plan != nil {
c.Ui.Output(FormatPlanJSON(plan))
return 0
}

c.Ui.Output(FormatStateJSON(state))
return 0

default:
c.Ui.Error(fmt.Sprintf("%q is not a supported output format", format))
return 1
}
}

func (c *ShowCommand) Help() string {
Expand All @@ -117,11 +136,24 @@ Usage: terraform show [options] [path]

Options:

-format=name Specifies the output format. By default, human-readable
output is produced. Set -format=json for a
machine-readable JSON data structure. The remaining
options are ignored for JSON output.

-module-depth=n Specifies the depth of modules to show in the output.
By default this is -1, which will expand all.

-no-color If specified, output won't contain any color.

WARNING: JSON output is provided as a convenience for lightweight integrations
with external tools, but the JSON format is *not* frozen and may change in
future versions of Terraform.

JSON output is also more detailed than the standard human-readable output and
may contain sensitive information that is not normally included, including
the values of any outputs that are marked as sensitive.

`
return strings.TrimSpace(helpText)
}
Expand Down
73 changes: 73 additions & 0 deletions command/show_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package command

import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
Expand Down Expand Up @@ -183,3 +184,75 @@ func TestShow_state(t *testing.T) {
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
}
}

func TestShow_planJSON(t *testing.T) {
planPath := testPlanFile(t, &terraform.Plan{
Module: new(module.Tree),
State: testState(),
})

ui := new(cli.MockUi)
c := &ShowCommand{
Meta: Meta{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,
},
}

args := []string{
"-format=json",
planPath,
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
}

output := ui.OutputWriter.Bytes()
data := make(map[string]interface{})
err := json.Unmarshal(output, &data)
if err != nil {
t.Fatalf("not valid JSON: %s", err)
}
// Sniff to make sure this looks like a plan rather than a state
if _, ok := data["module"]; !ok {
t.Fatalf("JSON does not contain 'module' key")
}
if _, ok := data["state"]; !ok {
t.Fatalf("JSON does not contain 'state' key")
}
}

func TestShow_stateJSON(t *testing.T) {
originalState := testState()
statePath := testStateFile(t, originalState)

ui := new(cli.MockUi)
c := &ShowCommand{
Meta: Meta{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,
},
}

args := []string{
"-format=json",
statePath,
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
}

output := ui.OutputWriter.Bytes()
data := make(map[string]interface{})
err := json.Unmarshal(output, &data)
if err != nil {
t.Fatalf("not valid JSON: %s", err)
}
// Sniff to make sure this looks like a state rather than a plan
if _, ok := data["modules"]; !ok {
t.Fatalf("JSON does not contain 'modules' key")
}
if _, ok := data["lineage"]; !ok {
t.Fatalf("JSON does not contain 'lineage' key")
}
}
88 changes: 46 additions & 42 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ type Config struct {
// Dir is the path to the directory where this configuration was
// loaded from. If it is blank, this configuration wasn't loaded from
// any meaningful directory.
Dir string
Dir string `json:"source_dir"`

Terraform *Terraform
Atlas *AtlasConfig
Modules []*Module
ProviderConfigs []*ProviderConfig
Resources []*Resource
Variables []*Variable
Outputs []*Output
Terraform *Terraform `json:"terraform"`
Atlas *AtlasConfig `json:"atlas"`
Modules []*Module `json:"modules"`
ProviderConfigs []*ProviderConfig `json:"provider_configs"`
Resources []*Resource `json:"resources"`
Variables []*Variable `json:"variables"`
Outputs []*Output `json:"outputs"`

// The fields below can be filled in by loaders for validation
// purposes.
Expand All @@ -44,50 +44,54 @@ type Config struct {
// Terraform is the Terraform meta-configuration that can be present
// in configuration files for configuring Terraform itself.
type Terraform struct {
RequiredVersion string `hcl:"required_version"` // Required Terraform version (constraint)
// Required Terraform version (constraint)
RequiredVersion string `hcl:"required_version" json:"required_version"`
}

// AtlasConfig is the configuration for building in HashiCorp's Atlas.
type AtlasConfig struct {
Name string
Include []string
Exclude []string
Name string `json:"name"`
Include []string `json:"include"`
Exclude []string `json:"exclude"`
}

// Module is a module used within a configuration.
//
// This does not represent a module itself, this represents a module
// call-site within an existing configuration.
type Module struct {
Name string
Source string
RawConfig *RawConfig
Name string `json:"name"`
Source string `json:"source"`
RawConfig *RawConfig `json:"config"`
}

// ProviderConfig is the configuration for a resource provider.
//
// For example, Terraform needs to set the AWS access keys for the AWS
// resource provider.
type ProviderConfig struct {
Name string
Alias string
RawConfig *RawConfig
Name string `json:"name"`
Alias string `json:"alias,omitempty"`
RawConfig *RawConfig `json:"config"`
}

// A resource represents a single Terraform resource in the configuration.
// A Terraform resource is something that supports some or all of the
// usual "create, read, update, delete" operations, depending on
// the given Mode.
type Resource struct {
Mode ResourceMode // which operations the resource supports
Name string
Type string
RawCount *RawConfig
RawConfig *RawConfig
Provisioners []*Provisioner
Provider string
DependsOn []string
Lifecycle ResourceLifecycle

// which operations the resource supports
Mode ResourceMode `json:"mode"`

Name string `json:"name"`
Type string `json:"type"`
RawCount *RawConfig `json:"meta_config"`
RawConfig *RawConfig `json:"config"`
Provisioners []*Provisioner `json:"provisioners,omitempty"`
Provider string `json:"provider_name,omitempty"`
DependsOn []string `json:"depends_on,omitempty"`
Lifecycle ResourceLifecycle `json:"lifecycle"`
}

// Copy returns a copy of this Resource. Helpful for avoiding shared
Expand Down Expand Up @@ -115,9 +119,9 @@ func (r *Resource) Copy() *Resource {
// ResourceLifecycle is used to store the lifecycle tuning parameters
// to allow customized behavior
type ResourceLifecycle struct {
CreateBeforeDestroy bool `mapstructure:"create_before_destroy"`
PreventDestroy bool `mapstructure:"prevent_destroy"`
IgnoreChanges []string `mapstructure:"ignore_changes"`
CreateBeforeDestroy bool `mapstructure:"create_before_destroy" json:"create_before_destroy"`
PreventDestroy bool `mapstructure:"prevent_destroy" json:"prevent_destroy"`
IgnoreChanges []string `mapstructure:"ignore_changes" json:"ignore_changes"`
}

// Copy returns a copy of this ResourceLifecycle
Expand All @@ -133,9 +137,9 @@ func (r *ResourceLifecycle) Copy() *ResourceLifecycle {

// Provisioner is a configured provisioner step on a resource.
type Provisioner struct {
Type string
RawConfig *RawConfig
ConnInfo *RawConfig
Type string `json:"type"`
RawConfig *RawConfig `json:"config"`
ConnInfo *RawConfig `json:"conn_info,omitempty"`
}

// Copy returns a copy of this Provisioner
Expand All @@ -149,22 +153,22 @@ func (p *Provisioner) Copy() *Provisioner {

// Variable is a variable defined within the configuration.
type Variable struct {
Name string
DeclaredType string `mapstructure:"type"`
Default interface{}
Description string
Name string `json:"name"`
DeclaredType string `json:"type" mapstructure:"type"`
Default interface{} `json:"default,omitempty"`
Description string `json:"description,omitempty"`
}

// Output is an output defined within the configuration. An output is
// resulting data that is highlighted by Terraform when finished. An
// output marked Sensitive will be output in a masked form following
// application, but will still be available in state.
type Output struct {
Name string
DependsOn []string
Description string
Sensitive bool
RawConfig *RawConfig
Name string `json:"name"`
DependsOn []string `json:"depends_on,omitempty"`
Description string `json:"description,omitempty"`
Sensitive bool `json:"sensitive"`
RawConfig *RawConfig `json:"config"`
}

// VariableType is the type of value a variable is holding, and returned
Expand Down
Loading