opentofu/command/state_meta.go
Martin Atkins 73dda868cc Move backend/ to internal/backend/
This is part of a general effort to move all of Terraform's non-library
package surface under internal in order to reinforce that these are for
internal use within Terraform only.

If you were previously importing packages under this prefix into an
external codebase, you could pin to an earlier release tag as an interim
solution until you've make a plan to achieve the same functionality some
other way.
2021-05-17 14:09:07 -07:00

224 lines
6.7 KiB
Go

package command
import (
"fmt"
"sort"
"time"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/tfdiags"
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
}