package command import ( "fmt" "sort" "time" "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/internal/tfdiags" "github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/states/statemgr" backendLocal "github.com/hashicorp/terraform/internal/backend/local" ) // StateMeta is the meta struct that should be embedded in state subcommands. type StateMeta struct { Meta } // State returns the state for this meta. This gets the appropriate state from // the backend, but changes the way that backups are done. This configures // backups to be timestamped rather than just the original state path plus a // backup path. func (c *StateMeta) State() (statemgr.Full, error) { var realState statemgr.Full backupPath := c.backupPath stateOutPath := c.statePath // use the specified state if c.statePath != "" { realState = statemgr.NewFilesystem(c.statePath) } else { // Load the backend b, backendDiags := c.Backend(nil) if backendDiags.HasErrors() { return nil, backendDiags.Err() } workspace, err := c.Workspace() if err != nil { return nil, err } // Check remote Terraform version is compatible remoteVersionDiags := c.remoteBackendVersionCheck(b, workspace) c.showDiagnostics(remoteVersionDiags) if remoteVersionDiags.HasErrors() { return nil, fmt.Errorf("Error checking remote Terraform version") } // Get the state s, err := b.StateMgr(workspace) if err != nil { return nil, err } // Get a local backend localRaw, backendDiags := c.Backend(&BackendOpts{ForceLocal: true}) if backendDiags.HasErrors() { // This should never fail panic(backendDiags.Err()) } localB := localRaw.(*backendLocal.Local) _, stateOutPath, _ = localB.StatePaths(workspace) if err != nil { return nil, err } realState = s } // We always backup state commands, so set the back if none was specified // (the default is "-", but some tests bypass the flag parsing). if backupPath == "-" || backupPath == "" { // Determine the backup path. stateOutPath is set to the resulting // file where state is written (cached in the case of remote state) backupPath = fmt.Sprintf( "%s.%d%s", stateOutPath, time.Now().UTC().Unix(), DefaultBackupExtension) } // If the backend is local (which it should always be, given our asserting // of it above) we can now enable backups for it. if lb, ok := realState.(*statemgr.Filesystem); ok { lb.SetBackupPath(backupPath) } return realState, nil } 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 } 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. // found is used to identify cases where the selected module has no // resources, but one or more of its submodules does. found := false ms := state.Module(addr) if ms != nil { found = true ret = append(ret, c.collectModuleResourceInstances(ms)...) } for _, cms := range state.Modules { if !addr.Equal(cms.Addr) { if addr.IsAncestor(cms.Addr) || addr.TargetContains(cms.Addr) { found = true ret = append(ret, c.collectModuleResourceInstances(cms)...) } } } if found == false && !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), )) } 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]) }) return ret, diags } 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 } 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 } 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)) } return ret } func (c *StateMeta) lookupAllResources(state *states.State) ([]*states.Resource, tfdiags.Diagnostics) { var ret []*states.Resource var diags tfdiags.Diagnostics for _, ms := range state.Modules { for _, resource := range ms.Resources { ret = append(ret, resource) } } return ret, diags }