github.com/pulumi/terraform@v1.4.0/pkg/command/state_meta.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"time"
     7  
     8  	"github.com/pulumi/terraform/pkg/addrs"
     9  	"github.com/pulumi/terraform/pkg/states"
    10  	"github.com/pulumi/terraform/pkg/states/statemgr"
    11  	"github.com/pulumi/terraform/pkg/tfdiags"
    12  
    13  	backendLocal "github.com/pulumi/terraform/pkg/backend/local"
    14  )
    15  
    16  // StateMeta is the meta struct that should be embedded in state subcommands.
    17  type StateMeta struct {
    18  	Meta
    19  }
    20  
    21  // State returns the state for this meta. This gets the appropriate state from
    22  // the backend, but changes the way that backups are done. This configures
    23  // backups to be timestamped rather than just the original state path plus a
    24  // backup path.
    25  func (c *StateMeta) State() (statemgr.Full, error) {
    26  	var realState statemgr.Full
    27  	backupPath := c.backupPath
    28  	stateOutPath := c.statePath
    29  
    30  	// use the specified state
    31  	if c.statePath != "" {
    32  		realState = statemgr.NewFilesystem(c.statePath)
    33  	} else {
    34  		// Load the backend
    35  		b, backendDiags := c.Backend(nil)
    36  		if backendDiags.HasErrors() {
    37  			return nil, backendDiags.Err()
    38  		}
    39  
    40  		workspace, err := c.Workspace()
    41  		if err != nil {
    42  			return nil, err
    43  		}
    44  
    45  		// Check remote Terraform version is compatible
    46  		remoteVersionDiags := c.remoteVersionCheck(b, workspace)
    47  		c.showDiagnostics(remoteVersionDiags)
    48  		if remoteVersionDiags.HasErrors() {
    49  			return nil, fmt.Errorf("Error checking remote Terraform version")
    50  		}
    51  
    52  		// Get the state
    53  		s, err := b.StateMgr(workspace)
    54  		if err != nil {
    55  			return nil, err
    56  		}
    57  
    58  		// Get a local backend
    59  		localRaw, backendDiags := c.Backend(&BackendOpts{ForceLocal: true})
    60  		if backendDiags.HasErrors() {
    61  			// This should never fail
    62  			panic(backendDiags.Err())
    63  		}
    64  		localB := localRaw.(*backendLocal.Local)
    65  		_, stateOutPath, _ = localB.StatePaths(workspace)
    66  		if err != nil {
    67  			return nil, err
    68  		}
    69  
    70  		realState = s
    71  	}
    72  
    73  	// We always backup state commands, so set the back if none was specified
    74  	// (the default is "-", but some tests bypass the flag parsing).
    75  	if backupPath == "-" || backupPath == "" {
    76  		// Determine the backup path. stateOutPath is set to the resulting
    77  		// file where state is written (cached in the case of remote state)
    78  		backupPath = fmt.Sprintf(
    79  			"%s.%d%s",
    80  			stateOutPath,
    81  			time.Now().UTC().Unix(),
    82  			DefaultBackupExtension)
    83  	}
    84  
    85  	// If the backend is local (which it should always be, given our asserting
    86  	// of it above) we can now enable backups for it.
    87  	if lb, ok := realState.(*statemgr.Filesystem); ok {
    88  		lb.SetBackupPath(backupPath)
    89  	}
    90  
    91  	return realState, nil
    92  }
    93  
    94  func (c *StateMeta) lookupResourceInstanceAddr(state *states.State, allowMissing bool, addrStr string) ([]addrs.AbsResourceInstance, tfdiags.Diagnostics) {
    95  	target, diags := addrs.ParseTargetStr(addrStr)
    96  	if diags.HasErrors() {
    97  		return nil, diags
    98  	}
    99  
   100  	targetAddr := target.Subject
   101  	var ret []addrs.AbsResourceInstance
   102  	switch addr := targetAddr.(type) {
   103  	case addrs.ModuleInstance:
   104  		// Matches all instances within the indicated module and all of its
   105  		// descendent modules.
   106  
   107  		// found is used to identify cases where the selected module has no
   108  		// resources, but one or more of its submodules does.
   109  		found := false
   110  		ms := state.Module(addr)
   111  		if ms != nil {
   112  			found = true
   113  			ret = append(ret, c.collectModuleResourceInstances(ms)...)
   114  		}
   115  		for _, cms := range state.Modules {
   116  			if !addr.Equal(cms.Addr) {
   117  				if addr.IsAncestor(cms.Addr) || addr.TargetContains(cms.Addr) {
   118  					found = true
   119  					ret = append(ret, c.collectModuleResourceInstances(cms)...)
   120  				}
   121  			}
   122  		}
   123  
   124  		if !found && !allowMissing {
   125  			diags = diags.Append(tfdiags.Sourceless(
   126  				tfdiags.Error,
   127  				"Unknown module",
   128  				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),
   129  			))
   130  		}
   131  
   132  	case addrs.AbsResource:
   133  		// Matches all instances of the specific selected resource.
   134  		rs := state.Resource(addr)
   135  		if rs == nil {
   136  			if !allowMissing {
   137  				diags = diags.Append(tfdiags.Sourceless(
   138  					tfdiags.Error,
   139  					"Unknown resource",
   140  					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),
   141  				))
   142  			}
   143  			break
   144  		}
   145  		ret = append(ret, c.collectResourceInstances(addr.Module, rs)...)
   146  	case addrs.AbsResourceInstance:
   147  		is := state.ResourceInstance(addr)
   148  		if is == nil {
   149  			if !allowMissing {
   150  				diags = diags.Append(tfdiags.Sourceless(
   151  					tfdiags.Error,
   152  					"Unknown resource instance",
   153  					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),
   154  				))
   155  			}
   156  			break
   157  		}
   158  		ret = append(ret, addr)
   159  	}
   160  	sort.Slice(ret, func(i, j int) bool {
   161  		return ret[i].Less(ret[j])
   162  	})
   163  
   164  	return ret, diags
   165  }
   166  
   167  func (c *StateMeta) lookupSingleStateObjectAddr(state *states.State, addrStr string) (addrs.Targetable, tfdiags.Diagnostics) {
   168  	target, diags := addrs.ParseTargetStr(addrStr)
   169  	if diags.HasErrors() {
   170  		return nil, diags
   171  	}
   172  	return target.Subject, diags
   173  }
   174  
   175  func (c *StateMeta) lookupResourceInstanceAddrs(state *states.State, addrStrs ...string) ([]addrs.AbsResourceInstance, tfdiags.Diagnostics) {
   176  	var ret []addrs.AbsResourceInstance
   177  	var diags tfdiags.Diagnostics
   178  	for _, addrStr := range addrStrs {
   179  		moreAddrs, moreDiags := c.lookupResourceInstanceAddr(state, false, addrStr)
   180  		ret = append(ret, moreAddrs...)
   181  		diags = diags.Append(moreDiags)
   182  	}
   183  	return ret, diags
   184  }
   185  
   186  func (c *StateMeta) lookupAllResourceInstanceAddrs(state *states.State) ([]addrs.AbsResourceInstance, tfdiags.Diagnostics) {
   187  	var ret []addrs.AbsResourceInstance
   188  	var diags tfdiags.Diagnostics
   189  	for _, ms := range state.Modules {
   190  		ret = append(ret, c.collectModuleResourceInstances(ms)...)
   191  	}
   192  	sort.Slice(ret, func(i, j int) bool {
   193  		return ret[i].Less(ret[j])
   194  	})
   195  	return ret, diags
   196  }
   197  
   198  func (c *StateMeta) collectModuleResourceInstances(ms *states.Module) []addrs.AbsResourceInstance {
   199  	var ret []addrs.AbsResourceInstance
   200  	for _, rs := range ms.Resources {
   201  		ret = append(ret, c.collectResourceInstances(ms.Addr, rs)...)
   202  	}
   203  	return ret
   204  }
   205  
   206  func (c *StateMeta) collectResourceInstances(moduleAddr addrs.ModuleInstance, rs *states.Resource) []addrs.AbsResourceInstance {
   207  	var ret []addrs.AbsResourceInstance
   208  	for key := range rs.Instances {
   209  		ret = append(ret, rs.Addr.Instance(key))
   210  	}
   211  	return ret
   212  }
   213  
   214  func (c *StateMeta) lookupAllResources(state *states.State) ([]*states.Resource, tfdiags.Diagnostics) {
   215  	var ret []*states.Resource
   216  	var diags tfdiags.Diagnostics
   217  	for _, ms := range state.Modules {
   218  		for _, resource := range ms.Resources {
   219  			ret = append(ret, resource)
   220  		}
   221  	}
   222  	return ret, diags
   223  }