mirror of
https://github.com/hashicorp/terraform.git
synced 2026-02-19 02:39:17 -05:00
Some checks are pending
build / Determine intended Terraform version (push) Waiting to run
build / Determine Go toolchain version (push) Waiting to run
build / Generate release metadata (push) Blocked by required conditions
build / Build for freebsd_386 (push) Blocked by required conditions
build / Build for linux_386 (push) Blocked by required conditions
build / Build for openbsd_386 (push) Blocked by required conditions
build / Build for windows_386 (push) Blocked by required conditions
build / Build for darwin_amd64 (push) Blocked by required conditions
build / Build for freebsd_amd64 (push) Blocked by required conditions
build / Build for linux_amd64 (push) Blocked by required conditions
build / Build for openbsd_amd64 (push) Blocked by required conditions
build / Build for solaris_amd64 (push) Blocked by required conditions
build / Build for windows_amd64 (push) Blocked by required conditions
build / Build for freebsd_arm (push) Blocked by required conditions
build / Build for linux_arm (push) Blocked by required conditions
build / Build for darwin_arm64 (push) Blocked by required conditions
build / Build for linux_arm64 (push) Blocked by required conditions
build / Build for windows_arm64 (push) Blocked by required conditions
build / Build Docker image for linux_386 (push) Blocked by required conditions
build / Build Docker image for linux_amd64 (push) Blocked by required conditions
build / Build Docker image for linux_arm (push) Blocked by required conditions
build / Build Docker image for linux_arm64 (push) Blocked by required conditions
build / Build e2etest for linux_386 (push) Blocked by required conditions
build / Build e2etest for windows_386 (push) Blocked by required conditions
build / Build e2etest for darwin_amd64 (push) Blocked by required conditions
build / Build e2etest for linux_amd64 (push) Blocked by required conditions
build / Build e2etest for windows_amd64 (push) Blocked by required conditions
build / Build e2etest for linux_arm (push) Blocked by required conditions
build / Build e2etest for darwin_arm64 (push) Blocked by required conditions
build / Build e2etest for linux_arm64 (push) Blocked by required conditions
build / Run e2e test for linux_386 (push) Blocked by required conditions
build / Run e2e test for windows_386 (push) Blocked by required conditions
build / Run e2e test for darwin_amd64 (push) Blocked by required conditions
build / Run e2e test for linux_amd64 (push) Blocked by required conditions
build / Run e2e test for windows_amd64 (push) Blocked by required conditions
build / Run e2e test for linux_arm (push) Blocked by required conditions
build / Run e2e test for linux_arm64 (push) Blocked by required conditions
build / Run terraform-exec test for linux amd64 (push) Blocked by required conditions
Quick Checks / Unit Tests (push) Waiting to run
Quick Checks / Race Tests (push) Waiting to run
Quick Checks / End-to-end Tests (push) Waiting to run
Quick Checks / Code Consistency Checks (push) Waiting to run
* Add a generic method for loading an operations backend in non-init commands * Refactor commands to use new prepareBackend method: group 1 * Refactor commands to use new prepareBackend method: group 2, where config parsing needs to be explicitly added * Refactor commands to use new prepareBackend method: group 3, where we can use already parsed config * Additional, more nested, places where logic for accessing backends needs to be refactored * Remove duplicated comment * Add test coverage of `(m *Meta) prepareBackend()` * Add TODO related to using plans for backend/state_store config in apply commands * Add `testStateStoreMockWithChunkNegotiation` test helper * Add assertions to tests about the backend (remote-state, local, etc) in use within operations backend * Stop prepareBackend taking locks as argument * Code comment in prepareBackend * Replace c.Meta.prepareBackend with c.prepareBackend * Change `c.Meta.loadSingleModule` to `c.loadSingleModule` * Rename (Meta).prepareBackend to (Meta).backend, update godoc comment to make relationship to (Meta).Backend more obvious. * Revert change from config.Module to config.Root.Module * Update `(m *Meta) backend` method to parse config itself, and also to adhere to calling code's viewtype instructions * Update all tests and calling code following previous commit * Change how an operations backend is obtained by autocomplete code * Update autocomplete to return nil if no workspace names are returned from the backend * Add test coverage for autocompleting workspace names when using a pluggable state store * Fix output command: pass view type data to new `backend` method * Fix in plan command: pass correct view type to `backend` method * Fix `providers schema` command to use correct viewtype when preparing a backend
228 lines
6.9 KiB
Go
228 lines
6.9 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package command
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/command/arguments"
|
|
"github.com/hashicorp/terraform/internal/states"
|
|
"github.com/hashicorp/terraform/internal/states/statemgr"
|
|
"github.com/hashicorp/terraform/internal/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(view arguments.ViewType) (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, diags := c.backend(".", view)
|
|
if diags.HasErrors() {
|
|
return nil, diags.Err()
|
|
}
|
|
|
|
workspace, err := c.Workspace()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Check remote Terraform version is compatible
|
|
remoteVersionDiags := c.remoteVersionCheck(b, workspace)
|
|
c.showDiagnostics(remoteVersionDiags)
|
|
if remoteVersionDiags.HasErrors() {
|
|
return nil, fmt.Errorf("Error checking remote Terraform version")
|
|
}
|
|
|
|
// Get the state
|
|
s, sDiags := b.StateMgr(workspace)
|
|
if sDiags.HasErrors() {
|
|
return nil, sDiags.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
|
|
// descendant 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 && !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
|
|
}
|