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