command: Use statemgr.Import and statemgr.Export for state push and pull

We previously hacked around the import/export functionality being missing
in the statemgr layer after refactoring, but now it's been reintroduced
to fix functionality elsewhere we should use the centralized Import and
Export functions to ensure consistent behavior.

In particular, this pushes the logic for checking lineage and serial
during push down into the state manager itself, which is better because
all other details about lineage and serial are managed within the state
managers.
This commit is contained in:
Martin Atkins 2018-11-13 16:48:59 -08:00
parent 985b414dca
commit 22c84c71a4
6 changed files with 50 additions and 54 deletions

View File

@ -35,7 +35,7 @@ func (c *StatePullCommand) Run(args []string) int {
return 1 return 1
} }
// Get the state // Get the state manager for the current workspace
env := c.Workspace() env := c.Workspace()
stateMgr, err := b.StateMgr(env) stateMgr, err := b.StateMgr(env)
if err != nil { if err != nil {
@ -47,24 +47,20 @@ func (c *StatePullCommand) Run(args []string) int {
return 1 return 1
} }
state := stateMgr.State() // Get a statefile object representing the latest snapshot
if state == nil { stateFile := statemgr.Export(stateMgr)
// Output on "error" so it shows up on stderr
c.Ui.Error("Empty state (no state)") if stateFile != nil { // we produce no output if the statefile is nil
return 0 var buf bytes.Buffer
err = statefile.Write(stateFile, &buf)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
return 1
}
c.Ui.Output(buf.String())
} }
// Get the state file.
stateFile := statemgr.StateFile(stateMgr, state)
var buf bytes.Buffer
err = statefile.Write(stateFile, &buf)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
return 1
}
c.Ui.Output(buf.String())
return 0 return 0
} }

View File

@ -47,7 +47,7 @@ func TestStatePull_noState(t *testing.T) {
defer testFixCwd(t, tmp, cwd) defer testFixCwd(t, tmp, cwd)
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := cli.NewMockUi()
c := &StatePullCommand{ c := &StatePullCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),

View File

@ -70,7 +70,7 @@ func (c *StatePushCommand) Run(args []string) int {
return 1 return 1
} }
// Get the state // Get the state manager for the currently-selected workspace
env := c.Workspace() env := c.Workspace()
stateMgr, err := b.StateMgr(env) stateMgr, err := b.StateMgr(env)
if err != nil { if err != nil {
@ -81,23 +81,17 @@ func (c *StatePushCommand) Run(args []string) int {
c.Ui.Error(fmt.Sprintf("Failed to refresh destination state: %s", err)) c.Ui.Error(fmt.Sprintf("Failed to refresh destination state: %s", err))
return 1 return 1
} }
dstState := stateMgr.State()
// If we're not forcing, then perform safety checks if srcStateFile == nil {
if !flagForce && !dstState.Empty() { // We'll push a new empty state instead
dstStateFile := statemgr.StateFile(stateMgr, dstState) srcStateFile = statemgr.NewStateFile()
if dstStateFile.Lineage != srcStateFile.Lineage {
c.Ui.Error(strings.TrimSpace(errStatePushLineage))
return 1
}
if dstStateFile.Serial > srcStateFile.Serial {
c.Ui.Error(strings.TrimSpace(errStatePushSerialNewer))
return 1
}
} }
// Overwrite it // Import it, forcing through the lineage/serial if requested and possible.
if err := statemgr.Import(srcStateFile, stateMgr, flagForce); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
return 1
}
if err := stateMgr.WriteState(srcStateFile.State); err != nil { if err := stateMgr.WriteState(srcStateFile.State); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err)) c.Ui.Error(fmt.Sprintf("Failed to write state: %s", err))
return 1 return 1

View File

@ -1,5 +1,17 @@
{ {
"version": 3, "version": 3,
"serial": 1, "serial": 1,
"lineage": "mismatch" "lineage": "mismatch",
"modules": [
{
"path": ["root"],
"outputs": {
"foo": {
"type": "string",
"value": "bar"
}
},
"resources": {}
}
]
} }

View File

@ -1,5 +1,17 @@
{ {
"version": 3, "version": 3,
"serial": 2, "serial": 2,
"lineage": "hello" "lineage": "hello",
"modules": [
{
"path": ["root"],
"outputs": {
"foo": {
"type": "string",
"value": "baz"
}
},
"resources": {}
}
]
} }

View File

@ -15,28 +15,10 @@ func NewStateFile() *statefile.File {
return &statefile.File{ return &statefile.File{
Lineage: NewLineage(), Lineage: NewLineage(),
TerraformVersion: version.SemVer, TerraformVersion: version.SemVer,
State: states.NewState(),
} }
} }
// StateFile is a special helper to obtain a statefile representation
// of a state snapshot that can be written later by a call
func StateFile(mgr Storage, state *states.State) *statefile.File {
ret := &statefile.File{
State: state.DeepCopy(),
TerraformVersion: version.SemVer,
}
// If the given manager uses snapshot metadata then we'll save that
// in our file so we can check it again during WritePlannedStateUpdate.
if mr, ok := mgr.(PersistentMeta); ok {
m := mr.StateSnapshotMeta()
ret.Lineage = m.Lineage
ret.Serial = m.Serial
}
return ret
}
// RefreshAndRead refreshes the persistent snapshot in the given state manager // RefreshAndRead refreshes the persistent snapshot in the given state manager
// and then returns it. // and then returns it.
// //