Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

command: Fix various issues in the "terraform state ..." subcommands #20719

Merged
merged 1 commit into from
Mar 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 35 additions & 21 deletions command/state_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"fmt"
"strings"

"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/tfdiags"
"github.com/mitchellh/cli"
)

Expand Down Expand Up @@ -54,49 +56,61 @@ func (c *StateListCommand) Run(args []string) int {
return 1
}

filter := &states.Filter{State: state}
results, err := filter.Filter(args...)
if err != nil {
c.Ui.Error(fmt.Sprintf(errStateFilter, err))
return cli.RunResultHelp
var addrs []addrs.AbsResourceInstance
var diags tfdiags.Diagnostics
if len(args) == 0 {
addrs, diags = c.lookupAllResourceInstanceAddrs(state)
} else {
addrs, diags = c.lookupResourceInstanceAddrs(state, args...)
}
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}

for _, result := range results {
if is, ok := result.Value.(*states.ResourceInstance); ok {
for _, addr := range addrs {
if is := state.ResourceInstance(addr); is != nil {
if *lookupId == "" || *lookupId == states.LegacyInstanceObjectID(is.Current) {
c.Ui.Output(result.Address.String())
c.Ui.Output(addr.String())
}
}
}

c.showDiagnostics(diags)

return 0
}

func (c *StateListCommand) Help() string {
helpText := `
Usage: terraform state list [options] [pattern...]
Usage: terraform state list [options] [address...]

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.
This command lists resource instances in the Terraform state. The address
argument can be used to filter the instances by resource or module. If
no pattern is given, all resource instances 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 addresses must either be module addresses or absolute resource
addresses, such as:
aws_instance.example
module.example
module.example.module.child
module.example.aws_instance.example

The pattern argument accepts any resource targeting syntax. Please
refer to the documentation on resource targeting syntax for more
information.
An error will be returned if any of the resources or modules given as
filter addresses do not exist in the state.

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.
up Terraform-managed resources. By default, Terraform
will consult the state of the currently-selected
workspace.

-id=ID Restricts the output to objects whose id is ID.
-id=ID Filters the results to include only instances whose
resource types have an attribute named "id" whose value
equals the given id string.

`
return strings.TrimSpace(helpText)
Expand Down
146 changes: 104 additions & 42 deletions command/state_meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/tfdiags"

backendLocal "github.com/hashicorp/terraform/backend/local"
)
Expand Down Expand Up @@ -80,57 +81,118 @@ func (c *StateMeta) State() (state.State, error) {
return realState, nil
}

func (c *StateMeta) filter(state *states.State, args []string) ([]*states.FilterResult, error) {
var results []*states.FilterResult
func (c *StateMeta) lookupResourceInstanceAddr(state *states.State, allowMissing bool, addrStr string) ([]addrs.AbsResourceInstance, tfdiags.Diagnostics) {
target, diags := addrs.ParseTargetStr(addrStr)
if diags.HasErrors() {
return nil, diags
}

filter := &states.Filter{State: state}
for _, arg := range args {
filtered, err := filter.Filter(arg)
if err != nil {
return nil, err
targetAddr := target.Subject
var ret []addrs.AbsResourceInstance
switch addr := targetAddr.(type) {
case addrs.ModuleInstance:
// Matches all instances within the indicated module and all of its
// descendent modules.
ms := state.Module(addr)
if ms == nil {
if !allowMissing {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Unknown module",
fmt.Sprintf(`The current state contains no module at %s. If you've just added this module to the configuration, you must run "terraform apply" first to create the module's entry in the state.`, addr),
))
}
break
}

filtered:
for _, result := range filtered {
switch result.Address.(type) {
case addrs.ModuleInstance:
for _, result := range filtered {
if _, ok := result.Address.(addrs.ModuleInstance); ok {
results = append(results, result)
}
}
break filtered
case addrs.AbsResource:
for _, result := range filtered {
if _, ok := result.Address.(addrs.AbsResource); ok {
results = append(results, result)
}
}
break filtered
case addrs.AbsResourceInstance:
results = append(results, result)
ret = append(ret, c.collectModuleResourceInstances(ms)...)
for _, cms := range state.Modules {
candidateAddr := ms.Addr
if len(candidateAddr) > len(addr) && candidateAddr[:len(addr)].Equal(addr) {
ret = append(ret, c.collectModuleResourceInstances(cms)...)
}
}
case addrs.AbsResource:
// Matches all instances of the specific selected resource.
rs := state.Resource(addr)
if rs == nil {
if !allowMissing {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Unknown resource",
fmt.Sprintf(`The current state contains no resource %s. If you've just added this resource to the configuration, you must run "terraform apply" first to create the resource's entry in the state.`, addr),
))
}
break
}
ret = append(ret, c.collectResourceInstances(addr.Module, rs)...)
case addrs.AbsResourceInstance:
is := state.ResourceInstance(addr)
if is == nil {
if !allowMissing {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Unknown resource instance",
fmt.Sprintf(`The current state contains no resource instance %s. If you've just added its resource to the configuration or have changed the count or for_each arguments, you must run "terraform apply" first to update the resource's entry in the state.`, addr),
))
}
break
}
ret = append(ret, addr)
}
sort.Slice(ret, func(i, j int) bool {
return ret[i].Less(ret[j])
})

// Sort the results
sort.Slice(results, func(i, j int) bool {
a, b := results[i], results[j]
return ret, diags
}

// If the length is different, sort on the length so that the
// best match is the first result.
if len(a.Address.String()) != len(b.Address.String()) {
return len(a.Address.String()) < len(b.Address.String())
}
func (c *StateMeta) lookupSingleResourceInstanceAddr(state *states.State, addrStr string) (addrs.AbsResourceInstance, tfdiags.Diagnostics) {
return addrs.ParseAbsResourceInstanceStr(addrStr)
}

// If the addresses are different it is just lexographic sorting
if a.Address.String() != b.Address.String() {
return a.Address.String() < b.Address.String()
}
func (c *StateMeta) lookupSingleStateObjectAddr(state *states.State, addrStr string) (addrs.Targetable, tfdiags.Diagnostics) {
target, diags := addrs.ParseTargetStr(addrStr)
if diags.HasErrors() {
return nil, diags
}
return target.Subject, diags
}

func (c *StateMeta) lookupResourceInstanceAddrs(state *states.State, addrStrs ...string) ([]addrs.AbsResourceInstance, tfdiags.Diagnostics) {
var ret []addrs.AbsResourceInstance
var diags tfdiags.Diagnostics
for _, addrStr := range addrStrs {
moreAddrs, moreDiags := c.lookupResourceInstanceAddr(state, false, addrStr)
ret = append(ret, moreAddrs...)
diags = diags.Append(moreDiags)
}
return ret, diags
}

// Addresses are the same, which means it matters on the type
return a.SortedType() < b.SortedType()
func (c *StateMeta) lookupAllResourceInstanceAddrs(state *states.State) ([]addrs.AbsResourceInstance, tfdiags.Diagnostics) {
var ret []addrs.AbsResourceInstance
var diags tfdiags.Diagnostics
for _, ms := range state.Modules {
ret = append(ret, c.collectModuleResourceInstances(ms)...)
}
sort.Slice(ret, func(i, j int) bool {
return ret[i].Less(ret[j])
})
return ret, diags
}

func (c *StateMeta) collectModuleResourceInstances(ms *states.Module) []addrs.AbsResourceInstance {
var ret []addrs.AbsResourceInstance
for _, rs := range ms.Resources {
ret = append(ret, c.collectResourceInstances(ms.Addr, rs)...)
}
return ret
}

return results, nil
func (c *StateMeta) collectResourceInstances(moduleAddr addrs.ModuleInstance, rs *states.Resource) []addrs.AbsResourceInstance {
var ret []addrs.AbsResourceInstance
for key := range rs.Instances {
ret = append(ret, rs.Addr.Instance(key).Absolute(moduleAddr))
}
return ret
}
Loading