github.com/hartzell/terraform@v0.8.6-0.20180503104400-0cc9e050ecd4/command/state_push.go (about)

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