diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index a791ad472..190dae287 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -962,7 +962,7 @@ }, { "ImportPath": "github.com/mitchellh/cli", - "Rev": "cb6853d606ea4a12a15ac83cc43503df99fd28fb" + "Rev": "83f97d41cf100ee5f33944a8815c167d5e4aa272" }, { "ImportPath": "github.com/mitchellh/cloudflare-go", diff --git a/command/meta.go b/command/meta.go index bd4855964..db4c2a940 100644 --- a/command/meta.go +++ b/command/meta.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 } diff --git a/command/state_command.go b/command/state_command.go new file mode 100644 index 000000000..ce4e0a2ec --- /dev/null +++ b/command/state_command.go @@ -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 [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" +} diff --git a/command/state_list.go b/command/state_list.go new file mode 100644 index 000000000..daa96b684 --- /dev/null +++ b/command/state_list.go @@ -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.` diff --git a/command/state_list_test.go b/command/state_list_test.go new file mode 100644 index 000000000..0edb97d69 --- /dev/null +++ b/command/state_list_test.go @@ -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 +` diff --git a/commands.go b/commands.go index 903f965de..9ba629e4a 100644 --- a/commands.go +++ b/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 + }, } } diff --git a/help.go b/help.go new file mode 100644 index 000000000..b621f710b --- /dev/null +++ b/help.go @@ -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] [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() +} diff --git a/main.go b/main.go index 7e47e7875..5f74281a3 100644 --- a/main.go +++ b/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, } diff --git a/terraform/resource_address.go b/terraform/resource_address.go index d87f645ae..46b47aa2c 100644 --- a/terraform/resource_address.go +++ b/terraform/resource_address.go @@ -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 { diff --git a/terraform/resource_address_test.go b/terraform/resource_address_test.go index 03e762a74..6fee98d1f 100644 --- a/terraform/resource_address_test.go +++ b/terraform/resource_address_test.go @@ -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) + } } } diff --git a/terraform/state_filter.go b/terraform/state_filter.go new file mode 100644 index 000000000..cb485416d --- /dev/null +++ b/terraform/state_filter.go @@ -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() +} diff --git a/terraform/state_filter_test.go b/terraform/state_filter_test.go new file mode 100644 index 000000000..f58677a95 --- /dev/null +++ b/terraform/state_filter_test.go @@ -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) + } + } +} diff --git a/terraform/test-fixtures/state-filter/complete.tfstate b/terraform/test-fixtures/state-filter/complete.tfstate new file mode 100644 index 000000000..587243002 --- /dev/null +++ b/terraform/test-fixtures/state-filter/complete.tfstate @@ -0,0 +1,1311 @@ +{ + "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" + } + } + } + } + }, + { + "path": [ + "root", + "consul" + ], + "outputs": { + "consul_ips": "10.201.1.8,10.201.2.8,10.201.3.8,", + "security_group_id": "sg-6c4d2f0b" + }, + "resources": { + "aws_instance.consul-green.0": { + "type": "aws_instance", + "depends_on": [ + "aws_security_group.consul" + ], + "primary": { + "id": "i-6dc2acb5", + "attributes": { + "ami": "ami-abcd1234", + "availability_zone": "us-west-2a", + "ebs_block_device.#": "0", + "ebs_optimized": "false", + "ephemeral_block_device.#": "0", + "iam_instance_profile": "", + "id": "i-6dc2acb5", + "instance_state": "running", + "instance_type": "t2.small", + "key_name": "onprem", + "monitoring": "false", + "private_dns": "ip-10-201-1-8.us-west-2.compute.internal", + "private_ip": "10.201.1.8", + "public_dns": "", + "public_ip": "", + "root_block_device.#": "1", + "root_block_device.0.delete_on_termination": "true", + "root_block_device.0.iops": "24", + "root_block_device.0.volume_size": "8", + "root_block_device.0.volume_type": "gp2", + "security_groups.#": "0", + "source_dest_check": "true", + "subnet_id": "subnet-d558bba3", + "tags.#": "1", + "tags.Name": "onprem-consul", + "tenancy": "default", + "user_data": "daea808a0010d9ab14d862878905052ee9e3fe55", + "vpc_security_group_ids.#": "1", + "vpc_security_group_ids.753260136": "sg-6c4d2f0b" + }, + "meta": { + "schema_version": "1" + } + } + }, + "aws_instance.consul-green.1": { + "type": "aws_instance", + "depends_on": [ + "aws_security_group.consul" + ], + "primary": { + "id": "i-59bde69e", + "attributes": { + "ami": "ami-abcd1234", + "availability_zone": "us-west-2b", + "ebs_block_device.#": "0", + "ebs_optimized": "false", + "ephemeral_block_device.#": "0", + "iam_instance_profile": "", + "id": "i-59bde69e", + "instance_state": "running", + "instance_type": "t2.small", + "key_name": "onprem", + "monitoring": "false", + "private_dns": "ip-10-201-2-8.us-west-2.compute.internal", + "private_ip": "10.201.2.8", + "public_dns": "", + "public_ip": "", + "root_block_device.#": "1", + "root_block_device.0.delete_on_termination": "true", + "root_block_device.0.iops": "24", + "root_block_device.0.volume_size": "8", + "root_block_device.0.volume_type": "gp2", + "security_groups.#": "0", + "source_dest_check": "true", + "subnet_id": "subnet-984f81fc", + "tags.#": "1", + "tags.Name": "onprem-consul", + "tenancy": "default", + "user_data": "daea808a0010d9ab14d862878905052ee9e3fe55", + "vpc_security_group_ids.#": "1", + "vpc_security_group_ids.753260136": "sg-6c4d2f0b" + }, + "meta": { + "schema_version": "1" + } + } + }, + "aws_instance.consul-green.2": { + "type": "aws_instance", + "depends_on": [ + "aws_security_group.consul" + ], + "primary": { + "id": "i-24d5e9fe", + "attributes": { + "ami": "ami-abcd1234", + "availability_zone": "us-west-2c", + "ebs_block_device.#": "0", + "ebs_optimized": "false", + "ephemeral_block_device.#": "0", + "iam_instance_profile": "", + "id": "i-24d5e9fe", + "instance_state": "running", + "instance_type": "t2.small", + "key_name": "onprem", + "monitoring": "false", + "private_dns": "ip-10-201-3-8.us-west-2.compute.internal", + "private_ip": "10.201.3.8", + "public_dns": "", + "public_ip": "", + "root_block_device.#": "1", + "root_block_device.0.delete_on_termination": "true", + "root_block_device.0.iops": "24", + "root_block_device.0.volume_size": "8", + "root_block_device.0.volume_type": "gp2", + "security_groups.#": "0", + "source_dest_check": "true", + "subnet_id": "subnet-776d532e", + "tags.#": "1", + "tags.Name": "onprem-consul", + "tenancy": "default", + "user_data": "daea808a0010d9ab14d862878905052ee9e3fe55", + "vpc_security_group_ids.#": "1", + "vpc_security_group_ids.753260136": "sg-6c4d2f0b" + }, + "meta": { + "schema_version": "1" + } + } + }, + "aws_security_group.consul": { + "type": "aws_security_group", + "primary": { + "id": "sg-6c4d2f0b", + "attributes": { + "description": "Managed by Terraform", + "egress.#": "1", + "egress.482069346.cidr_blocks.#": "1", + "egress.482069346.cidr_blocks.0": "0.0.0.0/0", + "egress.482069346.from_port": "0", + "egress.482069346.protocol": "-1", + "egress.482069346.security_groups.#": "0", + "egress.482069346.self": "false", + "egress.482069346.to_port": "0", + "id": "sg-6c4d2f0b", + "ingress.#": "1", + "ingress.3832255922.cidr_blocks.#": "2", + "ingress.3832255922.cidr_blocks.0": "10.201.0.0/16", + "ingress.3832255922.cidr_blocks.1": "127.0.0.1/32", + "ingress.3832255922.from_port": "0", + "ingress.3832255922.protocol": "-1", + "ingress.3832255922.security_groups.#": "0", + "ingress.3832255922.self": "false", + "ingress.3832255922.to_port": "0", + "name": "onprem-consul", + "owner_id": "209146746714", + "tags.#": "0", + "vpc_id": "vpc-65814701" + } + } + } + } + }, + { + "path": [ + "root", + "network-core" + ], + "outputs": { + "private_az1_subnet_id": "subnet-d558bba3", + "private_az2_subnet_id": "subnet-984f81fc", + "private_az3_subnet_id": "subnet-776d532e", + "public_az1_subnet_id": "subnet-d658bba0", + "public_az2_subnet_id": "subnet-9f4f81fb", + "public_az3_subnet_id": "subnet-756d532c", + "vpc_cidr": "10.201.0.0/16", + "vpc_id": "vpc-65814701" + }, + "resources": {} + }, + { + "path": [ + "root", + "vault" + ], + "outputs": { + "dns_name": "internal-onprem-vault-2015291251.us-west-2.elb.amazonaws.com", + "private_ips": "10.201.1.145,10.201.2.191,10.201.3.230" + }, + "resources": { + "aws_elb.vault": { + "type": "aws_elb", + "depends_on": [ + "aws_instance.vault", + "aws_security_group.elb" + ], + "primary": { + "id": "onprem-vault", + "attributes": { + "access_logs.#": "0", + "availability_zones.#": "3", + "availability_zones.2050015877": "us-west-2c", + "availability_zones.221770259": "us-west-2b", + "availability_zones.2487133097": "us-west-2a", + "connection_draining": "true", + "connection_draining_timeout": "400", + "cross_zone_load_balancing": "true", + "dns_name": "internal-onprem-vault-2015291251.us-west-2.elb.amazonaws.com", + "health_check.#": "1", + "health_check.4162994118.healthy_threshold": "2", + "health_check.4162994118.interval": "15", + "health_check.4162994118.target": "HTTPS:8200/v1/sys/health", + "health_check.4162994118.timeout": "5", + "health_check.4162994118.unhealthy_threshold": "3", + "id": "onprem-vault", + "idle_timeout": "60", + "instances.#": "3", + "instances.1694111637": "i-b6d5e96c", + "instances.237539873": "i-11bee5d6", + "instances.3767473091": "i-f3c2ac2b", + "internal": "true", + "listener.#": "2", + "listener.2355508663.instance_port": "8200", + "listener.2355508663.instance_protocol": "tcp", + "listener.2355508663.lb_port": "443", + "listener.2355508663.lb_protocol": "tcp", + "listener.2355508663.ssl_certificate_id": "", + "listener.3383204430.instance_port": "8200", + "listener.3383204430.instance_protocol": "tcp", + "listener.3383204430.lb_port": "80", + "listener.3383204430.lb_protocol": "tcp", + "listener.3383204430.ssl_certificate_id": "", + "name": "onprem-vault", + "security_groups.#": "1", + "security_groups.4254461258": "sg-6b4d2f0c", + "source_security_group": "onprem-vault-elb", + "source_security_group_id": "sg-6b4d2f0c", + "subnets.#": "3", + "subnets.1994053001": "subnet-d658bba0", + "subnets.3216774672": "subnet-756d532c", + "subnets.3611140374": "subnet-9f4f81fb", + "tags.#": "0", + "zone_id": "Z33MTJ483KN6FU" + } + } + }, + "aws_instance.vault.0": { + "type": "aws_instance", + "depends_on": [ + "aws_security_group.vault", + "template_cloudinit_config.config" + ], + "primary": { + "id": "i-f3c2ac2b", + "attributes": { + "ami": "ami-abcd1234", + "availability_zone": "us-west-2a", + "ebs_block_device.#": "0", + "ebs_optimized": "false", + "ephemeral_block_device.#": "0", + "iam_instance_profile": "", + "id": "i-f3c2ac2b", + "instance_state": "running", + "instance_type": "t2.small", + "key_name": "onprem", + "monitoring": "false", + "private_dns": "ip-10-201-1-145.us-west-2.compute.internal", + "private_ip": "10.201.1.145", + "public_dns": "", + "public_ip": "", + "root_block_device.#": "1", + "root_block_device.0.delete_on_termination": "true", + "root_block_device.0.iops": "24", + "root_block_device.0.volume_size": "8", + "root_block_device.0.volume_type": "gp2", + "security_groups.#": "0", + "source_dest_check": "true", + "subnet_id": "subnet-d558bba3", + "tags.#": "1", + "tags.Name": "onprem-vault - 0", + "tenancy": "default", + "user_data": "423b5c91392a6b2ac287a118fcdad0aadaeffd48", + "vpc_security_group_ids.#": "1", + "vpc_security_group_ids.1377395316": "sg-6a4d2f0d" + }, + "meta": { + "schema_version": "1" + } + } + }, + "aws_instance.vault.1": { + "type": "aws_instance", + "depends_on": [ + "aws_security_group.vault", + "template_cloudinit_config.config" + ], + "primary": { + "id": "i-11bee5d6", + "attributes": { + "ami": "ami-abcd1234", + "availability_zone": "us-west-2b", + "ebs_block_device.#": "0", + "ebs_optimized": "false", + "ephemeral_block_device.#": "0", + "iam_instance_profile": "", + "id": "i-11bee5d6", + "instance_state": "running", + "instance_type": "t2.small", + "key_name": "onprem", + "monitoring": "false", + "private_dns": "ip-10-201-2-191.us-west-2.compute.internal", + "private_ip": "10.201.2.191", + "public_dns": "", + "public_ip": "", + "root_block_device.#": "1", + "root_block_device.0.delete_on_termination": "true", + "root_block_device.0.iops": "24", + "root_block_device.0.volume_size": "8", + "root_block_device.0.volume_type": "gp2", + "security_groups.#": "0", + "source_dest_check": "true", + "subnet_id": "subnet-984f81fc", + "tags.#": "1", + "tags.Name": "onprem-vault - 1", + "tenancy": "default", + "user_data": "de5ec79c02b721123a7c2a1622257b425aa26e61", + "vpc_security_group_ids.#": "1", + "vpc_security_group_ids.1377395316": "sg-6a4d2f0d" + }, + "meta": { + "schema_version": "1" + } + } + }, + "aws_instance.vault.2": { + "type": "aws_instance", + "depends_on": [ + "aws_security_group.vault", + "template_cloudinit_config.config" + ], + "primary": { + "id": "i-b6d5e96c", + "attributes": { + "ami": "ami-abcd1234", + "availability_zone": "us-west-2c", + "ebs_block_device.#": "0", + "ebs_optimized": "false", + "ephemeral_block_device.#": "0", + "iam_instance_profile": "", + "id": "i-b6d5e96c", + "instance_state": "running", + "instance_type": "t2.small", + "key_name": "onprem", + "monitoring": "false", + "private_dns": "ip-10-201-3-230.us-west-2.compute.internal", + "private_ip": "10.201.3.230", + "public_dns": "", + "public_ip": "", + "root_block_device.#": "1", + "root_block_device.0.delete_on_termination": "true", + "root_block_device.0.iops": "24", + "root_block_device.0.volume_size": "8", + "root_block_device.0.volume_type": "gp2", + "security_groups.#": "0", + "source_dest_check": "true", + "subnet_id": "subnet-776d532e", + "tags.#": "1", + "tags.Name": "onprem-vault - 2", + "tenancy": "default", + "user_data": "7ecdafc11c715866578ab5441bb27abbae97c850", + "vpc_security_group_ids.#": "1", + "vpc_security_group_ids.1377395316": "sg-6a4d2f0d" + }, + "meta": { + "schema_version": "1" + } + } + }, + "aws_security_group.elb": { + "type": "aws_security_group", + "primary": { + "id": "sg-6b4d2f0c", + "attributes": { + "description": "Managed by Terraform", + "egress.#": "1", + "egress.482069346.cidr_blocks.#": "1", + "egress.482069346.cidr_blocks.0": "0.0.0.0/0", + "egress.482069346.from_port": "0", + "egress.482069346.protocol": "-1", + "egress.482069346.security_groups.#": "0", + "egress.482069346.self": "false", + "egress.482069346.to_port": "0", + "id": "sg-6b4d2f0c", + "ingress.#": "2", + "ingress.2915022413.cidr_blocks.#": "1", + "ingress.2915022413.cidr_blocks.0": "10.201.0.0/16", + "ingress.2915022413.from_port": "80", + "ingress.2915022413.protocol": "tcp", + "ingress.2915022413.security_groups.#": "0", + "ingress.2915022413.self": "false", + "ingress.2915022413.to_port": "80", + "ingress.382081576.cidr_blocks.#": "1", + "ingress.382081576.cidr_blocks.0": "10.201.0.0/16", + "ingress.382081576.from_port": "443", + "ingress.382081576.protocol": "tcp", + "ingress.382081576.security_groups.#": "0", + "ingress.382081576.self": "false", + "ingress.382081576.to_port": "443", + "name": "onprem-vault-elb", + "owner_id": "209146746714", + "tags.#": "0", + "vpc_id": "vpc-65814701" + } + } + }, + "aws_security_group.vault": { + "type": "aws_security_group", + "primary": { + "id": "sg-6a4d2f0d", + "attributes": { + "description": "Managed by Terraform", + "egress.#": "1", + "egress.482069346.cidr_blocks.#": "1", + "egress.482069346.cidr_blocks.0": "0.0.0.0/0", + "egress.482069346.from_port": "0", + "egress.482069346.protocol": "-1", + "egress.482069346.security_groups.#": "0", + "egress.482069346.self": "false", + "egress.482069346.to_port": "0", + "id": "sg-6a4d2f0d", + "ingress.#": "1", + "ingress.2546146930.cidr_blocks.#": "1", + "ingress.2546146930.cidr_blocks.0": "10.201.0.0/16", + "ingress.2546146930.from_port": "0", + "ingress.2546146930.protocol": "-1", + "ingress.2546146930.security_groups.#": "0", + "ingress.2546146930.self": "false", + "ingress.2546146930.to_port": "0", + "name": "onprem-vault", + "owner_id": "209146746714", + "tags.#": "0", + "vpc_id": "vpc-65814701" + } + } + } + } + }, + { + "path": [ + "root", + "network-core", + "igw" + ], + "outputs": { + "id": "igw-d06c48b5" + }, + "resources": { + "aws_internet_gateway.main_igw": { + "type": "aws_internet_gateway", + "primary": { + "id": "igw-d06c48b5", + "attributes": { + "id": "igw-d06c48b5", + "vpc_id": "vpc-65814701" + } + } + } + } + }, + { + "path": [ + "root", + "network-core", + "private-subnets" + ], + "outputs": { + "az1_subnet_id": "subnet-d558bba3", + "az2_subnet_id": "subnet-984f81fc", + "az3_subnet_id": "subnet-776d532e" + }, + "resources": { + "aws_subnet.subnet_az1_private": { + "type": "aws_subnet", + "primary": { + "id": "subnet-d558bba3", + "attributes": { + "availability_zone": "us-west-2a", + "cidr_block": "10.201.1.0/24", + "id": "subnet-d558bba3", + "map_public_ip_on_launch": "false", + "tags.#": "1", + "tags.Name": "onprem-private", + "vpc_id": "vpc-65814701" + } + } + }, + "aws_subnet.subnet_az2_private": { + "type": "aws_subnet", + "primary": { + "id": "subnet-984f81fc", + "attributes": { + "availability_zone": "us-west-2b", + "cidr_block": "10.201.2.0/24", + "id": "subnet-984f81fc", + "map_public_ip_on_launch": "false", + "tags.#": "1", + "tags.Name": "onprem-private", + "vpc_id": "vpc-65814701" + } + } + }, + "aws_subnet.subnet_az3_private": { + "type": "aws_subnet", + "primary": { + "id": "subnet-776d532e", + "attributes": { + "availability_zone": "us-west-2c", + "cidr_block": "10.201.3.0/24", + "id": "subnet-776d532e", + "map_public_ip_on_launch": "false", + "tags.#": "1", + "tags.Name": "onprem-private", + "vpc_id": "vpc-65814701" + } + } + } + } + }, + { + "path": [ + "root", + "network-core", + "public-subnets" + ], + "outputs": { + "az1_nat_gateway_id": "nat-05ca7f2d5f1f96693", + "az1_subnet_id": "subnet-d658bba0", + "az2_nat_gateway_id": "nat-03223582301f75a08", + "az2_subnet_id": "subnet-9f4f81fb", + "az3_nat_gateway_id": "nat-0f2710d577d3f32ee", + "az3_subnet_id": "subnet-756d532c" + }, + "resources": { + "aws_eip.eip_nat_az1": { + "type": "aws_eip", + "primary": { + "id": "eipalloc-5f7bd73b", + "attributes": { + "association_id": "", + "domain": "vpc", + "id": "eipalloc-5f7bd73b", + "instance": "", + "network_interface": "", + "private_ip": "", + "public_ip": "52.37.99.10", + "vpc": "true" + } + } + }, + "aws_eip.eip_nat_az2": { + "type": "aws_eip", + "primary": { + "id": "eipalloc-927bd7f6", + "attributes": { + "association_id": "", + "domain": "vpc", + "id": "eipalloc-927bd7f6", + "instance": "", + "network_interface": "", + "private_ip": "", + "public_ip": "52.36.32.86", + "vpc": "true" + } + } + }, + "aws_eip.eip_nat_az3": { + "type": "aws_eip", + "primary": { + "id": "eipalloc-fe76da9a", + "attributes": { + "association_id": "", + "domain": "vpc", + "id": "eipalloc-fe76da9a", + "instance": "", + "network_interface": "", + "private_ip": "", + "public_ip": "52.25.71.124", + "vpc": "true" + } + } + }, + "aws_nat_gateway.nat_gw_az1": { + "type": "aws_nat_gateway", + "depends_on": [ + "aws_eip.eip_nat_az1", + "aws_subnet.subnet_az1_public" + ], + "primary": { + "id": "nat-05ca7f2d5f1f96693", + "attributes": { + "allocation_id": "eipalloc-5f7bd73b", + "id": "nat-05ca7f2d5f1f96693", + "network_interface_id": "eni-c3ff6089", + "private_ip": "10.201.101.229", + "public_ip": "52.37.99.10", + "subnet_id": "subnet-d658bba0" + } + } + }, + "aws_nat_gateway.nat_gw_az2": { + "type": "aws_nat_gateway", + "depends_on": [ + "aws_eip.eip_nat_az2", + "aws_subnet.subnet_az2_public" + ], + "primary": { + "id": "nat-03223582301f75a08", + "attributes": { + "allocation_id": "eipalloc-927bd7f6", + "id": "nat-03223582301f75a08", + "network_interface_id": "eni-db22f0a0", + "private_ip": "10.201.102.214", + "public_ip": "52.36.32.86", + "subnet_id": "subnet-9f4f81fb" + } + } + }, + "aws_nat_gateway.nat_gw_az3": { + "type": "aws_nat_gateway", + "depends_on": [ + "aws_eip.eip_nat_az3", + "aws_subnet.subnet_az3_public" + ], + "primary": { + "id": "nat-0f2710d577d3f32ee", + "attributes": { + "allocation_id": "eipalloc-fe76da9a", + "id": "nat-0f2710d577d3f32ee", + "network_interface_id": "eni-e0cd4dbd", + "private_ip": "10.201.103.58", + "public_ip": "52.25.71.124", + "subnet_id": "subnet-756d532c" + } + } + }, + "aws_route_table.route_table_public": { + "type": "aws_route_table", + "primary": { + "id": "rtb-838f29e7", + "attributes": { + "id": "rtb-838f29e7", + "propagating_vgws.#": "0", + "route.#": "1", + "route.1250083285.cidr_block": "0.0.0.0/0", + "route.1250083285.gateway_id": "igw-d06c48b5", + "route.1250083285.instance_id": "", + "route.1250083285.nat_gateway_id": "", + "route.1250083285.network_interface_id": "", + "route.1250083285.vpc_peering_connection_id": "", + "tags.#": "1", + "tags.Name": "onprem-public", + "vpc_id": "vpc-65814701" + } + } + }, + "aws_route_table_association.route_table_az1": { + "type": "aws_route_table_association", + "depends_on": [ + "aws_route_table.route_table_public", + "aws_subnet.subnet_az1_public" + ], + "primary": { + "id": "rtbassoc-a5d6abc1", + "attributes": { + "id": "rtbassoc-a5d6abc1", + "route_table_id": "rtb-838f29e7", + "subnet_id": "subnet-d658bba0" + } + } + }, + "aws_route_table_association.route_table_az2": { + "type": "aws_route_table_association", + "depends_on": [ + "aws_route_table.route_table_public", + "aws_subnet.subnet_az2_public" + ], + "primary": { + "id": "rtbassoc-a0d6abc4", + "attributes": { + "id": "rtbassoc-a0d6abc4", + "route_table_id": "rtb-838f29e7", + "subnet_id": "subnet-9f4f81fb" + } + } + }, + "aws_route_table_association.route_table_az3": { + "type": "aws_route_table_association", + "depends_on": [ + "aws_route_table.route_table_public", + "aws_subnet.subnet_az3_public" + ], + "primary": { + "id": "rtbassoc-a7d6abc3", + "attributes": { + "id": "rtbassoc-a7d6abc3", + "route_table_id": "rtb-838f29e7", + "subnet_id": "subnet-756d532c" + } + } + }, + "aws_subnet.subnet_az1_public": { + "type": "aws_subnet", + "primary": { + "id": "subnet-d658bba0", + "attributes": { + "availability_zone": "us-west-2a", + "cidr_block": "10.201.101.0/24", + "id": "subnet-d658bba0", + "map_public_ip_on_launch": "true", + "tags.#": "1", + "tags.Name": "onprem-public", + "vpc_id": "vpc-65814701" + } + } + }, + "aws_subnet.subnet_az2_public": { + "type": "aws_subnet", + "primary": { + "id": "subnet-9f4f81fb", + "attributes": { + "availability_zone": "us-west-2b", + "cidr_block": "10.201.102.0/24", + "id": "subnet-9f4f81fb", + "map_public_ip_on_launch": "true", + "tags.#": "1", + "tags.Name": "onprem-public", + "vpc_id": "vpc-65814701" + } + } + }, + "aws_subnet.subnet_az3_public": { + "type": "aws_subnet", + "primary": { + "id": "subnet-756d532c", + "attributes": { + "availability_zone": "us-west-2c", + "cidr_block": "10.201.103.0/24", + "id": "subnet-756d532c", + "map_public_ip_on_launch": "true", + "tags.#": "1", + "tags.Name": "onprem-public", + "vpc_id": "vpc-65814701" + } + } + } + } + }, + { + "path": [ + "root", + "network-core", + "restricted-subnets" + ], + "outputs": { + "az1_subnet_id": "subnet-d758bba1", + "az2_subnet_id": "subnet-994f81fd", + "az3_subnet_id": "subnet-746d532d" + }, + "resources": { + "aws_subnet.subnet_az1_private": { + "type": "aws_subnet", + "primary": { + "id": "subnet-d758bba1", + "attributes": { + "availability_zone": "us-west-2a", + "cidr_block": "10.201.220.0/24", + "id": "subnet-d758bba1", + "map_public_ip_on_launch": "false", + "tags.#": "1", + "tags.Name": "onprem-restricted", + "vpc_id": "vpc-65814701" + } + } + }, + "aws_subnet.subnet_az2_private": { + "type": "aws_subnet", + "primary": { + "id": "subnet-994f81fd", + "attributes": { + "availability_zone": "us-west-2b", + "cidr_block": "10.201.221.0/24", + "id": "subnet-994f81fd", + "map_public_ip_on_launch": "false", + "tags.#": "1", + "tags.Name": "onprem-restricted", + "vpc_id": "vpc-65814701" + } + } + }, + "aws_subnet.subnet_az3_private": { + "type": "aws_subnet", + "primary": { + "id": "subnet-746d532d", + "attributes": { + "availability_zone": "us-west-2c", + "cidr_block": "10.201.222.0/24", + "id": "subnet-746d532d", + "map_public_ip_on_launch": "false", + "tags.#": "1", + "tags.Name": "onprem-restricted", + "vpc_id": "vpc-65814701" + } + } + } + } + }, + { + "path": [ + "root", + "network-core", + "routing-private" + ], + "outputs": { + "az1_route_table_id": "rtb-828f29e6", + "az2_route_table_id": "rtb-808f29e4", + "az3_route_table_id": "rtb-818f29e5" + }, + "resources": { + "aws_route.route_table_az1_private_default": { + "type": "aws_route", + "depends_on": [ + "aws_route_table.route_table_az1_private", + "aws_route_table.route_table_az1_private" + ], + "primary": { + "id": "r-rtb-828f29e61080289494", + "attributes": { + "destination_cidr_block": "0.0.0.0/0", + "destination_prefix_list_id": "", + "gateway_id": "", + "id": "r-rtb-828f29e61080289494", + "instance_id": "", + "instance_owner_id": "", + "nat_gateway_id": "nat-05ca7f2d5f1f96693", + "network_interface_id": "", + "origin": "CreateRoute", + "route_table_id": "rtb-828f29e6", + "state": "active", + "vpc_peering_connection_id": "" + } + } + }, + "aws_route.route_table_az2_private_default": { + "type": "aws_route", + "depends_on": [ + "aws_route_table.route_table_az2_private", + "aws_route_table.route_table_az2_private" + ], + "primary": { + "id": "r-rtb-808f29e41080289494", + "attributes": { + "destination_cidr_block": "0.0.0.0/0", + "destination_prefix_list_id": "", + "gateway_id": "", + "id": "r-rtb-808f29e41080289494", + "instance_id": "", + "instance_owner_id": "", + "nat_gateway_id": "nat-03223582301f75a08", + "network_interface_id": "", + "origin": "CreateRoute", + "route_table_id": "rtb-808f29e4", + "state": "active", + "vpc_peering_connection_id": "" + } + } + }, + "aws_route.route_table_az3_private_default": { + "type": "aws_route", + "depends_on": [ + "aws_route_table.route_table_az3_private", + "aws_route_table.route_table_az3_private" + ], + "primary": { + "id": "r-rtb-818f29e51080289494", + "attributes": { + "destination_cidr_block": "0.0.0.0/0", + "destination_prefix_list_id": "", + "gateway_id": "", + "id": "r-rtb-818f29e51080289494", + "instance_id": "", + "instance_owner_id": "", + "nat_gateway_id": "nat-0f2710d577d3f32ee", + "network_interface_id": "", + "origin": "CreateRoute", + "route_table_id": "rtb-818f29e5", + "state": "active", + "vpc_peering_connection_id": "" + } + } + }, + "aws_route_table.route_table_az1_private": { + "type": "aws_route_table", + "primary": { + "id": "rtb-828f29e6", + "attributes": { + "id": "rtb-828f29e6", + "propagating_vgws.#": "0", + "route.#": "0", + "tags.#": "1", + "tags.Name": "onprem-routing-private", + "vpc_id": "vpc-65814701" + } + } + }, + "aws_route_table.route_table_az2_private": { + "type": "aws_route_table", + "primary": { + "id": "rtb-808f29e4", + "attributes": { + "id": "rtb-808f29e4", + "propagating_vgws.#": "0", + "route.#": "0", + "tags.#": "1", + "tags.Name": "onprem-routing-private", + "vpc_id": "vpc-65814701" + } + } + }, + "aws_route_table.route_table_az3_private": { + "type": "aws_route_table", + "primary": { + "id": "rtb-818f29e5", + "attributes": { + "id": "rtb-818f29e5", + "propagating_vgws.#": "0", + "route.#": "0", + "tags.#": "1", + "tags.Name": "onprem-routing-private", + "vpc_id": "vpc-65814701" + } + } + }, + "aws_route_table_association.route_table_az1": { + "type": "aws_route_table_association", + "depends_on": [ + "aws_route_table.route_table_az1_private", + "aws_route_table.route_table_az1_private" + ], + "primary": { + "id": "rtbassoc-a4d6abc0", + "attributes": { + "id": "rtbassoc-a4d6abc0", + "route_table_id": "rtb-828f29e6", + "subnet_id": "subnet-d558bba3" + } + } + }, + "aws_route_table_association.route_table_az2": { + "type": "aws_route_table_association", + "depends_on": [ + "aws_route_table.route_table_az2_private", + "aws_route_table.route_table_az2_private" + ], + "primary": { + "id": "rtbassoc-d9d6abbd", + "attributes": { + "id": "rtbassoc-d9d6abbd", + "route_table_id": "rtb-808f29e4", + "subnet_id": "subnet-984f81fc" + } + } + }, + "aws_route_table_association.route_table_az3": { + "type": "aws_route_table_association", + "depends_on": [ + "aws_route_table.route_table_az3_private", + "aws_route_table.route_table_az3_private" + ], + "primary": { + "id": "rtbassoc-dbd6abbf", + "attributes": { + "id": "rtbassoc-dbd6abbf", + "route_table_id": "rtb-818f29e5", + "subnet_id": "subnet-776d532e" + } + } + }, + "aws_vpc_endpoint.vpe_s3_az1_private": { + "type": "aws_vpc_endpoint", + "depends_on": [ + "aws_route_table.route_table_az1_private", + "aws_route_table.route_table_az1_private" + ], + "primary": { + "id": "vpce-94e70afd", + "attributes": { + "id": "vpce-94e70afd", + "policy": "{\"Statement\":[{\"Action\":\"*\",\"Effect\":\"Allow\",\"Principal\":\"*\",\"Resource\":\"*\",\"Sid\":\"\"}],\"Version\":\"2008-10-17\"}", + "route_table_ids.#": "1", + "route_table_ids.1792300572": "rtb-828f29e6", + "service_name": "com.amazonaws.us-west-2.s3", + "vpc_id": "vpc-65814701" + } + } + }, + "aws_vpc_endpoint.vpe_s3_az2_private": { + "type": "aws_vpc_endpoint", + "depends_on": [ + "aws_route_table.route_table_az2_private", + "aws_route_table.route_table_az2_private" + ], + "primary": { + "id": "vpce-95e70afc", + "attributes": { + "id": "vpce-95e70afc", + "policy": "{\"Statement\":[{\"Action\":\"*\",\"Effect\":\"Allow\",\"Principal\":\"*\",\"Resource\":\"*\",\"Sid\":\"\"}],\"Version\":\"2008-10-17\"}", + "route_table_ids.#": "1", + "route_table_ids.323298841": "rtb-808f29e4", + "service_name": "com.amazonaws.us-west-2.s3", + "vpc_id": "vpc-65814701" + } + } + }, + "aws_vpc_endpoint.vpe_s3_az3_private": { + "type": "aws_vpc_endpoint", + "depends_on": [ + "aws_route_table.route_table_az3_private", + "aws_route_table.route_table_az3_private" + ], + "primary": { + "id": "vpce-97e70afe", + "attributes": { + "id": "vpce-97e70afe", + "policy": "{\"Statement\":[{\"Action\":\"*\",\"Effect\":\"Allow\",\"Principal\":\"*\",\"Resource\":\"*\",\"Sid\":\"\"}],\"Version\":\"2008-10-17\"}", + "route_table_ids.#": "1", + "route_table_ids.3258260795": "rtb-818f29e5", + "service_name": "com.amazonaws.us-west-2.s3", + "vpc_id": "vpc-65814701" + } + } + } + } + }, + { + "path": [ + "root", + "network-core", + "routing-restricted" + ], + "outputs": {}, + "resources": { + "aws_route_table.route_table_az1_restricted": { + "type": "aws_route_table", + "primary": { + "id": "rtb-428c2a26", + "attributes": { + "id": "rtb-428c2a26", + "propagating_vgws.#": "0", + "route.#": "1", + "route.1020029083.cidr_block": "0.0.0.0/0", + "route.1020029083.gateway_id": "", + "route.1020029083.instance_id": "", + "route.1020029083.nat_gateway_id": "nat-05ca7f2d5f1f96693", + "route.1020029083.network_interface_id": "", + "route.1020029083.vpc_peering_connection_id": "", + "tags.#": "1", + "tags.Name": "onprem-routing-restricted", + "vpc_id": "vpc-65814701" + } + } + }, + "aws_route_table.route_table_az2_restricted": { + "type": "aws_route_table", + "primary": { + "id": "rtb-2d8c2a49", + "attributes": { + "id": "rtb-2d8c2a49", + "propagating_vgws.#": "0", + "route.#": "0", + "tags.#": "1", + "tags.Name": "onprem-routing-restricted", + "vpc_id": "vpc-65814701" + } + } + }, + "aws_route_table.route_table_az3_restricted": { + "type": "aws_route_table", + "primary": { + "id": "rtb-4a8c2a2e", + "attributes": { + "id": "rtb-4a8c2a2e", + "propagating_vgws.#": "0", + "route.#": "1", + "route.3346134226.cidr_block": "0.0.0.0/0", + "route.3346134226.gateway_id": "", + "route.3346134226.instance_id": "", + "route.3346134226.nat_gateway_id": "nat-0f2710d577d3f32ee", + "route.3346134226.network_interface_id": "", + "route.3346134226.vpc_peering_connection_id": "", + "tags.#": "1", + "tags.Name": "onprem-routing-restricted", + "vpc_id": "vpc-65814701" + } + } + }, + "aws_route_table_association.route_table_az1": { + "type": "aws_route_table_association", + "depends_on": [ + "aws_route_table.route_table_az1_restricted" + ], + "primary": { + "id": "rtbassoc-76d1ac12", + "attributes": { + "id": "rtbassoc-76d1ac12", + "route_table_id": "rtb-428c2a26", + "subnet_id": "subnet-d758bba1" + } + } + }, + "aws_route_table_association.route_table_az2": { + "type": "aws_route_table_association", + "depends_on": [ + "aws_route_table.route_table_az2_restricted" + ], + "primary": { + "id": "rtbassoc-21d1ac45", + "attributes": { + "id": "rtbassoc-21d1ac45", + "route_table_id": "rtb-2d8c2a49", + "subnet_id": "subnet-994f81fd" + } + } + }, + "aws_route_table_association.route_table_az3": { + "type": "aws_route_table_association", + "depends_on": [ + "aws_route_table.route_table_az3_restricted" + ], + "primary": { + "id": "rtbassoc-45d1ac21", + "attributes": { + "id": "rtbassoc-45d1ac21", + "route_table_id": "rtb-4a8c2a2e", + "subnet_id": "subnet-746d532d" + } + } + } + } + }, + { + "path": [ + "root", + "network-core", + "vpc" + ], + "outputs": { + "cidr": "10.201.0.0/16", + "id": "vpc-65814701" + }, + "resources": { + "aws_vpc.vpc": { + "type": "aws_vpc", + "primary": { + "id": "vpc-65814701", + "attributes": { + "cidr_block": "10.201.0.0/16", + "default_network_acl_id": "acl-30964254", + "default_security_group_id": "sg-604d2f07", + "dhcp_options_id": "dopt-e1afbb83", + "enable_classiclink": "false", + "enable_dns_hostnames": "true", + "id": "vpc-65814701", + "main_route_table_id": "rtb-868f29e2", + "tags.#": "1", + "tags.Name": "onprem" + } + } + } + } + } + ] +} diff --git a/terraform/test-fixtures/state-filter/small.tfstate b/terraform/test-fixtures/state-filter/small.tfstate new file mode 100644 index 000000000..9cb3c1d9f --- /dev/null +++ b/terraform/test-fixtures/state-filter/small.tfstate @@ -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" + } + } + } + } + } + ] +} diff --git a/vendor/github.com/mitchellh/cli/cli.go b/vendor/github.com/mitchellh/cli/cli.go index e871e6136..81b14f1fc 100644 --- a/vendor/github.com/mitchellh/cli/cli.go +++ b/vendor/github.com/mitchellh/cli/cli.go @@ -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 == "" { diff --git a/website/source/assets/stylesheets/_docs.scss b/website/source/assets/stylesheets/_docs.scss index 37c7f1816..9c7afec52 100755 --- a/website/source/assets/stylesheets/_docs.scss +++ b/website/source/assets/stylesheets/_docs.scss @@ -6,6 +6,7 @@ body.page-sub{ background-color: $light-black; } +body.layout-commands-state, body.layout-atlas, body.layout-aws, body.layout-azure, diff --git a/website/source/docs/commands/index.html.markdown b/website/source/docs/commands/index.html.markdown index f6a9d9118..b02bef75d 100644 --- a/website/source/docs/commands/index.html.markdown +++ b/website/source/docs/commands/index.html.markdown @@ -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. ``` - diff --git a/website/source/docs/commands/state/addressing.html.md b/website/source/docs/commands/state/addressing.html.md new file mode 100644 index 000000000..0b1dee2bc --- /dev/null +++ b/website/source/docs/commands/state/addressing.html.md @@ -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). diff --git a/website/source/docs/commands/state/index.html.md b/website/source/docs/commands/state/index.html.md new file mode 100644 index 000000000..5edd084c0 --- /dev/null +++ b/website/source/docs/commands/state/index.html.md @@ -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 [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. diff --git a/website/source/docs/commands/state/list.html.md b/website/source/docs/commands/state/list.html.md new file mode 100644 index 000000000..da63da34b --- /dev/null +++ b/website/source/docs/commands/state/list.html.md @@ -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 +``` diff --git a/website/source/docs/state/index.html.md b/website/source/docs/state/index.html.md index f1847702d..3a78fc708 100644 --- a/website/source/docs/state/index.html.md +++ b/website/source/docs/state/index.html.md @@ -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 diff --git a/website/source/layouts/commands-state.erb b/website/source/layouts/commands-state.erb new file mode 100644 index 000000000..674cb4834 --- /dev/null +++ b/website/source/layouts/commands-state.erb @@ -0,0 +1,30 @@ +<% wrap_layout :inner do %> + <% content_for :sidebar do %> + + <% end %> + + <%= yield %> +<% end %> diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index d357c6d53..557966b7d 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -107,6 +107,10 @@ show + > + state + + > taint @@ -269,7 +273,7 @@ TLS - > + > Triton