Merge pull request #19200 from hashicorp/f-state-show
command/state: update and fix the state show command
This commit is contained in:
commit
b648da2636
|
@ -23,7 +23,6 @@ type ShowCommand struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ShowCommand) Run(args []string) int {
|
func (c *ShowCommand) Run(args []string) int {
|
||||||
|
|
||||||
args, err := c.Meta.process(args, false)
|
args, err := c.Meta.process(args, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 1
|
return 1
|
||||||
|
|
|
@ -133,9 +133,3 @@ func (c *StateMeta) filter(state *states.State, args []string) ([]*states.Filter
|
||||||
|
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const errStateMultiple = `Multiple instances found for the given pattern!
|
|
||||||
|
|
||||||
This command requires that the pattern match exactly one instance
|
|
||||||
of a resource. To view the matched instances, use "terraform state list".
|
|
||||||
Please modify the pattern to match only a single instance.`
|
|
||||||
|
|
|
@ -32,8 +32,8 @@ func (c *StatePushCommand) Run(args []string) int {
|
||||||
args = cmdFlags.Args()
|
args = cmdFlags.Args()
|
||||||
|
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
c.Ui.Error("Exactly one argument expected: path to state to push")
|
c.Ui.Error("Exactly one argument expected.\n")
|
||||||
return 1
|
return cli.RunResultHelp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine our reader for the input state. This is the filepath
|
// Determine our reader for the input state. This is the filepath
|
||||||
|
|
|
@ -2,8 +2,13 @@ package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
"github.com/hashicorp/terraform/backend"
|
||||||
|
"github.com/hashicorp/terraform/command/format"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,11 +25,15 @@ func (c *StateShowCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdFlags := c.Meta.flagSet("state show")
|
cmdFlags := c.Meta.flagSet("state show")
|
||||||
cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path")
|
cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path")
|
||||||
if err := cmdFlags.Parse(args); err != nil {
|
if err := cmdFlags.Parse(args); err != nil {
|
||||||
return cli.RunResultHelp
|
return cli.RunResultHelp
|
||||||
}
|
}
|
||||||
args = cmdFlags.Args()
|
args = cmdFlags.Args()
|
||||||
|
if len(args) != 1 {
|
||||||
|
c.Ui.Error("Exactly one argument expected.\n")
|
||||||
|
return cli.RunResultHelp
|
||||||
|
}
|
||||||
|
|
||||||
// Load the backend
|
// Load the backend
|
||||||
b, backendDiags := c.Backend(nil)
|
b, backendDiags := c.Backend(nil)
|
||||||
|
@ -33,73 +42,85 @@ func (c *StateShowCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the state
|
// We require a local backend
|
||||||
env := c.Workspace()
|
local, ok := b.(backend.Local)
|
||||||
state, err := b.StateMgr(env)
|
if !ok {
|
||||||
if err != nil {
|
c.Ui.Error(ErrUnsupportedLocalOp)
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if err := state.RefreshState(); err != nil {
|
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
stateReal := state.State()
|
// Check if the address can be parsed
|
||||||
if stateReal == nil {
|
addr, addrDiags := addrs.ParseAbsResourceInstanceStr(args[0])
|
||||||
|
if addrDiags.HasErrors() {
|
||||||
|
c.Ui.Error(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))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the operation (required to get the schemas)
|
||||||
|
opReq := c.Operation(b)
|
||||||
|
opReq.ConfigDir = cwd
|
||||||
|
opReq.ConfigLoader, err = c.initConfigLoader()
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Error initializing config loader: %s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the context (required to get the schemas)
|
||||||
|
ctx, _, ctxDiags := local.Context(opReq)
|
||||||
|
if ctxDiags.HasErrors() {
|
||||||
|
c.showDiagnostics(ctxDiags)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the schemas from the context
|
||||||
|
schemas := ctx.Schemas()
|
||||||
|
|
||||||
|
// Get the state
|
||||||
|
env := c.Workspace()
|
||||||
|
stateMgr, err := b.StateMgr(env)
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if err := stateMgr.RefreshState(); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Failed to refresh state: %s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
state := stateMgr.State()
|
||||||
|
if state == nil {
|
||||||
c.Ui.Error(fmt.Sprintf(errStateNotFound))
|
c.Ui.Error(fmt.Sprintf(errStateNotFound))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Ui.Error("state show not yet updated for new state types")
|
is := state.ResourceInstance(addr)
|
||||||
return 1
|
if !is.HasCurrent() {
|
||||||
|
c.Ui.Error(errNoInstanceFound)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
singleInstance := states.NewState()
|
||||||
filter := &terraform.StateFilter{State: stateReal}
|
singleInstance.EnsureModule(addr.Module).SetResourceInstanceCurrent(
|
||||||
results, err := filter.Filter(args...)
|
addr.Resource,
|
||||||
if err != nil {
|
is.Current,
|
||||||
c.Ui.Error(fmt.Sprintf(errStateFilter, err))
|
addr.Resource.Resource.DefaultProviderConfig().Absolute(addr.Module),
|
||||||
return 1
|
)
|
||||||
}
|
|
||||||
|
|
||||||
if len(results) == 0 {
|
output := format.State(&format.StateOpts{
|
||||||
return 0
|
State: singleInstance,
|
||||||
}
|
Color: c.Colorize(),
|
||||||
|
Schemas: schemas,
|
||||||
|
})
|
||||||
|
c.Ui.Output(output[strings.Index(output, "#"):])
|
||||||
|
|
||||||
instance, err := c.filterInstance(results)
|
return 0
|
||||||
if err != nil {
|
|
||||||
c.Ui.Error(err.Error())
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if instance == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
is := instance.Value.(*terraform.InstanceState)
|
|
||||||
|
|
||||||
// Sort the keys
|
|
||||||
var keys []string
|
|
||||||
for k, _ := range is.Attributes {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
|
|
||||||
// Build the output
|
|
||||||
var output []string
|
|
||||||
output = append(output, fmt.Sprintf("id | %s", is.ID))
|
|
||||||
for _, k := range keys {
|
|
||||||
if k != "id" {
|
|
||||||
output = append(output, fmt.Sprintf("%s | %s", k, is.Attributes[k]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output
|
|
||||||
config := columnize.DefaultConfig()
|
|
||||||
config.Glue = " = "
|
|
||||||
c.Ui.Output(columnize.Format(output, config))
|
|
||||||
return 0
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *StateShowCommand) Help() string {
|
func (c *StateShowCommand) Help() string {
|
||||||
|
@ -125,3 +146,15 @@ Options:
|
||||||
func (c *StateShowCommand) Synopsis() string {
|
func (c *StateShowCommand) Synopsis() string {
|
||||||
return "Show a resource in the state"
|
return "Show a resource in the state"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const errNoInstanceFound = `No instance found for the given address!
|
||||||
|
|
||||||
|
This command requires that the address references one specific instance.
|
||||||
|
To view the available instances, use "terraform state list". Please modify
|
||||||
|
the address to reference a specific instance.`
|
||||||
|
|
||||||
|
const errParsingAddress = `Error parsing instance address: %s
|
||||||
|
|
||||||
|
This command requires that the address references one specific instance.
|
||||||
|
To view the available instances, use "terraform state list". Please modify
|
||||||
|
the address to reference a specific instance.`
|
||||||
|
|
|
@ -4,10 +4,12 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/mitchellh/cli"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
"github.com/hashicorp/terraform/states"
|
"github.com/hashicorp/terraform/states"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/mitchellh/cli"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStateShow(t *testing.T) {
|
func TestStateShow(t *testing.T) {
|
||||||
|
@ -28,6 +30,18 @@ func TestStateShow(t *testing.T) {
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
|
p.GetSchemaReturn = &terraform.ProviderSchema{
|
||||||
|
ResourceTypes: map[string]*configschema.Block{
|
||||||
|
"test_instance": {
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
||||||
|
"foo": {Type: cty.String, Optional: true},
|
||||||
|
"bar": {Type: cty.String, Optional: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &StateShowCommand{
|
c := &StateShowCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
|
@ -45,7 +59,7 @@ func TestStateShow(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that outputs were displayed
|
// Test that outputs were displayed
|
||||||
expected := strings.TrimSpace(testStateShowOutput) + "\n"
|
expected := strings.TrimSpace(testStateShowOutput) + "\n\n\n"
|
||||||
actual := ui.OutputWriter.String()
|
actual := ui.OutputWriter.String()
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
t.Fatalf("Expected:\n%q\n\nTo equal: %q", actual, expected)
|
t.Fatalf("Expected:\n%q\n\nTo equal: %q", actual, expected)
|
||||||
|
@ -53,6 +67,7 @@ func TestStateShow(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStateShow_multi(t *testing.T) {
|
func TestStateShow_multi(t *testing.T) {
|
||||||
|
submod, _ := addrs.ParseModuleInstanceStr("module.sub")
|
||||||
state := states.BuildState(func(s *states.SyncState) {
|
state := states.BuildState(func(s *states.SyncState) {
|
||||||
s.SetResourceInstanceCurrent(
|
s.SetResourceInstanceCurrent(
|
||||||
addrs.Resource{
|
addrs.Resource{
|
||||||
|
@ -70,18 +85,30 @@ func TestStateShow_multi(t *testing.T) {
|
||||||
addrs.Resource{
|
addrs.Resource{
|
||||||
Mode: addrs.ManagedResourceMode,
|
Mode: addrs.ManagedResourceMode,
|
||||||
Type: "test_instance",
|
Type: "test_instance",
|
||||||
Name: "bar",
|
Name: "foo",
|
||||||
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
}.Instance(addrs.NoKey).Absolute(submod),
|
||||||
&states.ResourceInstanceObjectSrc{
|
&states.ResourceInstanceObjectSrc{
|
||||||
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
|
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
|
||||||
Status: states.ObjectReady,
|
Status: states.ObjectReady,
|
||||||
},
|
},
|
||||||
addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
|
addrs.ProviderConfig{Type: "test"}.Absolute(submod),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
p := testProvider()
|
p := testProvider()
|
||||||
|
p.GetSchemaReturn = &terraform.ProviderSchema{
|
||||||
|
ResourceTypes: map[string]*configschema.Block{
|
||||||
|
"test_instance": {
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
||||||
|
"foo": {Type: cty.String, Optional: true},
|
||||||
|
"bar": {Type: cty.String, Optional: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
c := &StateShowCommand{
|
c := &StateShowCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
|
@ -94,9 +121,16 @@ func TestStateShow_multi(t *testing.T) {
|
||||||
"-state", statePath,
|
"-state", statePath,
|
||||||
"test_instance.foo",
|
"test_instance.foo",
|
||||||
}
|
}
|
||||||
if code := c.Run(args); code != 1 {
|
if code := c.Run(args); code != 0 {
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test that outputs were displayed
|
||||||
|
expected := strings.TrimSpace(testStateShowOutput) + "\n\n\n"
|
||||||
|
actual := ui.OutputWriter.String()
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("Expected:\n%q\n\nTo equal: %q", actual, expected)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStateShow_noState(t *testing.T) {
|
func TestStateShow_noState(t *testing.T) {
|
||||||
|
@ -112,9 +146,14 @@ func TestStateShow_noState(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{}
|
args := []string{
|
||||||
|
"test_instance.foo",
|
||||||
|
}
|
||||||
if code := c.Run(args); code != 1 {
|
if code := c.Run(args); code != 1 {
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,13 +174,19 @@ func TestStateShow_emptyState(t *testing.T) {
|
||||||
"-state", statePath,
|
"-state", statePath,
|
||||||
"test_instance.foo",
|
"test_instance.foo",
|
||||||
}
|
}
|
||||||
if code := c.Run(args); code != 0 {
|
if code := c.Run(args); code != 1 {
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const testStateShowOutput = `
|
const testStateShowOutput = `
|
||||||
id = bar
|
# test_instance.foo:
|
||||||
bar = value
|
resource "test_instance" "foo" {
|
||||||
foo = value
|
bar = "value"
|
||||||
|
foo = "value"
|
||||||
|
id = "bar"
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
|
@ -40,7 +40,7 @@ func (f *Filter) Filter(fs ...string) ([]*FilterResult, error) {
|
||||||
as[i] = addr
|
as[i] = addr
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("Error parsing address '%s'", v)
|
return nil, fmt.Errorf("Error parsing address: %s", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we weren't given any filters, then we list all
|
// If we weren't given any filters, then we list all
|
||||||
|
|
Loading…
Reference in New Issue