add command/state show
This commit is contained in:
parent
a754723561
commit
f6692e66ac
|
@ -0,0 +1,34 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StateMeta is the meta struct that should be embedded in state subcommands.
|
||||||
|
type StateMeta struct{}
|
||||||
|
|
||||||
|
// filterInstance filters a single instance out of filter results.
|
||||||
|
func (c *StateMeta) filterInstance(rs []*terraform.StateFilterResult) (*terraform.StateFilterResult, error) {
|
||||||
|
var result *terraform.StateFilterResult
|
||||||
|
for _, r := range rs {
|
||||||
|
if _, ok := r.Value.(*terraform.InstanceState); !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if result != nil {
|
||||||
|
return nil, errors.New(errStateMultiple)
|
||||||
|
}
|
||||||
|
|
||||||
|
result = r
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, 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.`
|
|
@ -0,0 +1,98 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/mitchellh/cli"
|
||||||
|
"github.com/ryanuber/columnize"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StateShowCommand is a Command implementation that shows a single resource.
|
||||||
|
type StateShowCommand struct {
|
||||||
|
Meta
|
||||||
|
StateMeta
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *StateShowCommand) Run(args []string) int {
|
||||||
|
args = c.Meta.process(args, true)
|
||||||
|
|
||||||
|
cmdFlags := c.Meta.flagSet("state show")
|
||||||
|
cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path")
|
||||||
|
if err := cmdFlags.Parse(args); err != nil {
|
||||||
|
return cli.RunResultHelp
|
||||||
|
}
|
||||||
|
args = cmdFlags.Args()
|
||||||
|
|
||||||
|
state, err := c.State()
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf(errStateLoadingState, err))
|
||||||
|
return cli.RunResultHelp
|
||||||
|
}
|
||||||
|
|
||||||
|
stateReal := state.State()
|
||||||
|
if stateReal == nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf(errStateNotFound))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := &terraform.StateFilter{State: stateReal}
|
||||||
|
results, err := filter.Filter(args...)
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf(errStateFilter, err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
instance, err := c.filterInstance(results)
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
is := instance.Value.(*terraform.InstanceState)
|
||||||
|
|
||||||
|
// Sort the keys
|
||||||
|
keys := make([]string, 0, len(is.Attributes))
|
||||||
|
for k, _ := range is.Attributes {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
// Build the output
|
||||||
|
output := make([]string, 0, len(is.Attributes)+1)
|
||||||
|
output = append(output, fmt.Sprintf("id | %s", is.ID))
|
||||||
|
for _, k := range keys {
|
||||||
|
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 {
|
||||||
|
helpText := `
|
||||||
|
Usage: terraform state show [options] PATTERN
|
||||||
|
|
||||||
|
Shows the attributes of a resource in the Terraform state.
|
||||||
|
|
||||||
|
This command shows the attributes of a single resource in the Terraform
|
||||||
|
state. The pattern argument must be used to specify a single resource.
|
||||||
|
You can view the list of available resources with "terraform state list".
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
-state=statefile Path to a Terraform state file to use to look
|
||||||
|
up Terraform-managed resources. By default it will
|
||||||
|
use the state "terraform.tfstate" if it exists.
|
||||||
|
|
||||||
|
`
|
||||||
|
return strings.TrimSpace(helpText)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *StateShowCommand) Synopsis() string {
|
||||||
|
return "Show a resource in the state"
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/mitchellh/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStateShow(t *testing.T) {
|
||||||
|
state := &terraform.State{
|
||||||
|
Modules: []*terraform.ModuleState{
|
||||||
|
&terraform.ModuleState{
|
||||||
|
Path: []string{"root"},
|
||||||
|
Resources: map[string]*terraform.ResourceState{
|
||||||
|
"test_instance.foo": &terraform.ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &terraform.InstanceState{
|
||||||
|
ID: "bar",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"foo": "value",
|
||||||
|
"bar": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
|
p := testProvider()
|
||||||
|
ui := new(cli.MockUi)
|
||||||
|
c := &StateShowCommand{
|
||||||
|
Meta: Meta{
|
||||||
|
ContextOpts: testCtxConfig(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"-state", statePath,
|
||||||
|
"test_instance.foo",
|
||||||
|
}
|
||||||
|
if code := c.Run(args); code != 0 {
|
||||||
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that outputs were displayed
|
||||||
|
expected := strings.TrimSpace(testStateShowOutput) + "\n"
|
||||||
|
actual := ui.OutputWriter.String()
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("Expected:\n%q\n\nTo equal: %q", actual, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateShow_multi(t *testing.T) {
|
||||||
|
state := &terraform.State{
|
||||||
|
Modules: []*terraform.ModuleState{
|
||||||
|
&terraform.ModuleState{
|
||||||
|
Path: []string{"root"},
|
||||||
|
Resources: map[string]*terraform.ResourceState{
|
||||||
|
"test_instance.foo.0": &terraform.ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &terraform.InstanceState{
|
||||||
|
ID: "bar",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"foo": "value",
|
||||||
|
"bar": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"test_instance.foo.1": &terraform.ResourceState{
|
||||||
|
Type: "test_instance",
|
||||||
|
Primary: &terraform.InstanceState{
|
||||||
|
ID: "bar",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"foo": "value",
|
||||||
|
"bar": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
|
p := testProvider()
|
||||||
|
ui := new(cli.MockUi)
|
||||||
|
c := &StateShowCommand{
|
||||||
|
Meta: Meta{
|
||||||
|
ContextOpts: testCtxConfig(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"-state", statePath,
|
||||||
|
"test_instance.foo",
|
||||||
|
}
|
||||||
|
if code := c.Run(args); code != 1 {
|
||||||
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateShow_noState(t *testing.T) {
|
||||||
|
tmp, cwd := testCwd(t)
|
||||||
|
defer testFixCwd(t, tmp, cwd)
|
||||||
|
|
||||||
|
p := testProvider()
|
||||||
|
ui := new(cli.MockUi)
|
||||||
|
c := &StateShowCommand{
|
||||||
|
Meta: Meta{
|
||||||
|
ContextOpts: testCtxConfig(p),
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{}
|
||||||
|
if code := c.Run(args); code != 1 {
|
||||||
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testStateShowOutput = `
|
||||||
|
id = bar
|
||||||
|
bar = value
|
||||||
|
foo = value
|
||||||
|
`
|
|
@ -158,6 +158,12 @@ func init() {
|
||||||
Meta: meta,
|
Meta: meta,
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"state show": func() (cli.Command, error) {
|
||||||
|
return &command.StateShowCommand{
|
||||||
|
Meta: meta,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -108,11 +108,12 @@ func (f *StateFilter) filterSingle(a *ResourceAddress) []*StateFilterResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the resource level result
|
// Add the resource level result
|
||||||
results = append(results, &StateFilterResult{
|
resourceResult := &StateFilterResult{
|
||||||
Path: addr.Path,
|
Path: addr.Path,
|
||||||
Address: addr.String(),
|
Address: addr.String(),
|
||||||
Value: r,
|
Value: r,
|
||||||
})
|
}
|
||||||
|
results = append(results, resourceResult)
|
||||||
|
|
||||||
// Add the instances
|
// Add the instances
|
||||||
if r.Primary != nil {
|
if r.Primary != nil {
|
||||||
|
@ -120,6 +121,7 @@ func (f *StateFilter) filterSingle(a *ResourceAddress) []*StateFilterResult {
|
||||||
results = append(results, &StateFilterResult{
|
results = append(results, &StateFilterResult{
|
||||||
Path: addr.Path,
|
Path: addr.Path,
|
||||||
Address: addr.String(),
|
Address: addr.String(),
|
||||||
|
Parent: resourceResult,
|
||||||
Value: r.Primary,
|
Value: r.Primary,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -130,6 +132,7 @@ func (f *StateFilter) filterSingle(a *ResourceAddress) []*StateFilterResult {
|
||||||
results = append(results, &StateFilterResult{
|
results = append(results, &StateFilterResult{
|
||||||
Path: addr.Path,
|
Path: addr.Path,
|
||||||
Address: addr.String(),
|
Address: addr.String(),
|
||||||
|
Parent: resourceResult,
|
||||||
Value: instance,
|
Value: instance,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -141,6 +144,7 @@ func (f *StateFilter) filterSingle(a *ResourceAddress) []*StateFilterResult {
|
||||||
results = append(results, &StateFilterResult{
|
results = append(results, &StateFilterResult{
|
||||||
Path: addr.Path,
|
Path: addr.Path,
|
||||||
Address: addr.String(),
|
Address: addr.String(),
|
||||||
|
Parent: resourceResult,
|
||||||
Value: instance,
|
Value: instance,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -200,6 +204,11 @@ type StateFilterResult struct {
|
||||||
// Address is the address that can be used to reference this exact result.
|
// Address is the address that can be used to reference this exact result.
|
||||||
Address string
|
Address string
|
||||||
|
|
||||||
|
// Parent, if non-nil, is a parent of this result. For instances, the
|
||||||
|
// parent would be a resource. For resources, the parent would be
|
||||||
|
// a module. For modules, this is currently nil.
|
||||||
|
Parent *StateFilterResult
|
||||||
|
|
||||||
// Value is the actual value. This must be type switched on. It can be
|
// Value is the actual value. This must be type switched on. It can be
|
||||||
// any data structures that `State` can hold: `ModuleState`,
|
// any data structures that `State` can hold: `ModuleState`,
|
||||||
// `ResourceState`, `InstanceState`.
|
// `ResourceState`, `InstanceState`.
|
||||||
|
|
Loading…
Reference in New Issue