Files
opentf/command/state_push.go
James Bardin 39a5ddd381 Split Meta back out of StateMeta
Removing the call to StateMeta.Env, so that it doesn't need an embedded
Meta field. Embed Meta and StateMeta separately in all State commands.
2017-03-01 10:20:32 -05:00

146 lines
3.7 KiB
Go

package command
import (
"fmt"
"os"
"strings"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli"
)
// StatePushCommand is a Command implementation that shows a single resource.
type StatePushCommand struct {
Meta
StateMeta
}
func (c *StatePushCommand) Run(args []string) int {
args = c.Meta.process(args, true)
var flagForce bool
cmdFlags := c.Meta.flagSet("state push")
cmdFlags.BoolVar(&flagForce, "force", false, "")
if err := cmdFlags.Parse(args); err != nil {
return cli.RunResultHelp
}
args = cmdFlags.Args()
if len(args) != 1 {
c.Ui.Error("Exactly one argument expected: path to state to push")
return 1
}
// Read the state
f, err := os.Open(args[0])
if err != nil {
c.Ui.Error(err.Error())
return 1
}
sourceState, err := terraform.ReadState(f)
f.Close()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error reading source state %q: %s", args[0], err))
return 1
}
// Load the backend
b, err := c.Backend(nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
return 1
}
// Get the state
env := c.Env()
state, err := b.State(env)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
return 1
}
if err := state.RefreshState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
return 1
}
dstState := state.State()
// If we're not forcing, then perform safety checks
if !flagForce && !dstState.Empty() {
if !dstState.SameLineage(sourceState) {
c.Ui.Error(strings.TrimSpace(errStatePushLineage))
return 1
}
age, err := dstState.CompareAges(sourceState)
if err != nil {
c.Ui.Error(err.Error())
return 1
}
if age == terraform.StateAgeReceiverNewer {
c.Ui.Error(strings.TrimSpace(errStatePushSerialNewer))
return 1
}
}
// Overwrite it
if err := state.WriteState(sourceState); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
return 1
}
if err := state.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
return 1
}
return 0
}
func (c *StatePushCommand) Help() string {
helpText := `
Usage: terraform state push [options] PATH
Update remote state from a local state file at PATH.
This command "pushes" a local state and overwrites remote state
with a local state file. The command will protect you against writing
an older serial or a different state file lineage unless you specify the
"-force" flag.
This command works with local state (it will overwrite the local
state), but is less useful for this use case.
Options:
-force Write the state even if lineages don't match or the
remote serial is higher.
`
return strings.TrimSpace(helpText)
}
func (c *StatePushCommand) Synopsis() string {
return "Update remote state from a local state file"
}
const errStatePushLineage = `
The lineages do not match! The state will not be pushed.
The "lineage" is a unique identifier given to a state on creation. It helps
protect Terraform from overwriting a seemingly unrelated state file since it
represents potentially losing real state.
Please verify you're pushing the correct state. If you're sure you are, you
can force the behavior with the "-force" flag.
`
const errStatePushSerialNewer = `
The destination state has a higher serial number! The state will not be pushed.
A higher serial could indicate that there is data in the destination state
that was not present when the source state was created. As a protection measure,
Terraform will not automatically overwrite this state.
Please verify you're pushing the correct state. If you're sure you are, you
can force the behavior with the "-force" flag.
`