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