Merge pull request #29755 from hashicorp/jbardin/first-plan-lineage
Check for stale plan with no state metadata
This commit is contained in:
commit
ef3c98466d
|
@ -290,14 +290,27 @@ func (b *Local) localRunForPlanFile(op *backend.Operation, pf *planfile.Reader,
|
||||||
// has changed since the plan was created. (All of the "real-world"
|
// has changed since the plan was created. (All of the "real-world"
|
||||||
// state manager implementations support this, but simpler test backends
|
// state manager implementations support this, but simpler test backends
|
||||||
// may not.)
|
// may not.)
|
||||||
if currentStateMeta.Lineage != "" && priorStateFile.Lineage != "" {
|
|
||||||
if priorStateFile.Serial != currentStateMeta.Serial || priorStateFile.Lineage != currentStateMeta.Lineage {
|
// Because the plan always contains a state, even if it is empty, the
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
// first plan to be applied will have empty snapshot metadata. In this
|
||||||
tfdiags.Error,
|
// case we compare only the serial in order to provide a more correct
|
||||||
"Saved plan is stale",
|
// error.
|
||||||
"The given plan file can no longer be applied because the state was changed by another operation after the plan was created.",
|
firstPlan := priorStateFile.Lineage == "" && priorStateFile.Serial == 0
|
||||||
))
|
|
||||||
}
|
switch {
|
||||||
|
case !firstPlan && priorStateFile.Lineage != currentStateMeta.Lineage:
|
||||||
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Saved plan does not match the given state",
|
||||||
|
"The given plan file can not be applied because it was created from a different state lineage.",
|
||||||
|
))
|
||||||
|
|
||||||
|
case priorStateFile.Serial != currentStateMeta.Serial:
|
||||||
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Saved plan is stale",
|
||||||
|
"The given plan file can no longer be applied because the state was changed by another operation after the plan was created.",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// When we're applying a saved plan, the input state is the "prior state"
|
// When we're applying a saved plan, the input state is the "prior state"
|
||||||
|
|
|
@ -710,7 +710,6 @@ func TestApply_plan(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApply_plan_backup(t *testing.T) {
|
func TestApply_plan_backup(t *testing.T) {
|
||||||
planPath := applyFixturePlanFile(t)
|
|
||||||
statePath := testTempFile(t)
|
statePath := testTempFile(t)
|
||||||
backupPath := testTempFile(t)
|
backupPath := testTempFile(t)
|
||||||
|
|
||||||
|
@ -724,11 +723,17 @@ func TestApply_plan_backup(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a state file that needs to be backed up
|
// create a state file that needs to be backed up
|
||||||
err := statemgr.NewFilesystem(statePath).WriteState(states.NewState())
|
fs := statemgr.NewFilesystem(statePath)
|
||||||
|
fs.StateSnapshotMeta()
|
||||||
|
err := fs.WriteState(states.NewState())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the plan file must contain the metadata from the prior state to be
|
||||||
|
// backed up
|
||||||
|
planPath := applyFixturePlanFileMatchState(t, fs.StateSnapshotMeta())
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
"-state", statePath,
|
"-state", statePath,
|
||||||
"-backup", backupPath,
|
"-backup", backupPath,
|
||||||
|
@ -2280,6 +2285,13 @@ func applyFixtureProvider() *terraform.MockProvider {
|
||||||
// a single change to create the test_instance.foo that is included in the
|
// a single change to create the test_instance.foo that is included in the
|
||||||
// "apply" test fixture, returning the location of that plan file.
|
// "apply" test fixture, returning the location of that plan file.
|
||||||
func applyFixturePlanFile(t *testing.T) string {
|
func applyFixturePlanFile(t *testing.T) string {
|
||||||
|
return applyFixturePlanFileMatchState(t, statemgr.SnapshotMeta{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyFixturePlanFileMatchState creates a planfile like applyFixturePlanFile,
|
||||||
|
// but inserts the state meta information if that plan must match a preexisting
|
||||||
|
// state.
|
||||||
|
func applyFixturePlanFileMatchState(t *testing.T, stateMeta statemgr.SnapshotMeta) string {
|
||||||
_, snap := testModuleWithSnapshot(t, "apply")
|
_, snap := testModuleWithSnapshot(t, "apply")
|
||||||
plannedVal := cty.ObjectVal(map[string]cty.Value{
|
plannedVal := cty.ObjectVal(map[string]cty.Value{
|
||||||
"id": cty.UnknownVal(cty.String),
|
"id": cty.UnknownVal(cty.String),
|
||||||
|
@ -2310,11 +2322,12 @@ func applyFixturePlanFile(t *testing.T) string {
|
||||||
After: plannedValRaw,
|
After: plannedValRaw,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return testPlanFile(
|
return testPlanFileMatchState(
|
||||||
t,
|
t,
|
||||||
snap,
|
snap,
|
||||||
states.NewState(),
|
states.NewState(),
|
||||||
plan,
|
plan,
|
||||||
|
stateMeta,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -229,15 +229,21 @@ func testPlan(t *testing.T) *plans.Plan {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPlanFile(t *testing.T, configSnap *configload.Snapshot, state *states.State, plan *plans.Plan) string {
|
func testPlanFile(t *testing.T, configSnap *configload.Snapshot, state *states.State, plan *plans.Plan) string {
|
||||||
|
return testPlanFileMatchState(t, configSnap, state, plan, statemgr.SnapshotMeta{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPlanFileMatchState(t *testing.T, configSnap *configload.Snapshot, state *states.State, plan *plans.Plan, stateMeta statemgr.SnapshotMeta) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
stateFile := &statefile.File{
|
stateFile := &statefile.File{
|
||||||
Lineage: "",
|
Lineage: stateMeta.Lineage,
|
||||||
|
Serial: stateMeta.Serial,
|
||||||
State: state,
|
State: state,
|
||||||
TerraformVersion: version.SemVer,
|
TerraformVersion: version.SemVer,
|
||||||
}
|
}
|
||||||
prevStateFile := &statefile.File{
|
prevStateFile := &statefile.File{
|
||||||
Lineage: "",
|
Lineage: stateMeta.Lineage,
|
||||||
|
Serial: stateMeta.Serial,
|
||||||
State: state, // we just assume no changes detected during refresh
|
State: state, // we just assume no changes detected during refresh
|
||||||
TerraformVersion: version.SemVer,
|
TerraformVersion: version.SemVer,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue