Add `terraform state list` command
This introduces the terraform state list command to list the resources within a state. This is the first of many state management commands to come into 0.7. This is the first command of many to come that is considered a "plumbing" command within Terraform (see "plumbing vs porcelain": http://git.661346.n2.nabble.com/what-are-plumbing-and-porcelain-td2190639.html). As such, this PR also introduces a bunch of groundwork to support plumbing commands. The main changes: - Main command output is changed to split "common" and "uncommon" commands. - mitchellh/cli is updated to support nested subcommands, since terraform state list is a nested subcommand. - terraform.StateFilter is introduced as a way in core to filter/search the state files. This is very basic currently but I expect to make it more advanced as time goes on. - terraform state list command is introduced to list resources in a state. This can take a series of arguments to filter this down. Known issues, or things that aren't done in this PR on purpose: - Unit tests for terraform state list are on the way. Unit tests for the core changes are all there.
This commit is contained in:
parent
8e4da4e2a1
commit
d1b46e99bd
|
@ -962,7 +962,7 @@
|
|||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/cli",
|
||||
"Rev": "cb6853d606ea4a12a15ac83cc43503df99fd28fb"
|
||||
"Rev": "83f97d41cf100ee5f33944a8815c167d5e4aa272"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mitchellh/cloudflare-go",
|
||||
|
|
|
@ -326,6 +326,9 @@ func (m *Meta) flagSet(n string) *flag.FlagSet {
|
|||
}()
|
||||
f.SetOutput(errW)
|
||||
|
||||
// Set the default Usage to empty
|
||||
f.Usage = func() {}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
// StateCommand is a Command implementation that just shows help for
|
||||
// the subcommands nested below it.
|
||||
type StateCommand struct {
|
||||
Meta
|
||||
}
|
||||
|
||||
func (c *StateCommand) Run(args []string) int {
|
||||
return cli.RunResultHelp
|
||||
}
|
||||
|
||||
func (c *StateCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: terraform state <subcommand> [options] [args]
|
||||
|
||||
This command has subcommands for advanced state management.
|
||||
|
||||
These subcommands can be used to slice and dice the Terraform state.
|
||||
This is sometimes necessary in advanced cases. For your safety, all
|
||||
state management commands that modify the state create a timestamped
|
||||
backup of the state prior to making modifications.
|
||||
|
||||
The structure and output of the commands is specifically tailored to work
|
||||
well with the common Unix utilities such as grep, awk, etc. We recommend
|
||||
using those tools to perform more advanced state tasks.
|
||||
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *StateCommand) Synopsis() string {
|
||||
return "Advanced state management"
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
// StateListCommand is a Command implementation that lists the resources
|
||||
// within a state file.
|
||||
type StateListCommand struct {
|
||||
Meta
|
||||
}
|
||||
|
||||
func (c *StateListCommand) Run(args []string) int {
|
||||
args = c.Meta.process(args, true)
|
||||
|
||||
cmdFlags := c.Meta.flagSet("state list")
|
||||
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 cli.RunResultHelp
|
||||
}
|
||||
|
||||
for _, result := range results {
|
||||
if _, ok := result.Value.(*terraform.InstanceState); ok {
|
||||
c.Ui.Output(result.Address)
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *StateListCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: terraform state list [options] [pattern...]
|
||||
|
||||
List resources in the Terraform state.
|
||||
|
||||
This command lists resources in the Terraform state. The pattern argument
|
||||
can be used to filter the resources by resource or module. If no pattern
|
||||
is given, all resources are listed.
|
||||
|
||||
The pattern argument is meant to provide very simple filtering. For
|
||||
advanced filtering, please use tools such as "grep". The output of this
|
||||
command is designed to be friendly for this usage.
|
||||
|
||||
The pattern argument accepts any resource targeting syntax. Please
|
||||
refer to the documentation on resource targeting syntax for more
|
||||
information.
|
||||
|
||||
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 *StateListCommand) Synopsis() string {
|
||||
return "List resources in the state"
|
||||
}
|
||||
|
||||
const errStateFilter = `Error filtering state: %[1]s
|
||||
|
||||
Please ensure that all your addresses are formatted properly.`
|
||||
|
||||
const errStateLoadingState = `Error loading the state: %[1]s
|
||||
|
||||
Please ensure that your Terraform state exists and that you've
|
||||
configured it properly. You can use the "-state" flag to point
|
||||
Terraform at another state file.`
|
||||
|
||||
const errStateNotFound = `No state file was found!
|
||||
|
||||
State management commands require a state file. Run this command
|
||||
in a directory where Terraform has been run or use the -state flag
|
||||
to point the command to a specific state location.`
|
|
@ -0,0 +1,59 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func TestStateList(t *testing.T) {
|
||||
state := testState()
|
||||
statePath := testStateFile(t, state)
|
||||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
c := &StateListCommand{
|
||||
Meta: Meta{
|
||||
ContextOpts: testCtxConfig(p),
|
||||
Ui: ui,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-state", statePath,
|
||||
}
|
||||
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(testStateListOutput) + "\n"
|
||||
actual := ui.OutputWriter.String()
|
||||
if actual != expected {
|
||||
t.Fatalf("Expected:\n%q\n\nTo equal: %q", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateList_noState(t *testing.T) {
|
||||
tmp, cwd := testCwd(t)
|
||||
defer testFixCwd(t, tmp, cwd)
|
||||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
c := &StateListCommand{
|
||||
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 testStateListOutput = `
|
||||
test_instance.foo
|
||||
`
|
21
commands.go
21
commands.go
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
// Commands is the mapping of all the available Terraform commands.
|
||||
var Commands map[string]cli.CommandFactory
|
||||
var PlumbingCommands map[string]struct{}
|
||||
|
||||
// Ui is the cli.Ui used for communicating to the outside world.
|
||||
var Ui cli.Ui
|
||||
|
@ -34,6 +35,10 @@ func init() {
|
|||
Ui: Ui,
|
||||
}
|
||||
|
||||
PlumbingCommands = map[string]struct{}{
|
||||
"state": struct{}{}, // includes all subcommands
|
||||
}
|
||||
|
||||
Commands = map[string]cli.CommandFactory{
|
||||
"apply": func() (cli.Command, error) {
|
||||
return &command.ApplyCommand{
|
||||
|
@ -137,6 +142,22 @@ func init() {
|
|||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// Plumbing
|
||||
//-----------------------------------------------------------
|
||||
|
||||
"state": func() (cli.Command, error) {
|
||||
return &command.StateCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"state list": func() (cli.Command, error) {
|
||||
return &command.StateListCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
// helpFunc is a cli.HelpFunc that can is used to output the help for Terraform.
|
||||
func helpFunc(commands map[string]cli.CommandFactory) string {
|
||||
// Determine the maximum key length, and classify based on type
|
||||
porcelain := make(map[string]cli.CommandFactory)
|
||||
plumbing := make(map[string]cli.CommandFactory)
|
||||
maxKeyLen := 0
|
||||
for key, f := range commands {
|
||||
if len(key) > maxKeyLen {
|
||||
maxKeyLen = len(key)
|
||||
}
|
||||
|
||||
if _, ok := PlumbingCommands[key]; ok {
|
||||
plumbing[key] = f
|
||||
} else {
|
||||
porcelain[key] = f
|
||||
}
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("usage: terraform [--version] [--help] <command> [args]\n\n")
|
||||
buf.WriteString(
|
||||
"The available commands for execution are listed below.\n" +
|
||||
"The most common, useful commands are shown first, followed by\n" +
|
||||
"less common or more advanced commands. If you're just getting\n" +
|
||||
"started with Terraform, stick with the common commands. For the\n" +
|
||||
"other commands, please read the help and docs before usage.\n\n")
|
||||
buf.WriteString("Common commands:\n")
|
||||
buf.WriteString(listCommands(porcelain, maxKeyLen))
|
||||
buf.WriteString("\nAll other commands:\n")
|
||||
buf.WriteString(listCommands(plumbing, maxKeyLen))
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// listCommands just lists the commands in the map with the
|
||||
// given maximum key length.
|
||||
func listCommands(commands map[string]cli.CommandFactory, maxKeyLen int) string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
// Get the list of keys so we can sort them, and also get the maximum
|
||||
// key length so they can be aligned properly.
|
||||
keys := make([]string, 0, len(commands))
|
||||
for key, _ := range commands {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, key := range keys {
|
||||
commandFunc, ok := commands[key]
|
||||
if !ok {
|
||||
// This should never happen since we JUST built the list of
|
||||
// keys.
|
||||
panic("command not found: " + key)
|
||||
}
|
||||
|
||||
command, err := commandFunc()
|
||||
if err != nil {
|
||||
log.Printf("[ERR] cli: Command '%s' failed to load: %s",
|
||||
key, err)
|
||||
continue
|
||||
}
|
||||
|
||||
key = fmt.Sprintf("%s%s", key, strings.Repeat(" ", maxKeyLen-len(key)))
|
||||
buf.WriteString(fmt.Sprintf(" %s %s\n", key, command.Synopsis()))
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
2
main.go
2
main.go
|
@ -113,7 +113,7 @@ func wrappedMain() int {
|
|||
cli := &cli.CLI{
|
||||
Args: args,
|
||||
Commands: Commands,
|
||||
HelpFunc: cli.BasicHelpFunc("terraform"),
|
||||
HelpFunc: helpFunc,
|
||||
HelpWriter: os.Stdout,
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,35 @@ func (r *ResourceAddress) Copy() *ResourceAddress {
|
|||
return n
|
||||
}
|
||||
|
||||
// String outputs the address that parses into this address.
|
||||
func (r *ResourceAddress) String() string {
|
||||
var result []string
|
||||
for _, p := range r.Path {
|
||||
result = append(result, "module", p)
|
||||
}
|
||||
|
||||
if r.Type != "" {
|
||||
result = append(result, r.Type)
|
||||
}
|
||||
|
||||
if r.Name != "" {
|
||||
name := r.Name
|
||||
switch r.InstanceType {
|
||||
case TypeDeposed:
|
||||
name += ".deposed"
|
||||
case TypeTainted:
|
||||
name += ".tainted"
|
||||
}
|
||||
|
||||
if r.Index >= 0 {
|
||||
name += fmt.Sprintf("[%d]", r.Index)
|
||||
}
|
||||
result = append(result, name)
|
||||
}
|
||||
|
||||
return strings.Join(result, ".")
|
||||
}
|
||||
|
||||
func ParseResourceAddress(s string) (*ResourceAddress, error) {
|
||||
matches, err := tokenizeResourceAddress(s)
|
||||
if err != nil {
|
||||
|
|
|
@ -9,109 +9,121 @@ func TestParseResourceAddress(t *testing.T) {
|
|||
cases := map[string]struct {
|
||||
Input string
|
||||
Expected *ResourceAddress
|
||||
Output string
|
||||
}{
|
||||
"implicit primary, no specific index": {
|
||||
Input: "aws_instance.foo",
|
||||
Expected: &ResourceAddress{
|
||||
"aws_instance.foo",
|
||||
&ResourceAddress{
|
||||
Type: "aws_instance",
|
||||
Name: "foo",
|
||||
InstanceType: TypePrimary,
|
||||
Index: -1,
|
||||
},
|
||||
"",
|
||||
},
|
||||
"implicit primary, explicit index": {
|
||||
Input: "aws_instance.foo[2]",
|
||||
Expected: &ResourceAddress{
|
||||
"aws_instance.foo[2]",
|
||||
&ResourceAddress{
|
||||
Type: "aws_instance",
|
||||
Name: "foo",
|
||||
InstanceType: TypePrimary,
|
||||
Index: 2,
|
||||
},
|
||||
"",
|
||||
},
|
||||
"implicit primary, explicit index over ten": {
|
||||
Input: "aws_instance.foo[12]",
|
||||
Expected: &ResourceAddress{
|
||||
"aws_instance.foo[12]",
|
||||
&ResourceAddress{
|
||||
Type: "aws_instance",
|
||||
Name: "foo",
|
||||
InstanceType: TypePrimary,
|
||||
Index: 12,
|
||||
},
|
||||
"",
|
||||
},
|
||||
"explicit primary, explicit index": {
|
||||
Input: "aws_instance.foo.primary[2]",
|
||||
Expected: &ResourceAddress{
|
||||
"aws_instance.foo.primary[2]",
|
||||
&ResourceAddress{
|
||||
Type: "aws_instance",
|
||||
Name: "foo",
|
||||
InstanceType: TypePrimary,
|
||||
Index: 2,
|
||||
},
|
||||
"aws_instance.foo[2]",
|
||||
},
|
||||
"tainted": {
|
||||
Input: "aws_instance.foo.tainted",
|
||||
Expected: &ResourceAddress{
|
||||
"aws_instance.foo.tainted",
|
||||
&ResourceAddress{
|
||||
Type: "aws_instance",
|
||||
Name: "foo",
|
||||
InstanceType: TypeTainted,
|
||||
Index: -1,
|
||||
},
|
||||
"",
|
||||
},
|
||||
"deposed": {
|
||||
Input: "aws_instance.foo.deposed",
|
||||
Expected: &ResourceAddress{
|
||||
"aws_instance.foo.deposed",
|
||||
&ResourceAddress{
|
||||
Type: "aws_instance",
|
||||
Name: "foo",
|
||||
InstanceType: TypeDeposed,
|
||||
Index: -1,
|
||||
},
|
||||
"",
|
||||
},
|
||||
"with a hyphen": {
|
||||
Input: "aws_instance.foo-bar",
|
||||
Expected: &ResourceAddress{
|
||||
"aws_instance.foo-bar",
|
||||
&ResourceAddress{
|
||||
Type: "aws_instance",
|
||||
Name: "foo-bar",
|
||||
InstanceType: TypePrimary,
|
||||
Index: -1,
|
||||
},
|
||||
"",
|
||||
},
|
||||
"in a module": {
|
||||
Input: "module.child.aws_instance.foo",
|
||||
Expected: &ResourceAddress{
|
||||
"module.child.aws_instance.foo",
|
||||
&ResourceAddress{
|
||||
Path: []string{"child"},
|
||||
Type: "aws_instance",
|
||||
Name: "foo",
|
||||
InstanceType: TypePrimary,
|
||||
Index: -1,
|
||||
},
|
||||
"",
|
||||
},
|
||||
"nested modules": {
|
||||
Input: "module.a.module.b.module.forever.aws_instance.foo",
|
||||
Expected: &ResourceAddress{
|
||||
"module.a.module.b.module.forever.aws_instance.foo",
|
||||
&ResourceAddress{
|
||||
Path: []string{"a", "b", "forever"},
|
||||
Type: "aws_instance",
|
||||
Name: "foo",
|
||||
InstanceType: TypePrimary,
|
||||
Index: -1,
|
||||
},
|
||||
"",
|
||||
},
|
||||
"just a module": {
|
||||
Input: "module.a",
|
||||
Expected: &ResourceAddress{
|
||||
"module.a",
|
||||
&ResourceAddress{
|
||||
Path: []string{"a"},
|
||||
Type: "",
|
||||
Name: "",
|
||||
InstanceType: TypePrimary,
|
||||
Index: -1,
|
||||
},
|
||||
"",
|
||||
},
|
||||
"just a nested module": {
|
||||
Input: "module.a.module.b",
|
||||
Expected: &ResourceAddress{
|
||||
"module.a.module.b",
|
||||
&ResourceAddress{
|
||||
Path: []string{"a", "b"},
|
||||
Type: "",
|
||||
Name: "",
|
||||
InstanceType: TypePrimary,
|
||||
Index: -1,
|
||||
},
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -124,6 +136,14 @@ func TestParseResourceAddress(t *testing.T) {
|
|||
if !reflect.DeepEqual(out, tc.Expected) {
|
||||
t.Fatalf("bad: %q\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.Expected, out)
|
||||
}
|
||||
|
||||
expected := tc.Input
|
||||
if tc.Output != "" {
|
||||
expected = tc.Output
|
||||
}
|
||||
if out.String() != expected {
|
||||
t.Fatalf("bad: %q\n\nexpected: %s\n\ngot: %s", tn, expected, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// StateFilter is responsible for filtering and searching a state.
|
||||
//
|
||||
// This is a separate struct from State rather than a method on State
|
||||
// because StateFilter might create sidecar data structures to optimize
|
||||
// filtering on the state.
|
||||
//
|
||||
// If you change the State, the filter created is invalid and either
|
||||
// Reset should be called or a new one should be allocated. StateFilter
|
||||
// will not watch State for changes and do this for you. If you filter after
|
||||
// changing the State without calling Reset, the behavior is not defined.
|
||||
type StateFilter struct {
|
||||
State *State
|
||||
}
|
||||
|
||||
// Filter takes the addresses specified by fs and finds all the matches.
|
||||
// The values of fs are resource addressing syntax that can be parsed by
|
||||
// ParseResourceAddress.
|
||||
func (f *StateFilter) Filter(fs ...string) ([]*StateFilterResult, error) {
|
||||
// Parse all the addresses
|
||||
as := make([]*ResourceAddress, len(fs))
|
||||
for i, v := range fs {
|
||||
a, err := ParseResourceAddress(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing address '%s': %s", v, err)
|
||||
}
|
||||
|
||||
as[i] = a
|
||||
}
|
||||
|
||||
// If we werent given any filters, then we list all
|
||||
if len(fs) == 0 {
|
||||
as = append(as, &ResourceAddress{})
|
||||
}
|
||||
|
||||
// Filter each of the address. We keep track of this in a map to
|
||||
// strip duplicates.
|
||||
resultSet := make(map[string]*StateFilterResult)
|
||||
for _, a := range as {
|
||||
for _, r := range f.filterSingle(a) {
|
||||
resultSet[r.String()] = r
|
||||
}
|
||||
}
|
||||
|
||||
// Make the result list
|
||||
results := make([]*StateFilterResult, 0, len(resultSet))
|
||||
for _, v := range resultSet {
|
||||
results = append(results, v)
|
||||
}
|
||||
|
||||
// Sort them and return
|
||||
sort.Sort(StateFilterResultSlice(results))
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (f *StateFilter) filterSingle(a *ResourceAddress) []*StateFilterResult {
|
||||
// The slice to keep track of results
|
||||
var results []*StateFilterResult
|
||||
|
||||
// Go through modules first.
|
||||
modules := make([]*ModuleState, 0, len(f.State.Modules))
|
||||
for _, m := range f.State.Modules {
|
||||
if f.relevant(a, m) {
|
||||
modules = append(modules, m)
|
||||
|
||||
// Only add the module to the results if we haven't specified a type.
|
||||
// We also ignore the root module.
|
||||
if a.Type == "" && len(m.Path) > 1 {
|
||||
results = append(results, &StateFilterResult{
|
||||
Path: m.Path[1:],
|
||||
Address: (&ResourceAddress{Path: m.Path[1:]}).String(),
|
||||
Value: m,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// With the modules set, go through all the resources within
|
||||
// the modules to find relevant resources.
|
||||
for _, m := range modules {
|
||||
for n, r := range m.Resources {
|
||||
if f.relevant(a, r) {
|
||||
// The name in the state contains valuable information. Parse.
|
||||
key, err := ParseResourceStateKey(n)
|
||||
if err != nil {
|
||||
// If we get an error parsing, then just ignore it
|
||||
// out of the state.
|
||||
continue
|
||||
}
|
||||
|
||||
// Build the address for this resource
|
||||
addr := &ResourceAddress{
|
||||
Path: m.Path[1:],
|
||||
Name: key.Name,
|
||||
Type: key.Type,
|
||||
Index: key.Index,
|
||||
}
|
||||
|
||||
// Add the resource level result
|
||||
results = append(results, &StateFilterResult{
|
||||
Path: addr.Path,
|
||||
Address: addr.String(),
|
||||
Value: r,
|
||||
})
|
||||
|
||||
// Add the instances
|
||||
if r.Primary != nil {
|
||||
addr.InstanceType = TypePrimary
|
||||
results = append(results, &StateFilterResult{
|
||||
Path: addr.Path,
|
||||
Address: addr.String(),
|
||||
Value: r.Primary,
|
||||
})
|
||||
}
|
||||
|
||||
for _, instance := range r.Tainted {
|
||||
if f.relevant(a, instance) {
|
||||
addr.InstanceType = TypeTainted
|
||||
results = append(results, &StateFilterResult{
|
||||
Path: addr.Path,
|
||||
Address: addr.String(),
|
||||
Value: instance,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, instance := range r.Deposed {
|
||||
if f.relevant(a, instance) {
|
||||
addr.InstanceType = TypeDeposed
|
||||
results = append(results, &StateFilterResult{
|
||||
Path: addr.Path,
|
||||
Address: addr.String(),
|
||||
Value: instance,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// relevant checks for relevance of this address against the given value.
|
||||
func (f *StateFilter) relevant(addr *ResourceAddress, raw interface{}) bool {
|
||||
switch v := raw.(type) {
|
||||
case *ModuleState:
|
||||
path := v.Path[1:]
|
||||
|
||||
if len(addr.Path) > len(path) {
|
||||
// Longer path in address means there is no way we match.
|
||||
return false
|
||||
}
|
||||
|
||||
// Check for a prefix match
|
||||
for i, p := range addr.Path {
|
||||
if path[i] != p {
|
||||
// Any mismatches don't match.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
case *ResourceState:
|
||||
if addr.Type == "" {
|
||||
// If we have no resource type, then we're interested in all!
|
||||
return true
|
||||
}
|
||||
|
||||
// If the type doesn't match we fail immediately
|
||||
if v.Type != addr.Type {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
default:
|
||||
// If we don't know about it, let's just say no
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// StateFilterResult is a single result from a filter operation. Filter
|
||||
// can match multiple things within a state (module, resource, instance, etc.)
|
||||
// and this unifies that.
|
||||
type StateFilterResult struct {
|
||||
// Module path of the result
|
||||
Path []string
|
||||
|
||||
// Address is the address that can be used to reference this exact result.
|
||||
Address string
|
||||
|
||||
// Value is the actual value. This must be type switched on. It can be
|
||||
// any data structures that `State` can hold: `ModuleState`,
|
||||
// `ResourceState`, `InstanceState`.
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func (r *StateFilterResult) String() string {
|
||||
return fmt.Sprintf("%T: %s", r.Value, r.Address)
|
||||
}
|
||||
|
||||
func (r *StateFilterResult) sortedType() int {
|
||||
switch r.Value.(type) {
|
||||
case *ModuleState:
|
||||
return 0
|
||||
case *ResourceState:
|
||||
return 1
|
||||
case *InstanceState:
|
||||
return 2
|
||||
default:
|
||||
return 50
|
||||
}
|
||||
}
|
||||
|
||||
// StateFilterResultSlice is a slice of results that implements
|
||||
// sort.Interface. The sorting goal is what is most appealing to
|
||||
// human output.
|
||||
type StateFilterResultSlice []*StateFilterResult
|
||||
|
||||
func (s StateFilterResultSlice) Len() int { return len(s) }
|
||||
func (s StateFilterResultSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s StateFilterResultSlice) Less(i, j int) bool {
|
||||
a, b := s[i], s[j]
|
||||
|
||||
// If the addresses are different it is just lexographic sorting
|
||||
if a.Address != b.Address {
|
||||
return a.Address < b.Address
|
||||
}
|
||||
|
||||
// Addresses are the same, which means it matters on the type
|
||||
return a.sortedType() < b.sortedType()
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStateFilterFilter(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
State string
|
||||
Filters []string
|
||||
Expected []string
|
||||
}{
|
||||
"all": {
|
||||
"small.tfstate",
|
||||
[]string{},
|
||||
[]string{
|
||||
"*terraform.ResourceState: aws_key_pair.onprem",
|
||||
"*terraform.InstanceState: aws_key_pair.onprem",
|
||||
"*terraform.ModuleState: module.bootstrap",
|
||||
"*terraform.ResourceState: module.bootstrap.aws_route53_record.oasis-consul-bootstrap-a",
|
||||
"*terraform.InstanceState: module.bootstrap.aws_route53_record.oasis-consul-bootstrap-a",
|
||||
"*terraform.ResourceState: module.bootstrap.aws_route53_record.oasis-consul-bootstrap-ns",
|
||||
"*terraform.InstanceState: module.bootstrap.aws_route53_record.oasis-consul-bootstrap-ns",
|
||||
"*terraform.ResourceState: module.bootstrap.aws_route53_zone.oasis-consul-bootstrap",
|
||||
"*terraform.InstanceState: module.bootstrap.aws_route53_zone.oasis-consul-bootstrap",
|
||||
},
|
||||
},
|
||||
|
||||
"module filter": {
|
||||
"complete.tfstate",
|
||||
[]string{"module.bootstrap"},
|
||||
[]string{
|
||||
"*terraform.ModuleState: module.bootstrap",
|
||||
"*terraform.ResourceState: module.bootstrap.aws_route53_record.oasis-consul-bootstrap-a",
|
||||
"*terraform.InstanceState: module.bootstrap.aws_route53_record.oasis-consul-bootstrap-a",
|
||||
"*terraform.ResourceState: module.bootstrap.aws_route53_record.oasis-consul-bootstrap-ns",
|
||||
"*terraform.InstanceState: module.bootstrap.aws_route53_record.oasis-consul-bootstrap-ns",
|
||||
"*terraform.ResourceState: module.bootstrap.aws_route53_zone.oasis-consul-bootstrap",
|
||||
"*terraform.InstanceState: module.bootstrap.aws_route53_zone.oasis-consul-bootstrap",
|
||||
},
|
||||
},
|
||||
|
||||
"resource in module": {
|
||||
"complete.tfstate",
|
||||
[]string{"module.bootstrap.aws_route53_zone.oasis-consul-bootstrap"},
|
||||
[]string{
|
||||
"*terraform.ResourceState: module.bootstrap.aws_route53_zone.oasis-consul-bootstrap",
|
||||
"*terraform.InstanceState: module.bootstrap.aws_route53_zone.oasis-consul-bootstrap",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for n, tc := range cases {
|
||||
// Load our state
|
||||
f, err := os.Open(filepath.Join("./test-fixtures", "state-filter", tc.State))
|
||||
if err != nil {
|
||||
t.Fatalf("%q: err: %s", n, err)
|
||||
}
|
||||
|
||||
state, err := ReadState(f)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("%q: err: %s", n, err)
|
||||
}
|
||||
|
||||
// Create the filter
|
||||
filter := &StateFilter{State: state}
|
||||
|
||||
// Filter!
|
||||
results, err := filter.Filter(tc.Filters...)
|
||||
if err != nil {
|
||||
t.Fatalf("%q: err: %s", n, err)
|
||||
}
|
||||
|
||||
actual := make([]string, len(results))
|
||||
for i, result := range results {
|
||||
actual[i] = result.String()
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, tc.Expected) {
|
||||
t.Fatalf("%q: expected, then actual\n\n%#v\n\n%#v", n, tc.Expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,122 @@
|
|||
{
|
||||
"version": 1,
|
||||
"serial": 12,
|
||||
"modules": [
|
||||
{
|
||||
"path": [
|
||||
"root"
|
||||
],
|
||||
"outputs": {
|
||||
"public_az1_subnet_id": "subnet-d658bba0",
|
||||
"region": "us-west-2",
|
||||
"vpc_cidr": "10.201.0.0/16",
|
||||
"vpc_id": "vpc-65814701"
|
||||
},
|
||||
"resources": {
|
||||
"aws_key_pair.onprem": {
|
||||
"type": "aws_key_pair",
|
||||
"primary": {
|
||||
"id": "onprem",
|
||||
"attributes": {
|
||||
"id": "onprem",
|
||||
"key_name": "onprem",
|
||||
"public_key": "foo"
|
||||
},
|
||||
"meta": {
|
||||
"schema_version": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"root",
|
||||
"bootstrap"
|
||||
],
|
||||
"outputs": {
|
||||
"consul_bootstrap_dns": "consul.bootstrap"
|
||||
},
|
||||
"resources": {
|
||||
"aws_route53_record.oasis-consul-bootstrap-a": {
|
||||
"type": "aws_route53_record",
|
||||
"depends_on": [
|
||||
"aws_route53_zone.oasis-consul-bootstrap"
|
||||
],
|
||||
"primary": {
|
||||
"id": "Z68734P5178QN_consul.bootstrap_A",
|
||||
"attributes": {
|
||||
"failover": "",
|
||||
"fqdn": "consul.bootstrap",
|
||||
"health_check_id": "",
|
||||
"id": "Z68734P5178QN_consul.bootstrap_A",
|
||||
"name": "consul.bootstrap",
|
||||
"records.#": "6",
|
||||
"records.1148461392": "10.201.3.8",
|
||||
"records.1169574759": "10.201.2.8",
|
||||
"records.1206973758": "10.201.1.8",
|
||||
"records.1275070284": "10.201.2.4",
|
||||
"records.1304587643": "10.201.3.4",
|
||||
"records.1313257749": "10.201.1.4",
|
||||
"set_identifier": "",
|
||||
"ttl": "300",
|
||||
"type": "A",
|
||||
"weight": "-1",
|
||||
"zone_id": "Z68734P5178QN"
|
||||
}
|
||||
}
|
||||
},
|
||||
"aws_route53_record.oasis-consul-bootstrap-ns": {
|
||||
"type": "aws_route53_record",
|
||||
"depends_on": [
|
||||
"aws_route53_zone.oasis-consul-bootstrap",
|
||||
"aws_route53_zone.oasis-consul-bootstrap",
|
||||
"aws_route53_zone.oasis-consul-bootstrap",
|
||||
"aws_route53_zone.oasis-consul-bootstrap",
|
||||
"aws_route53_zone.oasis-consul-bootstrap"
|
||||
],
|
||||
"primary": {
|
||||
"id": "Z68734P5178QN_consul.bootstrap_NS",
|
||||
"attributes": {
|
||||
"failover": "",
|
||||
"fqdn": "consul.bootstrap",
|
||||
"health_check_id": "",
|
||||
"id": "Z68734P5178QN_consul.bootstrap_NS",
|
||||
"name": "consul.bootstrap",
|
||||
"records.#": "4",
|
||||
"records.1796532126": "ns-512.awsdns-00.net.",
|
||||
"records.2728059479": "ns-1536.awsdns-00.co.uk.",
|
||||
"records.4092160370": "ns-1024.awsdns-00.org.",
|
||||
"records.456007465": "ns-0.awsdns-00.com.",
|
||||
"set_identifier": "",
|
||||
"ttl": "30",
|
||||
"type": "NS",
|
||||
"weight": "-1",
|
||||
"zone_id": "Z68734P5178QN"
|
||||
}
|
||||
}
|
||||
},
|
||||
"aws_route53_zone.oasis-consul-bootstrap": {
|
||||
"type": "aws_route53_zone",
|
||||
"primary": {
|
||||
"id": "Z68734P5178QN",
|
||||
"attributes": {
|
||||
"comment": "Used to bootstrap consul dns",
|
||||
"id": "Z68734P5178QN",
|
||||
"name": "consul.bootstrap",
|
||||
"name_servers.#": "4",
|
||||
"name_servers.0": "ns-0.awsdns-00.com.",
|
||||
"name_servers.1": "ns-1024.awsdns-00.org.",
|
||||
"name_servers.2": "ns-1536.awsdns-00.co.uk.",
|
||||
"name_servers.3": "ns-512.awsdns-00.net.",
|
||||
"tags.#": "0",
|
||||
"vpc_id": "vpc-65814701",
|
||||
"vpc_region": "us-west-2",
|
||||
"zone_id": "Z68734P5178QN"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -122,20 +122,11 @@ func (c *CLI) Run() (int, error) {
|
|||
return 1, nil
|
||||
}
|
||||
|
||||
// If there is an invalid flag, then error
|
||||
if len(c.topFlags) > 0 {
|
||||
c.HelpWriter.Write([]byte(
|
||||
"Invalid flags before the subcommand. If these flags are for\n" +
|
||||
"the subcommand, please put them after the subcommand.\n\n"))
|
||||
c.HelpWriter.Write([]byte(c.HelpFunc(c.Commands) + "\n"))
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
// Attempt to get the factory function for creating the command
|
||||
// implementation. If the command is invalid or blank, it is an error.
|
||||
raw, ok := c.commandTree.Get(c.Subcommand())
|
||||
if !ok {
|
||||
c.HelpWriter.Write([]byte(c.HelpFunc(c.Commands) + "\n"))
|
||||
c.HelpWriter.Write([]byte(c.HelpFunc(c.helpCommands(c.subcommandParent())) + "\n"))
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
|
@ -150,6 +141,15 @@ func (c *CLI) Run() (int, error) {
|
|||
return 1, nil
|
||||
}
|
||||
|
||||
// If there is an invalid flag, then error
|
||||
if len(c.topFlags) > 0 {
|
||||
c.HelpWriter.Write([]byte(
|
||||
"Invalid flags before the subcommand. If these flags are for\n" +
|
||||
"the subcommand, please put them after the subcommand.\n\n"))
|
||||
c.commandHelp(command)
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
code := command.Run(c.SubcommandArgs())
|
||||
if code == RunResultHelp {
|
||||
// Requesting help
|
||||
|
@ -175,6 +175,27 @@ func (c *CLI) SubcommandArgs() []string {
|
|||
return c.subcommandArgs
|
||||
}
|
||||
|
||||
// subcommandParent returns the parent of this subcommand, if there is one.
|
||||
// If there isn't on, "" is returned.
|
||||
func (c *CLI) subcommandParent() string {
|
||||
// Get the subcommand, if it is "" alread just return
|
||||
sub := c.Subcommand()
|
||||
if sub == "" {
|
||||
return sub
|
||||
}
|
||||
|
||||
// Clear any trailing spaces and find the last space
|
||||
sub = strings.TrimRight(sub, " ")
|
||||
idx := strings.LastIndex(sub, " ")
|
||||
|
||||
if idx == -1 {
|
||||
// No space means our parent is root
|
||||
return ""
|
||||
}
|
||||
|
||||
return sub[:idx]
|
||||
}
|
||||
|
||||
func (c *CLI) init() {
|
||||
if c.HelpFunc == nil {
|
||||
c.HelpFunc = BasicHelpFunc("app")
|
||||
|
@ -268,15 +289,14 @@ func (c *CLI) commandHelp(command Command) {
|
|||
}
|
||||
|
||||
// Build subcommand list if we have it
|
||||
var subcommands []map[string]interface{}
|
||||
var subcommandsTpl []map[string]interface{}
|
||||
if c.commandNested {
|
||||
// Get the matching keys
|
||||
var keys []string
|
||||
prefix := c.Subcommand() + " "
|
||||
c.commandTree.WalkPrefix(prefix, func(k string, raw interface{}) bool {
|
||||
subcommands := c.helpCommands(c.Subcommand())
|
||||
keys := make([]string, 0, len(subcommands))
|
||||
for k, _ := range subcommands {
|
||||
keys = append(keys, k)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
// Sort the keys
|
||||
sort.Strings(keys)
|
||||
|
@ -290,34 +310,30 @@ func (c *CLI) commandHelp(command Command) {
|
|||
}
|
||||
|
||||
// Go through and create their structures
|
||||
subcommands = make([]map[string]interface{}, len(keys))
|
||||
for i, k := range keys {
|
||||
raw, ok := c.commandTree.Get(k)
|
||||
if !ok {
|
||||
// We just checked that it should be here above. If it is
|
||||
// isn't, there are serious problems.
|
||||
panic("value is missing")
|
||||
}
|
||||
|
||||
subcommandsTpl = make([]map[string]interface{}, 0, len(subcommands))
|
||||
for k, raw := range subcommands {
|
||||
// Get the command
|
||||
sub, err := raw.(CommandFactory)()
|
||||
sub, err := raw()
|
||||
if err != nil {
|
||||
c.HelpWriter.Write([]byte(fmt.Sprintf(
|
||||
"Error instantiating %q: %s", k, err)))
|
||||
}
|
||||
|
||||
// Determine some info
|
||||
name := strings.TrimPrefix(k, prefix)
|
||||
// Find the last space and make sure we only include that last part
|
||||
name := k
|
||||
if idx := strings.LastIndex(k, " "); idx > -1 {
|
||||
name = name[idx+1:]
|
||||
}
|
||||
|
||||
subcommands[i] = map[string]interface{}{
|
||||
subcommandsTpl = append(subcommandsTpl, map[string]interface{}{
|
||||
"Name": name,
|
||||
"NameAligned": name + strings.Repeat(" ", longest-len(k)),
|
||||
"Help": sub.Help(),
|
||||
"Synopsis": sub.Synopsis(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
data["Subcommands"] = subcommands
|
||||
data["Subcommands"] = subcommandsTpl
|
||||
|
||||
// Write
|
||||
err = t.Execute(c.HelpWriter, data)
|
||||
|
@ -330,6 +346,40 @@ func (c *CLI) commandHelp(command Command) {
|
|||
"Internal error rendering help: %s", err)))
|
||||
}
|
||||
|
||||
// helpCommands returns the subcommands for the HelpFunc argument.
|
||||
// This will only contain immediate subcommands.
|
||||
func (c *CLI) helpCommands(prefix string) map[string]CommandFactory {
|
||||
// If our prefix isn't empty, make sure it ends in ' '
|
||||
if prefix != "" && prefix[len(prefix)-1] != ' ' {
|
||||
prefix += " "
|
||||
}
|
||||
|
||||
// Get all the subkeys of this command
|
||||
var keys []string
|
||||
c.commandTree.WalkPrefix(prefix, func(k string, raw interface{}) bool {
|
||||
// Ignore any sub-sub keys, i.e. "foo bar baz" when we want "foo bar"
|
||||
if !strings.Contains(k[len(prefix):], " ") {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
// For each of the keys return that in the map
|
||||
result := make(map[string]CommandFactory, len(keys))
|
||||
for _, k := range keys {
|
||||
raw, ok := c.commandTree.Get(k)
|
||||
if !ok {
|
||||
// We just got it via WalkPrefix above, so we just panic
|
||||
panic("not found: " + k)
|
||||
}
|
||||
|
||||
result[k] = raw.(CommandFactory)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *CLI) processArgs() {
|
||||
for i, arg := range c.Args {
|
||||
if c.subcommand == "" {
|
||||
|
|
|
@ -6,6 +6,7 @@ body.page-sub{
|
|||
background-color: $light-black;
|
||||
}
|
||||
|
||||
body.layout-commands-state,
|
||||
body.layout-atlas,
|
||||
body.layout-aws,
|
||||
body.layout-azure,
|
||||
|
|
|
@ -55,4 +55,3 @@ Usage: terraform graph [options] PATH
|
|||
read this format is GraphViz, but many web services are also available
|
||||
to read this format.
|
||||
```
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
layout: "commands-state"
|
||||
page_title: "Command: state resource addressing"
|
||||
sidebar_current: "docs-state-address"
|
||||
description: |-
|
||||
The `terraform state` command is used for advanced state management.
|
||||
---
|
||||
|
||||
# Resource Addressing
|
||||
|
||||
The `terraform state` subcommands make heavy use of resource addressing
|
||||
for targeting and filtering specific resources and modules within the state.
|
||||
|
||||
Resource addressing is a common feature of Terraform that is used in
|
||||
multiple locations. For example, resource addressing syntax is also used for
|
||||
the `-target` flag for apply and plan commands.
|
||||
|
||||
Because resource addressing is unified across Terraform, it is documented
|
||||
in a single place rather than duplicating it in multiple locations. You
|
||||
can find the [resource addressing documentation here](/docs/internals/resource-addressing.html).
|
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
layout: "commands-state"
|
||||
page_title: "Command: state"
|
||||
sidebar_current: "docs-state-index"
|
||||
description: |-
|
||||
The `terraform state` command is used for advanced state management.
|
||||
---
|
||||
|
||||
# State Command
|
||||
|
||||
The `terraform state` command is used for advanced state management.
|
||||
As your Terraform usage becomes more advanced, there are some cases where
|
||||
you may need to modify the [Terraform state](/docs/state/index.html).
|
||||
Rather than modify the state directly, the `terraform state` commands can
|
||||
be used in many cases instead.
|
||||
|
||||
This command is a nested subcommand, meaning that it has further subcommands.
|
||||
These subcommands are listed to the left.
|
||||
|
||||
## Usage
|
||||
|
||||
Usage: `terraform state <subcommand> [options] [args]`
|
||||
|
||||
Please click a subcommand to the left for more information.
|
||||
|
||||
## Remote State
|
||||
|
||||
The Terraform state subcommands all work with remote state just as if it
|
||||
was local state. Reads and writes may take longer than normal as each read
|
||||
and each write do a full network roundtrip. Otherwise, backups are still
|
||||
written to disk and the CLI usage is the same as if it were local state.
|
||||
|
||||
## Backups
|
||||
|
||||
All `terraform state` subcommands that modify the state write backup
|
||||
files. The path of these backup file can be controlled with `-backup`.
|
||||
|
||||
Subcommands that are read-only (such as [list](/docs/commands/state/list.html))
|
||||
do not write any backup files since they aren't modifying the state.
|
||||
|
||||
Note that backups for state modification _can not be disabled_. Due to
|
||||
the sensitivity of the state file, Terraform forces every state modification
|
||||
command to write a backup file. You'll have to remove these files manually
|
||||
if you don't want to keep them around.
|
||||
|
||||
## Command-Line Friendly
|
||||
|
||||
The output and command-line structure of the state subcommands is
|
||||
designed to be easy to use with Unix command-line tools such as grep, awk,
|
||||
etc. Consequently, the output is also friendly to the equivalent PowerShell
|
||||
commands within Windows.
|
||||
|
||||
For advanced filtering and modification, we recommend piping Terraform
|
||||
state subcommands together with other command line tools.
|
|
@ -0,0 +1,63 @@
|
|||
---
|
||||
layout: "commands-state"
|
||||
page_title: "Command: state list"
|
||||
sidebar_current: "docs-state-sub-list"
|
||||
description: |-
|
||||
The `terraform init` command is used to initialize a Terraform configuration using another module as a skeleton.
|
||||
---
|
||||
|
||||
# Command: state list
|
||||
|
||||
The `terraform state list` command is used to list resources within a
|
||||
[Terraform state](/docs/state/index.html).
|
||||
|
||||
## Usage
|
||||
|
||||
Usage: `terraform state list [options] [pattern...]`
|
||||
|
||||
The command will list all resources in the state file matching the given
|
||||
patterns (if any). If no patterns are given, all resources are listed.
|
||||
|
||||
The resources listed are sorted according to module depth order followed
|
||||
by alphabetical. This means that resources that are in your immediate
|
||||
configuration are listed first, and resources that are more deeply nested
|
||||
within modules are listed last.
|
||||
|
||||
For complex infrastructures, the state can contain thousands of resources.
|
||||
To filter these, provide one or more patterns to the command. Patterns are
|
||||
in [resource addressing format](/docs/commands/state/addressing.html).
|
||||
|
||||
The command-line flags are all optional. The list of available flags are:
|
||||
|
||||
* `-state=path` - Path to the state file. Defaults to "terraform.tfstate".
|
||||
|
||||
## Example: All Resources
|
||||
|
||||
This example will list all resources, including modules:
|
||||
|
||||
```
|
||||
$ terraform state list
|
||||
aws_instance.foo
|
||||
aws_instance.bar[0]
|
||||
aws_instance.bar[1]
|
||||
module.elb.aws_elb.main
|
||||
```
|
||||
|
||||
## Example: Filtering by Resource
|
||||
|
||||
This example will only list resources for the given name:
|
||||
|
||||
```
|
||||
$ terraform state list aws_instance.bar
|
||||
aws_instance.bar[0]
|
||||
aws_instance.bar[1]
|
||||
```
|
||||
|
||||
## Example: Filtering by Module
|
||||
|
||||
This example will only list resources in the given module:
|
||||
|
||||
```
|
||||
$ terraform state list module.elb
|
||||
module.elb.aws_elb.main
|
||||
```
|
|
@ -25,6 +25,23 @@ state file with the real infrastructure if the file didn't exist. But currently,
|
|||
Terraform state is a mixture of both a cache and required configuration and
|
||||
isn't optional.
|
||||
|
||||
## Inspection and Modification
|
||||
|
||||
While the format of the state files are just JSON, direct file editing
|
||||
of the state is discouraged. Terraform provides the
|
||||
[terraform state](/docs/commands/state/index.html) command to perform
|
||||
basic modifications of the state using the CLI.
|
||||
|
||||
The CLI usage and output of the state commands is structured to be
|
||||
friendly for Unix tools such as grep, awk, etc. Additionally, the CLI
|
||||
insulates users from any format changes within the state itself. The Terraform
|
||||
project will keep the CLI working while the state format underneath it may
|
||||
shift.
|
||||
|
||||
Finally, the CLI manages backups for you automatically. If you make a mistake
|
||||
modifying your state, the state CLI will always have a backup available for
|
||||
you that you can restore.
|
||||
|
||||
## Format
|
||||
|
||||
The state is in JSON format and Terraform will promise backwards compatibility
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<% wrap_layout :inner do %>
|
||||
<% content_for :sidebar do %>
|
||||
<div class="docs-sidebar hidden-print affix-top" role="complementary">
|
||||
<ul class="nav docs-sidenav">
|
||||
<li<%= sidebar_current("docs-home") %>>
|
||||
<a href="/docs/commands/index.html">« Documentation Home</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-state-index") %>>
|
||||
<a href="/docs/commands/state/index.html">State Command</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-state-address") %>>
|
||||
<a href="/docs/commands/state/addressing.html">Resource Addressing</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current(/^docs-state-sub/) %>>
|
||||
<a href="#">Subcommands</a>
|
||||
<ul class="nav nav-visible">
|
||||
<li<%= sidebar_current("docs-state-sub-list") %>>
|
||||
<a href="/docs/commands/state/list.html">list</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= yield %>
|
||||
<% end %>
|
|
@ -107,6 +107,10 @@
|
|||
<a href="/docs/commands/show.html">show</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-commands-state") %>>
|
||||
<a href="/docs/commands/state/index.html">state</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-commands-taint") %>>
|
||||
<a href="/docs/commands/taint.html">taint</a>
|
||||
</li>
|
||||
|
|
Loading…
Reference in New Issue