command: Fix TestMetaBackend_localDoesNotDeleteLocal

The changes to how we handle setting the state path on the local backend
broke the heuristic we were using here for detecting migration from one
local backend to another with the same state path, which would by default
end up deleting the state altogether after migration.

We now use the StatePaths method to do this, which takes into account
both the default values and any settings that have been set.

Additionally this addresses a flaw in the old method which could
potentially have deleted all non-default workspace state files if the
"path" setting were changed without also changing the "workspace_dir"
setting. This new approach is conservative because it will preserve all
of the files if any one overlaps.
This commit is contained in:
Martin Atkins 2018-11-15 16:01:56 -08:00
parent be79bf0412
commit 27abd9c6b8
2 changed files with 39 additions and 1 deletions

View File

@ -528,6 +528,42 @@ func (b *Local) StatePaths(name string) (stateIn, stateOut, backupOut string) {
return statePath, stateOutPath, backupPath
}
// PathsConflictWith returns true if any state path used by a workspace in
// the receiver is the same as any state path used by the other given
// local backend instance.
//
// This should be used when "migrating" from one local backend configuration to
// another in order to avoid deleting the "old" state snapshots if they are
// in the same files as the "new" state snapshots.
func (b *Local) PathsConflictWith(other *Local) bool {
otherPaths := map[string]struct{}{}
otherWorkspaces, err := other.Workspaces()
if err != nil {
// If we can't enumerate the workspaces then we'll conservatively
// assume that paths _do_ overlap, since we can't be certain.
return true
}
for _, name := range otherWorkspaces {
p, _, _ := other.StatePaths(name)
otherPaths[p] = struct{}{}
}
ourWorkspaces, err := other.Workspaces()
if err != nil {
// If we can't enumerate the workspaces then we'll conservatively
// assume that paths _do_ overlap, since we can't be certain.
return true
}
for _, name := range ourWorkspaces {
p, _, _ := b.StatePaths(name)
if _, exists := otherPaths[p]; exists {
return true
}
}
return false
}
// this only ensures that the named directory exists
func (b *Local) createState(name string) error {
if name == backend.DefaultStateName {

View File

@ -668,13 +668,15 @@ func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *state.LocalSta
erase := true
if newLocalB, ok := b.(*backendLocal.Local); ok {
if localB, ok := localB.(*backendLocal.Local); ok {
if newLocalB.StatePath == localB.StatePath {
if newLocalB.PathsConflictWith(localB) {
erase = false
log.Printf("[TRACE] Meta.Backend: both old and new backends share the same local state paths, so not erasing old state")
}
}
}
if erase {
log.Printf("[TRACE] Meta.Backend: removing old state snapshots from old backend")
for _, localState := range localStates {
// We always delete the local state, unless that was our new state too.
if err := localState.WriteState(nil); err != nil {