github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/command/state_push.go (about)

     1  package command
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"strings"
     9  
    10  	"github.com/hashicorp/terraform/command/clistate"
    11  	"github.com/hashicorp/terraform/states/statefile"
    12  	"github.com/hashicorp/terraform/states/statemgr"
    13  	"github.com/mitchellh/cli"
    14  )
    15  
    16  // StatePushCommand is a Command implementation that shows a single resource.
    17  type StatePushCommand struct {
    18  	Meta
    19  	StateMeta
    20  }
    21  
    22  func (c *StatePushCommand) Run(args []string) int {
    23  	args, err := c.Meta.process(args, true)
    24  	if err != nil {
    25  		return 1
    26  	}
    27  
    28  	var flagForce bool
    29  	cmdFlags := c.Meta.defaultFlagSet("state push")
    30  	cmdFlags.BoolVar(&flagForce, "force", false, "")
    31  	cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
    32  	cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
    33  	if err := cmdFlags.Parse(args); err != nil {
    34  		c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
    35  		return 1
    36  	}
    37  	args = cmdFlags.Args()
    38  
    39  	if len(args) != 1 {
    40  		c.Ui.Error("Exactly one argument expected.\n")
    41  		return cli.RunResultHelp
    42  	}
    43  
    44  	// Determine our reader for the input state. This is the filepath
    45  	// or stdin if "-" is given.
    46  	var r io.Reader = os.Stdin
    47  	if args[0] != "-" {
    48  		f, err := os.Open(args[0])
    49  		if err != nil {
    50  			c.Ui.Error(err.Error())
    51  			return 1
    52  		}
    53  
    54  		// Note: we don't need to defer a Close here because we do a close
    55  		// automatically below directly after the read.
    56  
    57  		r = f
    58  	}
    59  
    60  	// Read the state
    61  	srcStateFile, err := statefile.Read(r)
    62  	if c, ok := r.(io.Closer); ok {
    63  		// Close the reader if possible right now since we're done with it.
    64  		c.Close()
    65  	}
    66  	if err != nil {
    67  		c.Ui.Error(fmt.Sprintf("Error reading source state %q: %s", args[0], err))
    68  		return 1
    69  	}
    70  	if err := srcStateFile.CheckTerraformVersion(); err != nil {
    71  		c.Ui.Error(fmt.Sprintf("Incompatible statefile %q: %s", args[0], err))
    72  		return 1
    73  	}
    74  
    75  	// Load the backend
    76  	b, backendDiags := c.Backend(nil)
    77  	if backendDiags.HasErrors() {
    78  		c.showDiagnostics(backendDiags)
    79  		return 1
    80  	}
    81  
    82  	// Get the state manager for the currently-selected workspace
    83  	env := c.Workspace()
    84  	stateMgr, err := b.StateMgr(env)
    85  	if err != nil {
    86  		c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
    87  		return 1
    88  	}
    89  
    90  	if c.stateLock {
    91  		stateLocker := clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize())
    92  		if err := stateLocker.Lock(stateMgr, "state-push"); err != nil {
    93  			c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
    94  			return 1
    95  		}
    96  		defer stateLocker.Unlock(nil)
    97  	}
    98  
    99  	if err := stateMgr.RefreshState(); err != nil {
   100  		c.Ui.Error(fmt.Sprintf("Failed to refresh destination state: %s", err))
   101  		return 1
   102  	}
   103  
   104  	if srcStateFile == nil {
   105  		// We'll push a new empty state instead
   106  		srcStateFile = statemgr.NewStateFile()
   107  	}
   108  
   109  	// Import it, forcing through the lineage/serial if requested and possible.
   110  	if err := statemgr.Import(srcStateFile, stateMgr, flagForce); err != nil {
   111  		c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
   112  		return 1
   113  	}
   114  	if err := stateMgr.WriteState(srcStateFile.State); err != nil {
   115  		c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
   116  		return 1
   117  	}
   118  	if err := stateMgr.PersistState(); err != nil {
   119  		c.Ui.Error(fmt.Sprintf("Failed to persist state: %s", err))
   120  		return 1
   121  	}
   122  
   123  	return 0
   124  }
   125  
   126  func (c *StatePushCommand) Help() string {
   127  	helpText := `
   128  Usage: terraform state push [options] PATH
   129  
   130    Update remote state from a local state file at PATH.
   131  
   132    This command "pushes" a local state and overwrites remote state
   133    with a local state file. The command will protect you against writing
   134    an older serial or a different state file lineage unless you specify the
   135    "-force" flag.
   136  
   137    This command works with local state (it will overwrite the local
   138    state), but is less useful for this use case.
   139  
   140    If PATH is "-", then this command will read the state to push from stdin.
   141    Data from stdin is not streamed to the backend: it is loaded completely
   142    (until pipe close), verified, and then pushed.
   143  
   144  Options:
   145  
   146    -force              Write the state even if lineages don't match or the
   147                        remote serial is higher.
   148  
   149    -lock=true          Lock the state file when locking is supported.
   150  
   151    -lock-timeout=0s    Duration to retry a state lock.
   152  
   153  `
   154  	return strings.TrimSpace(helpText)
   155  }
   156  
   157  func (c *StatePushCommand) Synopsis() string {
   158  	return "Update remote state from a local state file"
   159  }
   160  
   161  const errStatePushLineage = `
   162  The lineages do not match! The state will not be pushed.
   163  
   164  The "lineage" is a unique identifier given to a state on creation. It helps
   165  protect Terraform from overwriting a seemingly unrelated state file since it
   166  represents potentially losing real state.
   167  
   168  Please verify you're pushing the correct state. If you're sure you are, you
   169  can force the behavior with the "-force" flag.
   170  `
   171  
   172  const errStatePushSerialNewer = `
   173  The destination state has a higher serial number! The state will not be pushed.
   174  
   175  A higher serial could indicate that there is data in the destination state
   176  that was not present when the source state was created. As a protection measure,
   177  Terraform will not automatically overwrite this state.
   178  
   179  Please verify you're pushing the correct state. If you're sure you are, you
   180  can force the behavior with the "-force" flag.
   181  `