cli: Migrate show command to use command arguments and views
This commit is contained in:
parent
8d1bced812
commit
fea8f6cfa2
|
@ -0,0 +1,59 @@
|
||||||
|
package arguments
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Show represents the command-line arguments for the show command.
|
||||||
|
type Show struct {
|
||||||
|
// Path is the path to the state file or plan file to be displayed. If
|
||||||
|
// unspecified, show will display the latest state snapshot.
|
||||||
|
Path string
|
||||||
|
|
||||||
|
// ViewType specifies which output format to use: human, JSON, or "raw".
|
||||||
|
ViewType ViewType
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseShow processes CLI arguments, returning a Show value and errors.
|
||||||
|
// If errors are encountered, a Show value is still returned representing
|
||||||
|
// the best effort interpretation of the arguments.
|
||||||
|
func ParseShow(args []string) (*Show, tfdiags.Diagnostics) {
|
||||||
|
var diags tfdiags.Diagnostics
|
||||||
|
show := &Show{
|
||||||
|
Path: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonOutput bool
|
||||||
|
cmdFlags := defaultFlagSet("show")
|
||||||
|
cmdFlags.BoolVar(&jsonOutput, "json", false, "json")
|
||||||
|
|
||||||
|
if err := cmdFlags.Parse(args); err != nil {
|
||||||
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Failed to parse command-line flags",
|
||||||
|
err.Error(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
args = cmdFlags.Args()
|
||||||
|
if len(args) > 1 {
|
||||||
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Too many command line arguments",
|
||||||
|
"Expected at most one positional argument.",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) > 0 {
|
||||||
|
show.Path = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case jsonOutput:
|
||||||
|
show.ViewType = ViewJSON
|
||||||
|
default:
|
||||||
|
show.ViewType = ViewHuman
|
||||||
|
}
|
||||||
|
|
||||||
|
return show, diags
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package arguments
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseShow_valid(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
args []string
|
||||||
|
want *Show
|
||||||
|
}{
|
||||||
|
"defaults": {
|
||||||
|
nil,
|
||||||
|
&Show{
|
||||||
|
Path: "",
|
||||||
|
ViewType: ViewHuman,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"json": {
|
||||||
|
[]string{"-json"},
|
||||||
|
&Show{
|
||||||
|
Path: "",
|
||||||
|
ViewType: ViewJSON,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
[]string{"-json", "foo"},
|
||||||
|
&Show{
|
||||||
|
Path: "foo",
|
||||||
|
ViewType: ViewJSON,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
got, diags := ParseShow(tc.args)
|
||||||
|
if len(diags) > 0 {
|
||||||
|
t.Fatalf("unexpected diags: %v", diags)
|
||||||
|
}
|
||||||
|
if *got != *tc.want {
|
||||||
|
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseShow_invalid(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
args []string
|
||||||
|
want *Show
|
||||||
|
wantDiags tfdiags.Diagnostics
|
||||||
|
}{
|
||||||
|
"unknown flag": {
|
||||||
|
[]string{"-boop"},
|
||||||
|
&Show{
|
||||||
|
Path: "",
|
||||||
|
ViewType: ViewHuman,
|
||||||
|
},
|
||||||
|
tfdiags.Diagnostics{
|
||||||
|
tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Failed to parse command-line flags",
|
||||||
|
"flag provided but not defined: -boop",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"too many arguments": {
|
||||||
|
[]string{"-json", "bar", "baz"},
|
||||||
|
&Show{
|
||||||
|
Path: "bar",
|
||||||
|
ViewType: ViewJSON,
|
||||||
|
},
|
||||||
|
tfdiags.Diagnostics{
|
||||||
|
tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Too many command line arguments",
|
||||||
|
"Expected at most one positional argument.",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
got, gotDiags := ParseShow(tc.args)
|
||||||
|
if *got != *tc.want {
|
||||||
|
t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(gotDiags, tc.wantDiags) {
|
||||||
|
t.Errorf("wrong result\ngot: %s\nwant: %s", spew.Sdump(gotDiags), spew.Sdump(tc.wantDiags))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,9 +7,6 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/internal/backend"
|
"github.com/hashicorp/terraform/internal/backend"
|
||||||
"github.com/hashicorp/terraform/internal/command/arguments"
|
"github.com/hashicorp/terraform/internal/command/arguments"
|
||||||
"github.com/hashicorp/terraform/internal/command/format"
|
|
||||||
"github.com/hashicorp/terraform/internal/command/jsonplan"
|
|
||||||
"github.com/hashicorp/terraform/internal/command/jsonstate"
|
|
||||||
"github.com/hashicorp/terraform/internal/command/views"
|
"github.com/hashicorp/terraform/internal/command/views"
|
||||||
"github.com/hashicorp/terraform/internal/configs"
|
"github.com/hashicorp/terraform/internal/configs"
|
||||||
"github.com/hashicorp/terraform/internal/plans"
|
"github.com/hashicorp/terraform/internal/plans"
|
||||||
|
@ -26,142 +23,40 @@ type ShowCommand struct {
|
||||||
Meta
|
Meta
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ShowCommand) Run(args []string) int {
|
func (c *ShowCommand) Run(rawArgs []string) int {
|
||||||
args = c.Meta.process(args)
|
// Parse and apply global view arguments
|
||||||
cmdFlags := c.Meta.defaultFlagSet("show")
|
common, rawArgs := arguments.ParseView(rawArgs)
|
||||||
var jsonOutput bool
|
c.View.Configure(common)
|
||||||
cmdFlags.BoolVar(&jsonOutput, "json", false, "produce JSON output")
|
|
||||||
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
// Parse and validate flags
|
||||||
if err := cmdFlags.Parse(args); err != nil {
|
args, diags := arguments.ParseShow(rawArgs)
|
||||||
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
|
if diags.HasErrors() {
|
||||||
|
c.View.Diagnostics(diags)
|
||||||
|
c.View.HelpPrompt("show")
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
args = cmdFlags.Args()
|
// Set up view
|
||||||
if len(args) > 2 {
|
view := views.NewShow(args.ViewType, c.View)
|
||||||
c.Ui.Error(
|
|
||||||
"The show command expects at most two arguments.\n The path to a " +
|
|
||||||
"Terraform state or plan file, and optionally -json for json output.\n")
|
|
||||||
cmdFlags.Usage()
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for user-supplied plugin path
|
// Check for user-supplied plugin path
|
||||||
var err error
|
var err error
|
||||||
if c.pluginPath, err = c.loadPluginPath(); err != nil {
|
if c.pluginPath, err = c.loadPluginPath(); err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Error loading plugin path: %s", err))
|
diags = diags.Append(fmt.Errorf("error loading plugin path: %s", err))
|
||||||
|
view.Diagnostics(diags)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
var diags tfdiags.Diagnostics
|
// Get the data we need to display
|
||||||
|
plan, stateFile, config, schemas, showDiags := c.show(args.Path)
|
||||||
var planErr, stateErr error
|
diags = diags.Append(showDiags)
|
||||||
var plan *plans.Plan
|
if showDiags.HasErrors() {
|
||||||
var stateFile *statefile.File
|
view.Diagnostics(diags)
|
||||||
var config *configs.Config
|
|
||||||
var schemas *terraform.Schemas
|
|
||||||
|
|
||||||
// if a path was provided, try to read it as a path to a planfile
|
|
||||||
// if that fails, try to read the cli argument as a path to a statefile
|
|
||||||
if len(args) > 0 {
|
|
||||||
path := args[0]
|
|
||||||
plan, stateFile, config, planErr = getPlanFromPath(path)
|
|
||||||
if planErr != nil {
|
|
||||||
stateFile, stateErr = getStateFromPath(path)
|
|
||||||
if stateErr != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf(
|
|
||||||
"Terraform couldn't read the given file as a state or plan file.\n"+
|
|
||||||
"The errors while attempting to read the file as each format are\n"+
|
|
||||||
"shown below.\n\n"+
|
|
||||||
"State read error: %s\n\nPlan read error: %s",
|
|
||||||
stateErr,
|
|
||||||
planErr))
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Load the backend
|
|
||||||
b, backendDiags := c.Backend(nil)
|
|
||||||
diags = diags.Append(backendDiags)
|
|
||||||
if backendDiags.HasErrors() {
|
|
||||||
c.showDiagnostics(diags)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
c.ignoreRemoteVersionConflict(b)
|
|
||||||
|
|
||||||
workspace, err := c.Workspace()
|
// Display the data
|
||||||
if err != nil {
|
return view.Display(config, plan, stateFile, schemas)
|
||||||
c.Ui.Error(fmt.Sprintf("Error selecting workspace: %s", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
stateFile, stateErr = getStateFromBackend(b, workspace)
|
|
||||||
if stateErr != nil {
|
|
||||||
c.Ui.Error(stateErr.Error())
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config != nil || stateFile != nil {
|
|
||||||
opts, err := c.contextOpts()
|
|
||||||
if err != nil {
|
|
||||||
diags = diags.Append(err)
|
|
||||||
c.showDiagnostics(diags)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
tfCtx, ctxDiags := terraform.NewContext(opts)
|
|
||||||
diags = diags.Append(ctxDiags)
|
|
||||||
if ctxDiags.HasErrors() {
|
|
||||||
c.showDiagnostics(diags)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
var schemaDiags tfdiags.Diagnostics
|
|
||||||
schemas, schemaDiags = tfCtx.Schemas(config, stateFile.State)
|
|
||||||
diags = diags.Append(schemaDiags)
|
|
||||||
if schemaDiags.HasErrors() {
|
|
||||||
c.showDiagnostics(diags)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if plan != nil {
|
|
||||||
if jsonOutput {
|
|
||||||
jsonPlan, err := jsonplan.Marshal(config, plan, stateFile, schemas)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to marshal plan to json: %s", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
c.Ui.Output(string(jsonPlan))
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
view := views.NewShow(arguments.ViewHuman, c.View)
|
|
||||||
view.Plan(plan, schemas)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if jsonOutput {
|
|
||||||
// At this point, it is possible that there is neither state nor a plan.
|
|
||||||
// That's ok, we'll just return an empty object.
|
|
||||||
jsonState, err := jsonstate.Marshal(stateFile, schemas)
|
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to marshal state to json: %s", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
c.Ui.Output(string(jsonState))
|
|
||||||
} else {
|
|
||||||
if stateFile == nil {
|
|
||||||
c.Ui.Output("No state.")
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
c.Ui.Output(format.State(&format.StateOpts{
|
|
||||||
State: stateFile.State,
|
|
||||||
Color: c.Colorize(),
|
|
||||||
Schemas: schemas,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ShowCommand) Help() string {
|
func (c *ShowCommand) Help() string {
|
||||||
|
@ -185,8 +80,113 @@ func (c *ShowCommand) Synopsis() string {
|
||||||
return "Show the current state or a saved plan"
|
return "Show the current state or a saved plan"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ShowCommand) show(path string) (*plans.Plan, *statefile.File, *configs.Config, *terraform.Schemas, tfdiags.Diagnostics) {
|
||||||
|
var diags, showDiags tfdiags.Diagnostics
|
||||||
|
var plan *plans.Plan
|
||||||
|
var stateFile *statefile.File
|
||||||
|
var config *configs.Config
|
||||||
|
var schemas *terraform.Schemas
|
||||||
|
|
||||||
|
// No plan file or state file argument provided,
|
||||||
|
// so get the latest state snapshot
|
||||||
|
if path == "" {
|
||||||
|
stateFile, showDiags = c.showFromLatestStateSnapshot()
|
||||||
|
diags = diags.Append(showDiags)
|
||||||
|
if showDiags.HasErrors() {
|
||||||
|
return plan, stateFile, config, schemas, diags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plan file or state file argument provided,
|
||||||
|
// so try to load the argument as a plan file first.
|
||||||
|
// If that fails, try to load it as a statefile.
|
||||||
|
if path != "" {
|
||||||
|
plan, stateFile, config, showDiags = c.showFromPath(path)
|
||||||
|
diags = diags.Append(showDiags)
|
||||||
|
if showDiags.HasErrors() {
|
||||||
|
return plan, stateFile, config, schemas, diags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get schemas, if possible
|
||||||
|
if config != nil || stateFile != nil {
|
||||||
|
opts, err := c.contextOpts()
|
||||||
|
if err != nil {
|
||||||
|
diags = diags.Append(err)
|
||||||
|
return plan, stateFile, config, schemas, diags
|
||||||
|
}
|
||||||
|
tfCtx, ctxDiags := terraform.NewContext(opts)
|
||||||
|
diags = diags.Append(ctxDiags)
|
||||||
|
if ctxDiags.HasErrors() {
|
||||||
|
return plan, stateFile, config, schemas, diags
|
||||||
|
}
|
||||||
|
var schemaDiags tfdiags.Diagnostics
|
||||||
|
schemas, schemaDiags = tfCtx.Schemas(config, stateFile.State)
|
||||||
|
diags = diags.Append(schemaDiags)
|
||||||
|
if schemaDiags.HasErrors() {
|
||||||
|
return plan, stateFile, config, schemas, diags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return plan, stateFile, config, schemas, diags
|
||||||
|
}
|
||||||
|
func (c *ShowCommand) showFromLatestStateSnapshot() (*statefile.File, tfdiags.Diagnostics) {
|
||||||
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
|
// Load the backend
|
||||||
|
b, backendDiags := c.Backend(nil)
|
||||||
|
diags = diags.Append(backendDiags)
|
||||||
|
if backendDiags.HasErrors() {
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
c.ignoreRemoteVersionConflict(b)
|
||||||
|
|
||||||
|
// Load the workspace
|
||||||
|
workspace, err := c.Workspace()
|
||||||
|
if err != nil {
|
||||||
|
diags = diags.Append(fmt.Errorf("error selecting workspace: %s", err))
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the latest state snapshot from the backend for the current workspace
|
||||||
|
stateFile, stateErr := getStateFromBackend(b, workspace)
|
||||||
|
if stateErr != nil {
|
||||||
|
diags = diags.Append(stateErr.Error())
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
return stateFile, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ShowCommand) showFromPath(path string) (*plans.Plan, *statefile.File, *configs.Config, tfdiags.Diagnostics) {
|
||||||
|
var diags tfdiags.Diagnostics
|
||||||
|
var planErr, stateErr error
|
||||||
|
var plan *plans.Plan
|
||||||
|
var stateFile *statefile.File
|
||||||
|
var config *configs.Config
|
||||||
|
|
||||||
|
// Try to get the plan file and associated data from
|
||||||
|
// the path argument. If that fails, try to get the
|
||||||
|
// statefile from the path argument.
|
||||||
|
plan, stateFile, config, planErr = getPlanFromPath(path)
|
||||||
|
if planErr != nil {
|
||||||
|
stateFile, stateErr = getStateFromPath(path)
|
||||||
|
if stateErr != nil {
|
||||||
|
diags = diags.Append(
|
||||||
|
tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Failed to read the given file as a state or plan file",
|
||||||
|
fmt.Sprintf("State read error: %s\n\nPlan read error: %s", stateErr, planErr),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return nil, nil, nil, diags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return plan, stateFile, config, diags
|
||||||
|
}
|
||||||
|
|
||||||
// getPlanFromPath returns a plan, statefile, and config if the user-supplied
|
// getPlanFromPath returns a plan, statefile, and config if the user-supplied
|
||||||
// path points to a planfile. If both plan and error are nil, the path is likely
|
// path points to a plan file. 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.
|
// a directory. An error could suggest that the given path points to a statefile.
|
||||||
func getPlanFromPath(path string) (*plans.Plan, *statefile.File, *configs.Config, error) {
|
func getPlanFromPath(path string) (*plans.Plan, *statefile.File, *configs.Config, error) {
|
||||||
planReader, err := planfile.Open(path)
|
planReader, err := planfile.Open(path)
|
||||||
|
|
|
@ -2,7 +2,6 @@ package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -22,13 +21,11 @@ import (
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestShow(t *testing.T) {
|
func TestShow_badArgs(t *testing.T) {
|
||||||
ui := new(cli.MockUi)
|
view, done := testView(t)
|
||||||
view, _ := testView(t)
|
|
||||||
c := &ShowCommand{
|
c := &ShowCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||||
Ui: ui,
|
|
||||||
View: view,
|
View: view,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -36,40 +33,99 @@ func TestShow(t *testing.T) {
|
||||||
args := []string{
|
args := []string{
|
||||||
"bad",
|
"bad",
|
||||||
"bad",
|
"bad",
|
||||||
|
"-no-color",
|
||||||
}
|
}
|
||||||
if code := c.Run(args); code != 1 {
|
|
||||||
t.Fatalf("bad: \n%s", ui.OutputWriter.String())
|
code := c.Run(args)
|
||||||
|
output := done(t)
|
||||||
|
|
||||||
|
if code != 1 {
|
||||||
|
t.Fatalf("unexpected exit status %d; want 1\ngot: %s", code, output.Stdout())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShow_noArgs(t *testing.T) {
|
func TestShow_noArgsNoState(t *testing.T) {
|
||||||
|
view, done := testView(t)
|
||||||
|
c := &ShowCommand{
|
||||||
|
Meta: Meta{
|
||||||
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||||
|
View: view,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
code := c.Run([]string{})
|
||||||
|
output := done(t)
|
||||||
|
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr())
|
||||||
|
}
|
||||||
|
|
||||||
|
got := output.Stdout()
|
||||||
|
want := `No state.`
|
||||||
|
if !strings.Contains(got, want) {
|
||||||
|
t.Fatalf("unexpected output\ngot: %s\nwant: %s", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShow_noArgsWithState(t *testing.T) {
|
||||||
// Get a temp cwd
|
// Get a temp cwd
|
||||||
tmp, cwd := testCwd(t)
|
tmp, cwd := testCwd(t)
|
||||||
defer testFixCwd(t, tmp, cwd)
|
defer testFixCwd(t, tmp, cwd)
|
||||||
// Create the default state
|
// Create the default state
|
||||||
testStateFileDefault(t, testState())
|
testStateFileDefault(t, testState())
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
view, done := testView(t)
|
||||||
view, _ := testView(t)
|
|
||||||
c := &ShowCommand{
|
c := &ShowCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||||
Ui: ui,
|
|
||||||
View: view,
|
View: view,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if code := c.Run([]string{}); code != 0 {
|
code := c.Run([]string{})
|
||||||
t.Fatalf("bad: \n%s", ui.OutputWriter.String())
|
output := done(t)
|
||||||
|
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr())
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(ui.OutputWriter.String(), "# test_instance.foo:") {
|
got := output.Stdout()
|
||||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
want := `# test_instance.foo:`
|
||||||
|
if !strings.Contains(got, want) {
|
||||||
|
t.Fatalf("unexpected output\ngot: %s\nwant: %s", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShow_argsWithState(t *testing.T) {
|
||||||
|
// Create the default state
|
||||||
|
statePath := testStateFile(t, testState())
|
||||||
|
stateDir := filepath.Dir(statePath)
|
||||||
|
defer os.RemoveAll(stateDir)
|
||||||
|
defer testChdir(t, stateDir)()
|
||||||
|
|
||||||
|
view, done := testView(t)
|
||||||
|
c := &ShowCommand{
|
||||||
|
Meta: Meta{
|
||||||
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||||
|
View: view,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
path := filepath.Base(statePath)
|
||||||
|
args := []string{
|
||||||
|
path,
|
||||||
|
"-no-color",
|
||||||
|
}
|
||||||
|
code := c.Run(args)
|
||||||
|
output := done(t)
|
||||||
|
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/hashicorp/terraform/issues/21462
|
// https://github.com/hashicorp/terraform/issues/21462
|
||||||
func TestShow_aliasedProvider(t *testing.T) {
|
func TestShow_argsWithStateAliasedProvider(t *testing.T) {
|
||||||
// Create the default state with aliased resource
|
// Create the default state with aliased resource
|
||||||
testState := states.BuildState(func(s *states.SyncState) {
|
testState := states.BuildState(func(s *states.SyncState) {
|
||||||
s.SetResourceInstanceCurrent(
|
s.SetResourceInstanceCurrent(
|
||||||
|
@ -95,103 +151,198 @@ func TestShow_aliasedProvider(t *testing.T) {
|
||||||
defer os.RemoveAll(stateDir)
|
defer os.RemoveAll(stateDir)
|
||||||
defer testChdir(t, stateDir)()
|
defer testChdir(t, stateDir)()
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
view, done := testView(t)
|
||||||
view, _ := testView(t)
|
|
||||||
c := &ShowCommand{
|
c := &ShowCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||||
Ui: ui,
|
|
||||||
View: view,
|
View: view,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// the statefile created by testStateFile is named state.tfstate
|
path := filepath.Base(statePath)
|
||||||
args := []string{"state.tfstate"}
|
args := []string{
|
||||||
if code := c.Run(args); code != 0 {
|
path,
|
||||||
t.Fatalf("bad exit code: \n%s", ui.OutputWriter.String())
|
"-no-color",
|
||||||
|
}
|
||||||
|
code := c.Run(args)
|
||||||
|
output := done(t)
|
||||||
|
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr())
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(ui.OutputWriter.String(), "# missing schema for provider \"test.alias\"") {
|
got := output.Stdout()
|
||||||
t.Fatalf("bad output: \n%s", ui.OutputWriter.String())
|
want := `# missing schema for provider \"test.alias\"`
|
||||||
|
if strings.Contains(got, want) {
|
||||||
|
t.Fatalf("unexpected output\ngot: %s", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShow_noArgsNoState(t *testing.T) {
|
func TestShow_argsPlanFileDoesNotExist(t *testing.T) {
|
||||||
// Create the default state
|
view, done := testView(t)
|
||||||
statePath := testStateFile(t, testState())
|
|
||||||
stateDir := filepath.Dir(statePath)
|
|
||||||
defer os.RemoveAll(stateDir)
|
|
||||||
defer testChdir(t, stateDir)()
|
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
|
||||||
view, _ := testView(t)
|
|
||||||
c := &ShowCommand{
|
c := &ShowCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||||
Ui: ui,
|
|
||||||
View: view,
|
View: view,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// the statefile created by testStateFile is named state.tfstate
|
args := []string{
|
||||||
args := []string{"state.tfstate"}
|
"doesNotExist.tfplan",
|
||||||
if code := c.Run(args); code != 0 {
|
"-no-color",
|
||||||
t.Fatalf("bad: \n%s", ui.OutputWriter.String())
|
}
|
||||||
|
code := c.Run(args)
|
||||||
|
output := done(t)
|
||||||
|
|
||||||
|
if code != 1 {
|
||||||
|
t.Fatalf("unexpected exit status %d; want 1\ngot: %s", code, output.Stdout())
|
||||||
|
}
|
||||||
|
|
||||||
|
got := output.Stderr()
|
||||||
|
want := `Plan read error: open doesNotExist.tfplan:`
|
||||||
|
if !strings.Contains(got, want) {
|
||||||
|
t.Errorf("unexpected output\ngot: %s\nwant:\n%s", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShow_argsStatefileDoesNotExist(t *testing.T) {
|
||||||
|
view, done := testView(t)
|
||||||
|
c := &ShowCommand{
|
||||||
|
Meta: Meta{
|
||||||
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||||
|
View: view,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"doesNotExist.tfstate",
|
||||||
|
"-no-color",
|
||||||
|
}
|
||||||
|
code := c.Run(args)
|
||||||
|
output := done(t)
|
||||||
|
|
||||||
|
if code != 1 {
|
||||||
|
t.Fatalf("unexpected exit status %d; want 1\ngot: %s", code, output.Stdout())
|
||||||
|
}
|
||||||
|
|
||||||
|
got := output.Stderr()
|
||||||
|
want := `State read error: Error loading statefile:`
|
||||||
|
if !strings.Contains(got, want) {
|
||||||
|
t.Errorf("unexpected output\ngot: %s\nwant:\n%s", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShow_json_argsPlanFileDoesNotExist(t *testing.T) {
|
||||||
|
view, done := testView(t)
|
||||||
|
c := &ShowCommand{
|
||||||
|
Meta: Meta{
|
||||||
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||||
|
View: view,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"-json",
|
||||||
|
"doesNotExist.tfplan",
|
||||||
|
"-no-color",
|
||||||
|
}
|
||||||
|
code := c.Run(args)
|
||||||
|
output := done(t)
|
||||||
|
|
||||||
|
if code != 1 {
|
||||||
|
t.Fatalf("unexpected exit status %d; want 1\ngot: %s", code, output.Stdout())
|
||||||
|
}
|
||||||
|
|
||||||
|
got := output.Stderr()
|
||||||
|
want := `Plan read error: open doesNotExist.tfplan:`
|
||||||
|
if !strings.Contains(got, want) {
|
||||||
|
t.Errorf("unexpected output\ngot: %s\nwant:\n%s", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShow_json_argsStatefileDoesNotExist(t *testing.T) {
|
||||||
|
view, done := testView(t)
|
||||||
|
c := &ShowCommand{
|
||||||
|
Meta: Meta{
|
||||||
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||||
|
View: view,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"-json",
|
||||||
|
"doesNotExist.tfstate",
|
||||||
|
"-no-color",
|
||||||
|
}
|
||||||
|
code := c.Run(args)
|
||||||
|
output := done(t)
|
||||||
|
|
||||||
|
if code != 1 {
|
||||||
|
t.Fatalf("unexpected exit status %d; want 1\ngot: %s", code, output.Stdout())
|
||||||
|
}
|
||||||
|
|
||||||
|
got := output.Stderr()
|
||||||
|
want := `State read error: Error loading statefile:`
|
||||||
|
if !strings.Contains(got, want) {
|
||||||
|
t.Errorf("unexpected output\ngot: %s\nwant:\n%s", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShow_planNoop(t *testing.T) {
|
func TestShow_planNoop(t *testing.T) {
|
||||||
planPath := testPlanFileNoop(t)
|
planPath := testPlanFileNoop(t)
|
||||||
|
|
||||||
ui := cli.NewMockUi()
|
|
||||||
view, done := testView(t)
|
view, done := testView(t)
|
||||||
c := &ShowCommand{
|
c := &ShowCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||||
Ui: ui,
|
|
||||||
View: view,
|
View: view,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
planPath,
|
planPath,
|
||||||
|
"-no-color",
|
||||||
}
|
}
|
||||||
if code := c.Run(args); code != 0 {
|
code := c.Run(args)
|
||||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
output := done(t)
|
||||||
|
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
got := output.Stdout()
|
||||||
want := `No changes. Your infrastructure matches the configuration.`
|
want := `No changes. Your infrastructure matches the configuration.`
|
||||||
got := done(t).Stdout()
|
|
||||||
if !strings.Contains(got, want) {
|
if !strings.Contains(got, want) {
|
||||||
t.Errorf("missing expected output\nwant: %s\ngot:\n%s", want, got)
|
t.Errorf("unexpected output\ngot: %s\nwant:\n%s", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShow_planWithChanges(t *testing.T) {
|
func TestShow_planWithChanges(t *testing.T) {
|
||||||
planPathWithChanges := showFixturePlanFile(t, plans.DeleteThenCreate)
|
planPathWithChanges := showFixturePlanFile(t, plans.DeleteThenCreate)
|
||||||
|
|
||||||
ui := cli.NewMockUi()
|
|
||||||
view, done := testView(t)
|
view, done := testView(t)
|
||||||
c := &ShowCommand{
|
c := &ShowCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
testingOverrides: metaOverridesForProvider(showFixtureProvider()),
|
testingOverrides: metaOverridesForProvider(showFixtureProvider()),
|
||||||
Ui: ui,
|
|
||||||
View: view,
|
View: view,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
planPathWithChanges,
|
planPathWithChanges,
|
||||||
|
"-no-color",
|
||||||
|
}
|
||||||
|
code := c.Run(args)
|
||||||
|
output := done(t)
|
||||||
|
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr())
|
||||||
}
|
}
|
||||||
|
|
||||||
if code := c.Run(args); code != 0 {
|
got := output.Stdout()
|
||||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
want := `test_instance.foo must be replaced`
|
want := `test_instance.foo must be replaced`
|
||||||
got := done(t).Stdout()
|
|
||||||
if !strings.Contains(got, want) {
|
if !strings.Contains(got, want) {
|
||||||
t.Errorf("missing expected output\nwant: %s\ngot:\n%s", want, got)
|
t.Fatalf("unexpected output\ngot: %s\nwant: %s", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,30 +390,34 @@ func TestShow_planWithForceReplaceChange(t *testing.T) {
|
||||||
plan,
|
plan,
|
||||||
)
|
)
|
||||||
|
|
||||||
ui := cli.NewMockUi()
|
|
||||||
view, done := testView(t)
|
view, done := testView(t)
|
||||||
c := &ShowCommand{
|
c := &ShowCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
testingOverrides: metaOverridesForProvider(showFixtureProvider()),
|
testingOverrides: metaOverridesForProvider(showFixtureProvider()),
|
||||||
Ui: ui,
|
|
||||||
View: view,
|
View: view,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
planFilePath,
|
planFilePath,
|
||||||
|
"-no-color",
|
||||||
|
}
|
||||||
|
code := c.Run(args)
|
||||||
|
output := done(t)
|
||||||
|
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr())
|
||||||
}
|
}
|
||||||
|
|
||||||
if code := c.Run(args); code != 0 {
|
got := output.Stdout()
|
||||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
want := `test_instance.foo will be replaced, as requested`
|
||||||
|
if !strings.Contains(got, want) {
|
||||||
|
t.Fatalf("unexpected output\ngot: %s\nwant: %s", got, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
got := done(t).Stdout()
|
want = `Plan: 1 to add, 0 to change, 1 to destroy.`
|
||||||
if want := `test_instance.foo will be replaced, as requested`; !strings.Contains(got, want) {
|
if !strings.Contains(got, want) {
|
||||||
t.Errorf("wrong output\ngot:\n%s\n\nwant substring: %s", got, want)
|
t.Fatalf("unexpected output\ngot: %s\nwant: %s", got, want)
|
||||||
}
|
|
||||||
if want := `Plan: 1 to add, 0 to change, 1 to destroy.`; !strings.Contains(got, want) {
|
|
||||||
t.Errorf("wrong output\ngot:\n%s\n\nwant substring: %s", got, want)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -270,12 +425,10 @@ func TestShow_planWithForceReplaceChange(t *testing.T) {
|
||||||
func TestShow_plan_json(t *testing.T) {
|
func TestShow_plan_json(t *testing.T) {
|
||||||
planPath := showFixturePlanFile(t, plans.Create)
|
planPath := showFixturePlanFile(t, plans.Create)
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
view, done := testView(t)
|
||||||
view, _ := testView(t)
|
|
||||||
c := &ShowCommand{
|
c := &ShowCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
testingOverrides: metaOverridesForProvider(showFixtureProvider()),
|
testingOverrides: metaOverridesForProvider(showFixtureProvider()),
|
||||||
Ui: ui,
|
|
||||||
View: view,
|
View: view,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -283,9 +436,13 @@ func TestShow_plan_json(t *testing.T) {
|
||||||
args := []string{
|
args := []string{
|
||||||
"-json",
|
"-json",
|
||||||
planPath,
|
planPath,
|
||||||
|
"-no-color",
|
||||||
}
|
}
|
||||||
if code := c.Run(args); code != 0 {
|
code := c.Run(args)
|
||||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
output := done(t)
|
||||||
|
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -294,21 +451,23 @@ func TestShow_state(t *testing.T) {
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
defer os.RemoveAll(filepath.Dir(statePath))
|
defer os.RemoveAll(filepath.Dir(statePath))
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
view, done := testView(t)
|
||||||
view, _ := testView(t)
|
|
||||||
c := &ShowCommand{
|
c := &ShowCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||||
Ui: ui,
|
|
||||||
View: view,
|
View: view,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
statePath,
|
statePath,
|
||||||
|
"-no-color",
|
||||||
}
|
}
|
||||||
if code := c.Run(args); code != 0 {
|
code := c.Run(args)
|
||||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
output := done(t)
|
||||||
|
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -339,18 +498,15 @@ func TestShow_json_output(t *testing.T) {
|
||||||
defer close()
|
defer close()
|
||||||
|
|
||||||
p := showFixtureProvider()
|
p := showFixtureProvider()
|
||||||
ui := new(cli.MockUi)
|
|
||||||
view, _ := testView(t)
|
|
||||||
m := Meta{
|
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
|
||||||
Ui: ui,
|
|
||||||
View: view,
|
|
||||||
ProviderSource: providerSource,
|
|
||||||
}
|
|
||||||
|
|
||||||
// init
|
// init
|
||||||
|
ui := new(cli.MockUi)
|
||||||
ic := &InitCommand{
|
ic := &InitCommand{
|
||||||
Meta: m,
|
Meta: Meta{
|
||||||
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
Ui: ui,
|
||||||
|
ProviderSource: providerSource,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if code := ic.Run([]string{}); code != 0 {
|
if code := ic.Run([]string{}); code != 0 {
|
||||||
if expectError {
|
if expectError {
|
||||||
|
@ -360,22 +516,35 @@ func TestShow_json_output(t *testing.T) {
|
||||||
t.Fatalf("init failed\n%s", ui.ErrorWriter)
|
t.Fatalf("init failed\n%s", ui.ErrorWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// plan
|
||||||
|
planView, planDone := testView(t)
|
||||||
pc := &PlanCommand{
|
pc := &PlanCommand{
|
||||||
Meta: m,
|
Meta: Meta{
|
||||||
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
View: planView,
|
||||||
|
ProviderSource: providerSource,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
"-out=terraform.plan",
|
"-out=terraform.plan",
|
||||||
}
|
}
|
||||||
|
|
||||||
if code := pc.Run(args); code != 0 {
|
code := pc.Run(args)
|
||||||
t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String())
|
planOutput := planDone(t)
|
||||||
|
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, planOutput.Stderr())
|
||||||
}
|
}
|
||||||
|
|
||||||
// flush the plan output from the mock ui
|
// show
|
||||||
ui.OutputWriter.Reset()
|
showView, showDone := testView(t)
|
||||||
sc := &ShowCommand{
|
sc := &ShowCommand{
|
||||||
Meta: m,
|
Meta: Meta{
|
||||||
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
View: showView,
|
||||||
|
ProviderSource: providerSource,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
args = []string{
|
args = []string{
|
||||||
|
@ -383,25 +552,27 @@ func TestShow_json_output(t *testing.T) {
|
||||||
"terraform.plan",
|
"terraform.plan",
|
||||||
}
|
}
|
||||||
defer os.Remove("terraform.plan")
|
defer os.Remove("terraform.plan")
|
||||||
|
code = sc.Run(args)
|
||||||
|
showOutput := showDone(t)
|
||||||
|
|
||||||
if code := sc.Run(args); code != 0 {
|
if code != 0 {
|
||||||
t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String())
|
t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, showOutput.Stderr())
|
||||||
}
|
}
|
||||||
|
|
||||||
// compare ui output to wanted output
|
// compare view output to wanted output
|
||||||
var got, want plan
|
var got, want plan
|
||||||
|
|
||||||
gotString := ui.OutputWriter.String()
|
gotString := showOutput.Stdout()
|
||||||
json.Unmarshal([]byte(gotString), &got)
|
json.Unmarshal([]byte(gotString), &got)
|
||||||
|
|
||||||
wantFile, err := os.Open("output.json")
|
wantFile, err := os.Open("output.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("unexpected err: %s", err)
|
||||||
}
|
}
|
||||||
defer wantFile.Close()
|
defer wantFile.Close()
|
||||||
byteValue, err := ioutil.ReadAll(wantFile)
|
byteValue, err := ioutil.ReadAll(wantFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("unexpected err: %s", err)
|
||||||
}
|
}
|
||||||
json.Unmarshal([]byte(byteValue), &want)
|
json.Unmarshal([]byte(byteValue), &want)
|
||||||
|
|
||||||
|
@ -423,43 +594,48 @@ func TestShow_json_output_sensitive(t *testing.T) {
|
||||||
defer close()
|
defer close()
|
||||||
|
|
||||||
p := showFixtureSensitiveProvider()
|
p := showFixtureSensitiveProvider()
|
||||||
ui := new(cli.MockUi)
|
|
||||||
view, _ := testView(t)
|
|
||||||
m := Meta{
|
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
|
||||||
Ui: ui,
|
|
||||||
View: view,
|
|
||||||
ProviderSource: providerSource,
|
|
||||||
}
|
|
||||||
|
|
||||||
// init
|
// init
|
||||||
|
ui := new(cli.MockUi)
|
||||||
ic := &InitCommand{
|
ic := &InitCommand{
|
||||||
Meta: m,
|
Meta: Meta{
|
||||||
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
Ui: ui,
|
||||||
|
ProviderSource: providerSource,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if code := ic.Run([]string{}); code != 0 {
|
if code := ic.Run([]string{}); code != 0 {
|
||||||
t.Fatalf("init failed\n%s", ui.ErrorWriter)
|
t.Fatalf("init failed\n%s", ui.ErrorWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// flush init output
|
// plan
|
||||||
ui.OutputWriter.Reset()
|
planView, planDone := testView(t)
|
||||||
|
|
||||||
pc := &PlanCommand{
|
pc := &PlanCommand{
|
||||||
Meta: m,
|
Meta: Meta{
|
||||||
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
View: planView,
|
||||||
|
ProviderSource: providerSource,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
"-out=terraform.plan",
|
"-out=terraform.plan",
|
||||||
}
|
}
|
||||||
|
code := pc.Run(args)
|
||||||
|
planOutput := planDone(t)
|
||||||
|
|
||||||
if code := pc.Run(args); code != 0 {
|
if code != 0 {
|
||||||
fmt.Println(ui.OutputWriter.String())
|
t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, planOutput.Stderr())
|
||||||
t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// flush the plan output from the mock ui
|
// show
|
||||||
ui.OutputWriter.Reset()
|
showView, showDone := testView(t)
|
||||||
sc := &ShowCommand{
|
sc := &ShowCommand{
|
||||||
Meta: m,
|
Meta: Meta{
|
||||||
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
View: showView,
|
||||||
|
ProviderSource: providerSource,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
args = []string{
|
args = []string{
|
||||||
|
@ -467,25 +643,27 @@ func TestShow_json_output_sensitive(t *testing.T) {
|
||||||
"terraform.plan",
|
"terraform.plan",
|
||||||
}
|
}
|
||||||
defer os.Remove("terraform.plan")
|
defer os.Remove("terraform.plan")
|
||||||
|
code = sc.Run(args)
|
||||||
|
showOutput := showDone(t)
|
||||||
|
|
||||||
if code := sc.Run(args); code != 0 {
|
if code != 0 {
|
||||||
t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String())
|
t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, showOutput.Stderr())
|
||||||
}
|
}
|
||||||
|
|
||||||
// compare ui output to wanted output
|
// compare ui output to wanted output
|
||||||
var got, want plan
|
var got, want plan
|
||||||
|
|
||||||
gotString := ui.OutputWriter.String()
|
gotString := showOutput.Stdout()
|
||||||
json.Unmarshal([]byte(gotString), &got)
|
json.Unmarshal([]byte(gotString), &got)
|
||||||
|
|
||||||
wantFile, err := os.Open("output.json")
|
wantFile, err := os.Open("output.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("unexpected err: %s", err)
|
||||||
}
|
}
|
||||||
defer wantFile.Close()
|
defer wantFile.Close()
|
||||||
byteValue, err := ioutil.ReadAll(wantFile)
|
byteValue, err := ioutil.ReadAll(wantFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("unexpected err: %s", err)
|
||||||
}
|
}
|
||||||
json.Unmarshal([]byte(byteValue), &want)
|
json.Unmarshal([]byte(byteValue), &want)
|
||||||
|
|
||||||
|
@ -520,31 +698,35 @@ func TestShow_json_output_state(t *testing.T) {
|
||||||
defer close()
|
defer close()
|
||||||
|
|
||||||
p := showFixtureProvider()
|
p := showFixtureProvider()
|
||||||
ui := new(cli.MockUi)
|
|
||||||
view, _ := testView(t)
|
|
||||||
m := Meta{
|
|
||||||
testingOverrides: metaOverridesForProvider(p),
|
|
||||||
Ui: ui,
|
|
||||||
View: view,
|
|
||||||
ProviderSource: providerSource,
|
|
||||||
}
|
|
||||||
|
|
||||||
// init
|
// init
|
||||||
|
ui := new(cli.MockUi)
|
||||||
ic := &InitCommand{
|
ic := &InitCommand{
|
||||||
Meta: m,
|
Meta: Meta{
|
||||||
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
Ui: ui,
|
||||||
|
ProviderSource: providerSource,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if code := ic.Run([]string{}); code != 0 {
|
if code := ic.Run([]string{}); code != 0 {
|
||||||
t.Fatalf("init failed\n%s", ui.ErrorWriter)
|
t.Fatalf("init failed\n%s", ui.ErrorWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// flush the plan output from the mock ui
|
// show
|
||||||
ui.OutputWriter.Reset()
|
showView, showDone := testView(t)
|
||||||
sc := &ShowCommand{
|
sc := &ShowCommand{
|
||||||
Meta: m,
|
Meta: Meta{
|
||||||
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
View: showView,
|
||||||
|
ProviderSource: providerSource,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if code := sc.Run([]string{"-json"}); code != 0 {
|
code := sc.Run([]string{"-json"})
|
||||||
t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String())
|
showOutput := showDone(t)
|
||||||
|
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, showOutput.Stderr())
|
||||||
}
|
}
|
||||||
|
|
||||||
// compare ui output to wanted output
|
// compare ui output to wanted output
|
||||||
|
@ -556,17 +738,17 @@ func TestShow_json_output_state(t *testing.T) {
|
||||||
}
|
}
|
||||||
var got, want state
|
var got, want state
|
||||||
|
|
||||||
gotString := ui.OutputWriter.String()
|
gotString := showOutput.Stdout()
|
||||||
json.Unmarshal([]byte(gotString), &got)
|
json.Unmarshal([]byte(gotString), &got)
|
||||||
|
|
||||||
wantFile, err := os.Open("output.json")
|
wantFile, err := os.Open("output.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("unexpected error: %s", err)
|
||||||
}
|
}
|
||||||
defer wantFile.Close()
|
defer wantFile.Close()
|
||||||
byteValue, err := ioutil.ReadAll(wantFile)
|
byteValue, err := ioutil.ReadAll(wantFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("unexpected err: %s", err)
|
||||||
}
|
}
|
||||||
json.Unmarshal([]byte(byteValue), &want)
|
json.Unmarshal([]byte(byteValue), &want)
|
||||||
|
|
||||||
|
@ -599,27 +781,29 @@ func TestShow_planWithNonDefaultStateLineage(t *testing.T) {
|
||||||
}
|
}
|
||||||
planPath := testPlanFileMatchState(t, snap, state, plan, stateMeta)
|
planPath := testPlanFileMatchState(t, snap, state, plan, stateMeta)
|
||||||
|
|
||||||
ui := cli.NewMockUi()
|
|
||||||
view, done := testView(t)
|
view, done := testView(t)
|
||||||
c := &ShowCommand{
|
c := &ShowCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||||
Ui: ui,
|
|
||||||
View: view,
|
View: view,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
planPath,
|
planPath,
|
||||||
|
"-no-color",
|
||||||
}
|
}
|
||||||
if code := c.Run(args); code != 0 {
|
code := c.Run(args)
|
||||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
output := done(t)
|
||||||
|
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
got := output.Stdout()
|
||||||
want := `No changes. Your infrastructure matches the configuration.`
|
want := `No changes. Your infrastructure matches the configuration.`
|
||||||
got := done(t).Stdout()
|
|
||||||
if !strings.Contains(got, want) {
|
if !strings.Contains(got, want) {
|
||||||
t.Errorf("missing expected output\nwant: %s\ngot:\n%s", want, got)
|
t.Fatalf("unexpected output\ngot: %s\nwant: %s", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"format_version": "1.0"
|
||||||
|
}
|
|
@ -2,37 +2,95 @@ package views
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/internal/command/arguments"
|
"github.com/hashicorp/terraform/internal/command/arguments"
|
||||||
|
"github.com/hashicorp/terraform/internal/command/format"
|
||||||
|
"github.com/hashicorp/terraform/internal/command/jsonplan"
|
||||||
|
"github.com/hashicorp/terraform/internal/command/jsonstate"
|
||||||
|
"github.com/hashicorp/terraform/internal/configs"
|
||||||
"github.com/hashicorp/terraform/internal/plans"
|
"github.com/hashicorp/terraform/internal/plans"
|
||||||
|
"github.com/hashicorp/terraform/internal/states/statefile"
|
||||||
"github.com/hashicorp/terraform/internal/terraform"
|
"github.com/hashicorp/terraform/internal/terraform"
|
||||||
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FIXME: this is a temporary partial definition of the view for the show
|
|
||||||
// command, in place to allow access to the plan renderer which is now in the
|
|
||||||
// views package.
|
|
||||||
type Show interface {
|
type Show interface {
|
||||||
Plan(plan *plans.Plan, schemas *terraform.Schemas)
|
// Display renders the plan, if it is available. If plan is nil, it renders the statefile.
|
||||||
|
Display(config *configs.Config, plan *plans.Plan, stateFile *statefile.File, schemas *terraform.Schemas) int
|
||||||
|
|
||||||
|
// Diagnostics renders early diagnostics, resulting from argument parsing.
|
||||||
|
Diagnostics(diags tfdiags.Diagnostics)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: the show view should support both human and JSON types. This code is
|
|
||||||
// currently only used to render the plan in human-readable UI, so does not yet
|
|
||||||
// support JSON.
|
|
||||||
func NewShow(vt arguments.ViewType, view *View) Show {
|
func NewShow(vt arguments.ViewType, view *View) Show {
|
||||||
switch vt {
|
switch vt {
|
||||||
|
case arguments.ViewJSON:
|
||||||
|
return &ShowJSON{view: view}
|
||||||
case arguments.ViewHuman:
|
case arguments.ViewHuman:
|
||||||
return &ShowHuman{View: *view}
|
return &ShowHuman{view: view}
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("unknown view type %v", vt))
|
panic(fmt.Sprintf("unknown view type %v", vt))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShowHuman struct {
|
type ShowHuman struct {
|
||||||
View
|
view *View
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Show = (*ShowHuman)(nil)
|
var _ Show = (*ShowHuman)(nil)
|
||||||
|
|
||||||
func (v *ShowHuman) Plan(plan *plans.Plan, schemas *terraform.Schemas) {
|
func (v *ShowHuman) Display(config *configs.Config, plan *plans.Plan, stateFile *statefile.File, schemas *terraform.Schemas) int {
|
||||||
renderPlan(plan, schemas, &v.View)
|
if plan != nil {
|
||||||
|
renderPlan(plan, schemas, v.view)
|
||||||
|
} else {
|
||||||
|
if stateFile == nil {
|
||||||
|
v.view.streams.Println("No state.")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
v.view.streams.Println(format.State(&format.StateOpts{
|
||||||
|
State: stateFile.State,
|
||||||
|
Color: v.view.colorize,
|
||||||
|
Schemas: schemas,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *ShowHuman) Diagnostics(diags tfdiags.Diagnostics) {
|
||||||
|
v.view.Diagnostics(diags)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ShowJSON struct {
|
||||||
|
view *View
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Show = (*ShowJSON)(nil)
|
||||||
|
|
||||||
|
func (v *ShowJSON) Display(config *configs.Config, plan *plans.Plan, stateFile *statefile.File, schemas *terraform.Schemas) int {
|
||||||
|
if plan != nil {
|
||||||
|
jsonPlan, err := jsonplan.Marshal(config, plan, stateFile, schemas)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
v.view.streams.Eprintf("Failed to marshal plan to json: %s", err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
v.view.streams.Println(string(jsonPlan))
|
||||||
|
} else {
|
||||||
|
// It is possible that there is neither state nor a plan.
|
||||||
|
// That's ok, we'll just return an empty object.
|
||||||
|
jsonState, err := jsonstate.Marshal(stateFile, schemas)
|
||||||
|
if err != nil {
|
||||||
|
v.view.streams.Eprintf("Failed to marshal state to json: %s", err)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
v.view.streams.Println(string(jsonState))
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diagnostics should only be called if show cannot be executed.
|
||||||
|
// In this case, we choose to render human-readable diagnostic output,
|
||||||
|
// primarily for backwards compatibility.
|
||||||
|
func (v *ShowJSON) Diagnostics(diags tfdiags.Diagnostics) {
|
||||||
|
v.view.Diagnostics(diags)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,184 @@
|
||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/internal/addrs"
|
||||||
|
"github.com/hashicorp/terraform/internal/command/arguments"
|
||||||
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||||
|
"github.com/hashicorp/terraform/internal/initwd"
|
||||||
|
"github.com/hashicorp/terraform/internal/plans"
|
||||||
|
"github.com/hashicorp/terraform/internal/states"
|
||||||
|
"github.com/hashicorp/terraform/internal/states/statefile"
|
||||||
|
"github.com/hashicorp/terraform/internal/terminal"
|
||||||
|
"github.com/hashicorp/terraform/internal/terraform"
|
||||||
|
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShowHuman(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
plan *plans.Plan
|
||||||
|
stateFile *statefile.File
|
||||||
|
schemas *terraform.Schemas
|
||||||
|
wantExact bool
|
||||||
|
wantString string
|
||||||
|
}{
|
||||||
|
"plan file": {
|
||||||
|
testPlan(t),
|
||||||
|
nil,
|
||||||
|
testSchemas(),
|
||||||
|
false,
|
||||||
|
"# test_resource.foo will be created",
|
||||||
|
},
|
||||||
|
"statefile": {
|
||||||
|
nil,
|
||||||
|
&statefile.File{
|
||||||
|
Serial: 0,
|
||||||
|
Lineage: "fake-for-testing",
|
||||||
|
State: testState(),
|
||||||
|
},
|
||||||
|
testSchemas(),
|
||||||
|
false,
|
||||||
|
"# test_resource.foo:",
|
||||||
|
},
|
||||||
|
"empty statefile": {
|
||||||
|
nil,
|
||||||
|
&statefile.File{
|
||||||
|
Serial: 0,
|
||||||
|
Lineage: "fake-for-testing",
|
||||||
|
State: states.NewState(),
|
||||||
|
},
|
||||||
|
testSchemas(),
|
||||||
|
true,
|
||||||
|
"\n",
|
||||||
|
},
|
||||||
|
"nothing": {
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
"No state.\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
streams, done := terminal.StreamsForTesting(t)
|
||||||
|
view := NewView(streams)
|
||||||
|
view.Configure(&arguments.View{NoColor: true})
|
||||||
|
v := NewShow(arguments.ViewHuman, view)
|
||||||
|
|
||||||
|
code := v.Display(nil, testCase.plan, testCase.stateFile, testCase.schemas)
|
||||||
|
if code != 0 {
|
||||||
|
t.Errorf("expected 0 return code, got %d", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
output := done(t)
|
||||||
|
got := output.Stdout()
|
||||||
|
want := testCase.wantString
|
||||||
|
if (testCase.wantExact && got != want) || (!testCase.wantExact && !strings.Contains(got, want)) {
|
||||||
|
t.Fatalf("unexpected output\ngot: %s\nwant: %s", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShowJSON(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
plan *plans.Plan
|
||||||
|
stateFile *statefile.File
|
||||||
|
}{
|
||||||
|
"plan file": {
|
||||||
|
testPlan(t),
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"statefile": {
|
||||||
|
nil,
|
||||||
|
&statefile.File{
|
||||||
|
Serial: 0,
|
||||||
|
Lineage: "fake-for-testing",
|
||||||
|
State: testState(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"empty statefile": {
|
||||||
|
nil,
|
||||||
|
&statefile.File{
|
||||||
|
Serial: 0,
|
||||||
|
Lineage: "fake-for-testing",
|
||||||
|
State: states.NewState(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"nothing": {
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
config, _, configCleanup := initwd.MustLoadConfigForTests(t, "./testdata/show")
|
||||||
|
defer configCleanup()
|
||||||
|
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
streams, done := terminal.StreamsForTesting(t)
|
||||||
|
view := NewView(streams)
|
||||||
|
view.Configure(&arguments.View{NoColor: true})
|
||||||
|
v := NewShow(arguments.ViewJSON, view)
|
||||||
|
|
||||||
|
schemas := &terraform.Schemas{
|
||||||
|
Providers: map[addrs.Provider]*terraform.ProviderSchema{
|
||||||
|
addrs.NewDefaultProvider("test"): {
|
||||||
|
ResourceTypes: map[string]*configschema.Block{
|
||||||
|
"test_resource": {
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
||||||
|
"foo": {Type: cty.String, Optional: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
code := v.Display(config, testCase.plan, testCase.stateFile, schemas)
|
||||||
|
|
||||||
|
if code != 0 {
|
||||||
|
t.Errorf("expected 0 return code, got %d", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the result looks like JSON; we comprehensively test
|
||||||
|
// the structure of this output in the command package tests.
|
||||||
|
var result map[string]interface{}
|
||||||
|
got := done(t).All()
|
||||||
|
t.Logf("output: %s", got)
|
||||||
|
if err := json.Unmarshal([]byte(got), &result); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// testState returns a test State structure.
|
||||||
|
func testState() *states.State {
|
||||||
|
return states.BuildState(func(s *states.SyncState) {
|
||||||
|
s.SetResourceInstanceCurrent(
|
||||||
|
addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "test_resource",
|
||||||
|
Name: "foo",
|
||||||
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
|
&states.ResourceInstanceObjectSrc{
|
||||||
|
AttrsJSON: []byte(`{"id":"bar","foo":"value"}`),
|
||||||
|
Status: states.ObjectReady,
|
||||||
|
},
|
||||||
|
addrs.AbsProviderConfig{
|
||||||
|
Provider: addrs.NewDefaultProvider("test"),
|
||||||
|
Module: addrs.RootModule,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
// DeepCopy is used here to ensure our synthetic state matches exactly
|
||||||
|
// with a state that will have been copied during the command
|
||||||
|
// operation, and all fields have been copied correctly.
|
||||||
|
}).DeepCopy()
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
resource "test_resource" "foo" {
|
||||||
|
foo = "value"
|
||||||
|
}
|
Loading…
Reference in New Issue