Migrate 'state show' command to new renderer (#33116)

* Migrate 'state show' command to new renderer

* handle error
pull/33457/head
Liam Cervante 3 years ago committed by GitHub
parent f0b3b74f7c
commit b5576159da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,29 +0,0 @@
// Code generated by "stringer -type=DiffLanguage diff.go"; DO NOT EDIT.
package format
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[DiffLanguageProposedChange-80]
_ = x[DiffLanguageDetectedDrift-68]
}
const (
_DiffLanguage_name_0 = "DiffLanguageDetectedDrift"
_DiffLanguage_name_1 = "DiffLanguageProposedChange"
)
func (i DiffLanguage) String() string {
switch {
case i == 68:
return _DiffLanguage_name_0
case i == 80:
return _DiffLanguage_name_1
default:
return "DiffLanguage(" + strconv.FormatInt(int64(i), 10) + ")"
}
}

@ -6,3 +6,30 @@
// output formatting as much as possible so that text formats of Terraform
// structures have a consistent look and feel.
package format
import "github.com/hashicorp/terraform/internal/plans"
// DiffActionSymbol returns a string that, once passed through a
// colorstring.Colorize, will produce a result that can be written
// to a terminal to produce a symbol made of three printable
// characters, possibly interspersed with VT100 color codes.
func DiffActionSymbol(action plans.Action) string {
switch action {
case plans.DeleteThenCreate:
return "[red]-[reset]/[green]+[reset]"
case plans.CreateThenDelete:
return "[green]+[reset]/[red]-[reset]"
case plans.Create:
return " [green]+[reset]"
case plans.Delete:
return " [red]-[reset]"
case plans.Read:
return " [cyan]<=[reset]"
case plans.Update:
return " [yellow]~[reset]"
case plans.NoOp:
return " "
default:
return " ?"
}
}

@ -1,216 +0,0 @@
package format
import (
"bytes"
"fmt"
"sort"
"strings"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/terraform"
"github.com/mitchellh/colorstring"
)
// StateOpts are the options for formatting a state.
type StateOpts struct {
// State is the state to format. This is required.
State *states.State
// Schemas are used to decode attributes. This is required.
Schemas *terraform.Schemas
// Color is the colorizer. This is optional.
Color *colorstring.Colorize
}
// State takes a state and returns a string
func State(opts *StateOpts) string {
if opts.Color == nil {
panic("colorize not given")
}
if opts.Schemas == nil {
panic("schemas not given")
}
s := opts.State
if len(s.Modules) == 0 {
return "The state file is empty. No resources are represented."
}
buf := bytes.NewBufferString("[reset]")
p := blockBodyDiffPrinter{
buf: buf,
color: opts.Color,
action: plans.NoOp,
verbose: true,
}
// Format all the modules
for _, m := range s.Modules {
formatStateModule(p, m, opts.Schemas)
}
// Write the outputs for the root module
m := s.RootModule()
if m.OutputValues != nil {
if len(m.OutputValues) > 0 {
p.buf.WriteString("Outputs:\n\n")
}
// Sort the outputs
ks := make([]string, 0, len(m.OutputValues))
for k := range m.OutputValues {
ks = append(ks, k)
}
sort.Strings(ks)
// Output each output k/v pair
for _, k := range ks {
v := m.OutputValues[k]
p.buf.WriteString(fmt.Sprintf("%s = ", k))
if v.Sensitive {
p.buf.WriteString("(sensitive value)")
} else {
p.writeValue(v.Value, plans.NoOp, 0)
}
p.buf.WriteString("\n")
}
}
trimmedOutput := strings.TrimSpace(p.buf.String())
trimmedOutput += "[reset]"
return opts.Color.Color(trimmedOutput)
}
func formatStateModule(p blockBodyDiffPrinter, m *states.Module, schemas *terraform.Schemas) {
// First get the names of all the resources so we can show them
// in alphabetical order.
names := make([]string, 0, len(m.Resources))
for name := range m.Resources {
names = append(names, name)
}
sort.Strings(names)
// Go through each resource and begin building up the output.
for _, key := range names {
for k, v := range m.Resources[key].Instances {
// keep these in order to keep the current object first, and
// provide deterministic output for the deposed objects
type obj struct {
header string
instance *states.ResourceInstanceObjectSrc
}
instances := []obj{}
addr := m.Resources[key].Addr
resAddr := addr.Resource
taintStr := ""
if v.Current != nil && v.Current.Status == 'T' {
taintStr = " (tainted)"
}
instances = append(instances,
obj{fmt.Sprintf("# %s:%s\n", addr.Instance(k), taintStr), v.Current})
for dk, v := range v.Deposed {
instances = append(instances,
obj{fmt.Sprintf("# %s: (deposed object %s)\n", addr.Instance(k), dk), v})
}
// Sort the instances for consistent output.
// Starting the sort from the second index, so the current instance
// is always first.
sort.Slice(instances[1:], func(i, j int) bool {
return instances[i+1].header < instances[j+1].header
})
for _, obj := range instances {
header := obj.header
instance := obj.instance
p.buf.WriteString(header)
if instance == nil {
// this shouldn't happen, but there's nothing to do here so
// don't panic below.
continue
}
var schema *configschema.Block
provider := m.Resources[key].ProviderConfig.Provider
if _, exists := schemas.Providers[provider]; !exists {
// This should never happen in normal use because we should've
// loaded all of the schemas and checked things prior to this
// point. We can't return errors here, but since this is UI code
// we will try to do _something_ reasonable.
p.buf.WriteString(fmt.Sprintf("# missing schema for provider %q\n\n", provider.String()))
continue
}
switch resAddr.Mode {
case addrs.ManagedResourceMode:
schema, _ = schemas.ResourceTypeConfig(
provider,
resAddr.Mode,
resAddr.Type,
)
if schema == nil {
p.buf.WriteString(fmt.Sprintf(
"# missing schema for provider %q resource type %s\n\n", provider, resAddr.Type))
continue
}
p.buf.WriteString(fmt.Sprintf(
"resource %q %q {",
resAddr.Type,
resAddr.Name,
))
case addrs.DataResourceMode:
schema, _ = schemas.ResourceTypeConfig(
provider,
resAddr.Mode,
resAddr.Type,
)
if schema == nil {
p.buf.WriteString(fmt.Sprintf(
"# missing schema for provider %q data source %s\n\n", provider, resAddr.Type))
continue
}
p.buf.WriteString(fmt.Sprintf(
"data %q %q {",
resAddr.Type,
resAddr.Name,
))
default:
// should never happen, since the above is exhaustive
p.buf.WriteString(resAddr.String())
}
val, err := instance.Decode(schema.ImpliedType())
if err != nil {
fmt.Println(err.Error())
break
}
path := make(cty.Path, 0, 3)
result := p.writeBlockBodyDiff(schema, val.Value, val.Value, 2, path)
if result.bodyWritten {
p.buf.WriteString("\n")
}
p.buf.WriteString("}\n\n")
}
}
}
p.buf.WriteString("\n")
}

@ -1,400 +0,0 @@
package format
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/terraform"
"github.com/zclconf/go-cty/cty"
)
func TestState(t *testing.T) {
tests := []struct {
State *StateOpts
Want string
}{
{
&StateOpts{
State: &states.State{},
Color: disabledColorize,
Schemas: &terraform.Schemas{},
},
"The state file is empty. No resources are represented.",
},
{
&StateOpts{
State: basicState(t),
Color: disabledColorize,
Schemas: testSchemas(),
},
basicStateOutput,
},
{
&StateOpts{
State: nestedState(t),
Color: disabledColorize,
Schemas: testSchemas(),
},
nestedStateOutput,
},
{
&StateOpts{
State: deposedState(t),
Color: disabledColorize,
Schemas: testSchemas(),
},
deposedNestedStateOutput,
},
{
&StateOpts{
State: onlyDeposedState(t),
Color: disabledColorize,
Schemas: testSchemas(),
},
onlyDeposedOutput,
},
{
&StateOpts{
State: stateWithMoreOutputs(t),
Color: disabledColorize,
Schemas: testSchemas(),
},
stateWithMoreOutputsOutput,
},
}
for i, tt := range tests {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
got := State(tt.State)
if got != tt.Want {
t.Errorf(
"wrong result\ninput: %v\ngot: \n%q\nwant: \n%q",
tt.State.State, got, tt.Want,
)
}
})
}
}
func testProvider() *terraform.MockProvider {
p := new(terraform.MockProvider)
p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
return providers.ReadResourceResponse{NewState: req.PriorState}
}
p.GetProviderSchemaResponse = testProviderSchema()
return p
}
func testProviderSchema() *providers.GetProviderSchemaResponse {
return &providers.GetProviderSchemaResponse{
Provider: providers.Schema{
Block: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"region": {Type: cty.String, Optional: true},
},
},
},
ResourceTypes: map[string]providers.Schema{
"test_resource": {
Block: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Computed: true},
"foo": {Type: cty.String, Optional: true},
"woozles": {Type: cty.String, Optional: true},
},
BlockTypes: map[string]*configschema.NestedBlock{
"nested": {
Nesting: configschema.NestingList,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"compute": {Type: cty.String, Optional: true},
"value": {Type: cty.String, Optional: true},
},
},
},
},
},
},
},
DataSources: map[string]providers.Schema{
"test_data_source": {
Block: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"compute": {Type: cty.String, Optional: true},
"value": {Type: cty.String, Computed: true},
},
},
},
},
}
}
func testSchemas() *terraform.Schemas {
provider := testProvider()
return &terraform.Schemas{
Providers: map[addrs.Provider]*terraform.ProviderSchema{
addrs.NewDefaultProvider("test"): provider.ProviderSchema(),
},
}
}
const basicStateOutput = `# data.test_data_source.data:
data "test_data_source" "data" {
compute = "sure"
}
# test_resource.baz[0]:
resource "test_resource" "baz" {
woozles = "confuzles"
}
Outputs:
bar = "bar value"`
const nestedStateOutput = `# test_resource.baz[0]:
resource "test_resource" "baz" {
woozles = "confuzles"
nested {
value = "42"
}
}`
const deposedNestedStateOutput = `# test_resource.baz[0]:
resource "test_resource" "baz" {
woozles = "confuzles"
nested {
value = "42"
}
}
# test_resource.baz[0]: (deposed object 1234)
resource "test_resource" "baz" {
woozles = "confuzles"
nested {
value = "42"
}
}`
const onlyDeposedOutput = `# test_resource.baz[0]:
# test_resource.baz[0]: (deposed object 1234)
resource "test_resource" "baz" {
woozles = "confuzles"
nested {
value = "42"
}
}
# test_resource.baz[0]: (deposed object 5678)
resource "test_resource" "baz" {
woozles = "confuzles"
nested {
value = "42"
}
}`
const stateWithMoreOutputsOutput = `# test_resource.baz[0]:
resource "test_resource" "baz" {
woozles = "confuzles"
}
Outputs:
bool_var = true
int_var = 42
map_var = {
"first" = "foo"
"second" = "bar"
}
sensitive_var = (sensitive value)
string_var = "string value"`
func basicState(t *testing.T) *states.State {
state := states.NewState()
rootModule := state.RootModule()
if rootModule == nil {
t.Errorf("root module is nil; want valid object")
}
rootModule.SetLocalValue("foo", cty.StringVal("foo value"))
rootModule.SetOutputValue("bar", cty.StringVal("bar value"), false)
rootModule.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_resource",
Name: "baz",
}.Instance(addrs.IntKey(0)),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
SchemaVersion: 1,
AttrsJSON: []byte(`{"woozles":"confuzles"}`),
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
)
rootModule.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.DataResourceMode,
Type: "test_data_source",
Name: "data",
}.Instance(addrs.NoKey),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
SchemaVersion: 1,
AttrsJSON: []byte(`{"compute":"sure"}`),
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
)
return state
}
func stateWithMoreOutputs(t *testing.T) *states.State {
state := states.NewState()
rootModule := state.RootModule()
if rootModule == nil {
t.Errorf("root module is nil; want valid object")
}
rootModule.SetOutputValue("string_var", cty.StringVal("string value"), false)
rootModule.SetOutputValue("int_var", cty.NumberIntVal(42), false)
rootModule.SetOutputValue("bool_var", cty.BoolVal(true), false)
rootModule.SetOutputValue("sensitive_var", cty.StringVal("secret!!!"), true)
rootModule.SetOutputValue("map_var", cty.MapVal(map[string]cty.Value{
"first": cty.StringVal("foo"),
"second": cty.StringVal("bar"),
}), false)
rootModule.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_resource",
Name: "baz",
}.Instance(addrs.IntKey(0)),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
SchemaVersion: 1,
AttrsJSON: []byte(`{"woozles":"confuzles"}`),
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
)
return state
}
func nestedState(t *testing.T) *states.State {
state := states.NewState()
rootModule := state.RootModule()
if rootModule == nil {
t.Errorf("root module is nil; want valid object")
}
rootModule.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_resource",
Name: "baz",
}.Instance(addrs.IntKey(0)),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
SchemaVersion: 1,
AttrsJSON: []byte(`{"woozles":"confuzles","nested": [{"value": "42"}]}`),
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
)
return state
}
func deposedState(t *testing.T) *states.State {
state := nestedState(t)
rootModule := state.RootModule()
rootModule.SetResourceInstanceDeposed(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_resource",
Name: "baz",
}.Instance(addrs.IntKey(0)),
states.DeposedKey("1234"),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
SchemaVersion: 1,
AttrsJSON: []byte(`{"woozles":"confuzles","nested": [{"value": "42"}]}`),
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
)
return state
}
// replicate a corrupt resource where only a deposed exists
func onlyDeposedState(t *testing.T) *states.State {
state := states.NewState()
rootModule := state.RootModule()
if rootModule == nil {
t.Errorf("root module is nil; want valid object")
}
rootModule.SetResourceInstanceDeposed(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_resource",
Name: "baz",
}.Instance(addrs.IntKey(0)),
states.DeposedKey("1234"),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
SchemaVersion: 1,
AttrsJSON: []byte(`{"woozles":"confuzles","nested": [{"value": "42"}]}`),
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
)
rootModule.SetResourceInstanceDeposed(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_resource",
Name: "baz",
}.Instance(addrs.IntKey(0)),
states.DeposedKey("5678"),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
SchemaVersion: 1,
AttrsJSON: []byte(`{"woozles":"confuzles","nested": [{"value": "42"}]}`),
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
)
return state
}

@ -7,7 +7,6 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/mitchellh/colorstring"
"github.com/hashicorp/terraform/internal/command/format"
"github.com/hashicorp/terraform/internal/command/jsonprovider"
"github.com/hashicorp/terraform/internal/command/jsonstate"
"github.com/hashicorp/terraform/internal/states/statefile"
@ -26,56 +25,39 @@ func TestState(t *testing.T) {
color := &colorstring.Colorize{Colors: colorstring.DefaultColors, Disable: true}
tests := []struct {
State *format.StateOpts
Want string
State *states.State
Schemas *terraform.Schemas
Want string
}{
{
&format.StateOpts{
State: &states.State{},
Color: color,
Schemas: &terraform.Schemas{},
},
"The state file is empty. No resources are represented.\n",
State: &states.State{},
Schemas: &terraform.Schemas{},
Want: "The state file is empty. No resources are represented.\n",
},
{
&format.StateOpts{
State: basicState(t),
Color: color,
Schemas: testSchemas(),
},
basicStateOutput,
State: basicState(t),
Schemas: testSchemas(),
Want: basicStateOutput,
},
{
&format.StateOpts{
State: nestedState(t),
Color: color,
Schemas: testSchemas(),
},
nestedStateOutput,
State: nestedState(t),
Schemas: testSchemas(),
Want: nestedStateOutput,
},
{
&format.StateOpts{
State: deposedState(t),
Color: color,
Schemas: testSchemas(),
},
deposedNestedStateOutput,
State: deposedState(t),
Schemas: testSchemas(),
Want: deposedNestedStateOutput,
},
{
&format.StateOpts{
State: onlyDeposedState(t),
Color: color,
Schemas: testSchemas(),
},
onlyDeposedOutput,
State: onlyDeposedState(t),
Schemas: testSchemas(),
Want: onlyDeposedOutput,
},
{
&format.StateOpts{
State: stateWithMoreOutputs(t),
Color: color,
Schemas: testSchemas(),
},
stateWithMoreOutputsOutput,
State: stateWithMoreOutputs(t),
Schemas: testSchemas(),
Want: stateWithMoreOutputsOutput,
},
}
@ -83,8 +65,8 @@ func TestState(t *testing.T) {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
root, outputs, err := jsonstate.MarshalForRenderer(&statefile.File{
State: tt.State.State,
}, tt.State.Schemas)
State: tt.State,
}, tt.Schemas)
if err != nil {
t.Errorf("found err: %v", err)
@ -102,7 +84,7 @@ func TestState(t *testing.T) {
RootModule: root,
RootModuleOutputs: outputs,
ProviderFormatVersion: jsonprovider.FormatVersion,
ProviderSchemas: jsonprovider.MarshalForRenderer(tt.State.Schemas),
ProviderSchemas: jsonprovider.MarshalForRenderer(tt.Schemas),
})
result := done(t).All()

@ -8,8 +8,11 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/command/arguments"
"github.com/hashicorp/terraform/internal/command/format"
"github.com/hashicorp/terraform/internal/command/jsonformat"
"github.com/hashicorp/terraform/internal/command/jsonprovider"
"github.com/hashicorp/terraform/internal/command/jsonstate"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/states/statefile"
"github.com/mitchellh/cli"
)
@ -24,19 +27,19 @@ func (c *StateShowCommand) Run(args []string) int {
cmdFlags := c.Meta.defaultFlagSet("state show")
cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path")
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
c.Streams.Eprintf("Error parsing command-line flags: %s\n", err.Error())
return 1
}
args = cmdFlags.Args()
if len(args) != 1 {
c.Ui.Error("Exactly one argument expected.\n")
c.Streams.Eprint("Exactly one argument expected.\n")
return cli.RunResultHelp
}
// Check for user-supplied plugin path
var err error
if c.pluginPath, err = c.loadPluginPath(); err != nil {
c.Ui.Error(fmt.Sprintf("Error loading plugin path: %s", err))
c.Streams.Eprintf("Error loading plugin path: %\n", err)
return 1
}
@ -50,7 +53,7 @@ func (c *StateShowCommand) Run(args []string) int {
// We require a local backend
local, ok := b.(backend.Local)
if !ok {
c.Ui.Error(ErrUnsupportedLocalOp)
c.Streams.Eprint(ErrUnsupportedLocalOp)
return 1
}
@ -60,14 +63,14 @@ func (c *StateShowCommand) Run(args []string) int {
// Check if the address can be parsed
addr, addrDiags := addrs.ParseAbsResourceInstanceStr(args[0])
if addrDiags.HasErrors() {
c.Ui.Error(fmt.Sprintf(errParsingAddress, args[0]))
c.Streams.Eprintln(fmt.Sprintf(errParsingAddress, args[0]))
return 1
}
// We expect the config dir to always be the cwd
cwd, err := os.Getwd()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error getting cwd: %s", err))
c.Streams.Eprintf("Error getting cwd: %s\n", err)
return 1
}
@ -78,49 +81,49 @@ func (c *StateShowCommand) Run(args []string) int {
opReq.ConfigLoader, err = c.initConfigLoader()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error initializing config loader: %s", err))
c.Streams.Eprintf("Error initializing config loader: %s\n", err)
return 1
}
// Get the context (required to get the schemas)
lr, _, ctxDiags := local.LocalRun(opReq)
if ctxDiags.HasErrors() {
c.showDiagnostics(ctxDiags)
c.View.Diagnostics(ctxDiags)
return 1
}
// Get the schemas from the context
schemas, diags := lr.Core.Schemas(lr.Config, lr.InputState)
if diags.HasErrors() {
c.showDiagnostics(diags)
c.View.Diagnostics(diags)
return 1
}
// Get the state
env, err := c.Workspace()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error selecting workspace: %s", err))
c.Streams.Eprintf("Error selecting workspace: %s\n", err)
return 1
}
stateMgr, err := b.StateMgr(env)
if err != nil {
c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
c.Streams.Eprintln(fmt.Sprintf(errStateLoadingState, err))
return 1
}
if err := stateMgr.RefreshState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to refresh state: %s", err))
c.Streams.Eprintf("Failed to refresh state: %s\n", err)
return 1
}
state := stateMgr.State()
if state == nil {
c.Ui.Error(errStateNotFound)
c.Streams.Eprintln(errStateNotFound)
return 1
}
is := state.ResourceInstance(addr)
if !is.HasCurrent() {
c.Ui.Error(errNoInstanceFound)
c.Streams.Eprintln(errNoInstanceFound)
return 1
}
@ -138,13 +141,26 @@ func (c *StateShowCommand) Run(args []string) int {
absPc,
)
output := format.State(&format.StateOpts{
State: singleInstance,
Color: c.Colorize(),
Schemas: schemas,
})
c.Ui.Output(output[strings.Index(output, "#"):])
root, outputs, err := jsonstate.MarshalForRenderer(statefile.New(singleInstance, "", 0), schemas)
if err != nil {
c.Streams.Eprintf("Failed to marshal state to json: %s", err)
}
jstate := jsonformat.State{
StateFormatVersion: jsonstate.FormatVersion,
ProviderFormatVersion: jsonprovider.FormatVersion,
RootModule: root,
RootModuleOutputs: outputs,
ProviderSchemas: jsonprovider.MarshalForRenderer(schemas),
}
renderer := jsonformat.Renderer{
Streams: c.Streams,
Colorize: c.Colorize(),
RunningInAutomation: c.RunningInAutomation,
}
renderer.RenderHumanState(jstate)
return 0
}

@ -8,7 +8,7 @@ import (
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/states"
"github.com/mitchellh/cli"
"github.com/hashicorp/terraform/internal/terminal"
"github.com/zclconf/go-cty/cty"
)
@ -47,11 +47,11 @@ func TestStateShow(t *testing.T) {
},
}
ui := new(cli.MockUi)
streams, done := terminal.StreamsForTesting(t)
c := &StateShowCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
Streams: streams,
},
}
@ -59,13 +59,15 @@ func TestStateShow(t *testing.T) {
"-state", statePath,
"test_instance.foo",
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
code := c.Run(args)
output := done(t)
if code != 0 {
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
}
// Test that outputs were displayed
expected := strings.TrimSpace(testStateShowOutput) + "\n"
actual := ui.OutputWriter.String()
actual := output.Stdout()
if actual != expected {
t.Fatalf("Expected:\n%q\n\nTo equal:\n%q", actual, expected)
}
@ -122,11 +124,11 @@ func TestStateShow_multi(t *testing.T) {
},
}
ui := new(cli.MockUi)
streams, done := terminal.StreamsForTesting(t)
c := &StateShowCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
Streams: streams,
},
}
@ -134,13 +136,15 @@ func TestStateShow_multi(t *testing.T) {
"-state", statePath,
"test_instance.foo",
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
code := c.Run(args)
output := done(t)
if code != 0 {
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
}
// Test that outputs were displayed
expected := strings.TrimSpace(testStateShowOutput) + "\n"
actual := ui.OutputWriter.String()
actual := output.Stdout()
if actual != expected {
t.Fatalf("Expected:\n%q\n\nTo equal:\n%q", actual, expected)
}
@ -150,11 +154,11 @@ func TestStateShow_noState(t *testing.T) {
testCwd(t)
p := testProvider()
ui := new(cli.MockUi)
streams, done := terminal.StreamsForTesting(t)
c := &StateShowCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
Streams: streams,
},
}
@ -164,8 +168,9 @@ func TestStateShow_noState(t *testing.T) {
if code := c.Run(args); code != 1 {
t.Fatalf("bad: %d", code)
}
if !strings.Contains(ui.ErrorWriter.String(), "No state file was found!") {
t.Fatalf("expected a no state file error, got: %s", ui.ErrorWriter.String())
output := done(t)
if !strings.Contains(output.Stderr(), "No state file was found!") {
t.Fatalf("expected a no state file error, got: %s", output.Stderr())
}
}
@ -174,11 +179,11 @@ func TestStateShow_emptyState(t *testing.T) {
statePath := testStateFile(t, state)
p := testProvider()
ui := new(cli.MockUi)
streams, done := terminal.StreamsForTesting(t)
c := &StateShowCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
Streams: streams,
},
}
@ -189,8 +194,9 @@ func TestStateShow_emptyState(t *testing.T) {
if code := c.Run(args); code != 1 {
t.Fatalf("bad: %d", code)
}
if !strings.Contains(ui.ErrorWriter.String(), "No instance found for the given address!") {
t.Fatalf("expected a no instance found error, got: %s", ui.ErrorWriter.String())
output := done(t)
if !strings.Contains(output.Stderr(), "No instance found for the given address!") {
t.Fatalf("expected a no instance found error, got: %s", output.Stderr())
}
}
@ -229,7 +235,7 @@ func TestStateShow_configured_provider(t *testing.T) {
},
}
ui := new(cli.MockUi)
streams, done := terminal.StreamsForTesting(t)
c := &StateShowCommand{
Meta: Meta{
testingOverrides: &testingOverrides{
@ -237,7 +243,7 @@ func TestStateShow_configured_provider(t *testing.T) {
addrs.NewDefaultProvider("test-beta"): providers.FactoryFixed(p),
},
},
Ui: ui,
Streams: streams,
},
}
@ -245,13 +251,15 @@ func TestStateShow_configured_provider(t *testing.T) {
"-state", statePath,
"test_instance.foo",
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
code := c.Run(args)
output := done(t)
if code != 0 {
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
}
// Test that outputs were displayed
expected := strings.TrimSpace(testStateShowOutput) + "\n"
actual := ui.OutputWriter.String()
actual := output.Stdout()
if actual != expected {
t.Fatalf("Expected:\n%q\n\nTo equal:\n%q", actual, expected)
}

Loading…
Cancel
Save