command: Fix TestMetaBackend_configuredChangeCopy_multiToMulti

This was failing because we now handle the settings for the local backend
a little differently as a result of decoding it with the HCL2 machinery.

Specifically, the backend.State* fields are now assumed to be what is
given in configuration, and any CLI overrides are maintained separately
in OverrideState* fields so that they can be imposed "just in time" in
StatePaths.

This is particularly important because OverrideStatePath (when set) is
used regardless of workspace name, while StatePath is a suitable value
only for the "default" workspace, with others needing to be constructed
from StateWorkspaceDir instead.
This commit is contained in:
Martin Atkins 2018-11-14 16:03:14 -08:00
parent 6c7cecfbd8
commit ec27526cc3
4 changed files with 55 additions and 17 deletions

View File

@ -62,6 +62,14 @@ type Local struct {
StateBackupPath string StateBackupPath string
StateWorkspaceDir string StateWorkspaceDir string
// The OverrideState* paths are set based on per-operation CLI arguments
// and will override what'd be built from the State* fields if non-empty.
// While the interpretation of the State* fields depends on the active
// workspace, the OverrideState* fields are always used literally.
OverrideStatePath string
OverrideStateOutPath string
OverrideStateBackupPath string
// We only want to create a single instance of a local state, so store them // We only want to create a single instance of a local state, so store them
// here as they're loaded. // here as they're loaded.
states map[string]statemgr.Full states map[string]statemgr.Full
@ -251,6 +259,7 @@ func (b *Local) DeleteWorkspace(name string) error {
func (b *Local) StateMgr(name string) (statemgr.Full, error) { func (b *Local) StateMgr(name string) (statemgr.Full, error) {
statePath, stateOutPath, backupPath := b.StatePaths(name) statePath, stateOutPath, backupPath := b.StatePaths(name)
log.Printf("[TRACE] backend/local: state manager for workspace %q will:\n - read initial snapshot from %s\n - write new snapshots to %s\n - create any backup at %s", name, statePath, stateOutPath, backupPath)
// If we have a backend handling state, delegate to that. // If we have a backend handling state, delegate to that.
if b.Backend != nil { if b.Backend != nil {
@ -484,26 +493,31 @@ func (b *Local) schemaConfigure(ctx context.Context) error {
// StatePaths returns the StatePath, StateOutPath, and StateBackupPath as // StatePaths returns the StatePath, StateOutPath, and StateBackupPath as
// configured from the CLI. // configured from the CLI.
func (b *Local) StatePaths(name string) (stateIn, stateOut, backupOut string) { func (b *Local) StatePaths(name string) (stateIn, stateOut, backupOut string) {
statePath := b.StatePath statePath := b.OverrideStatePath
stateOutPath := b.StateOutPath stateOutPath := b.OverrideStateOutPath
backupPath := b.StateBackupPath backupPath := b.OverrideStateBackupPath
if name == "" { isDefault := name == backend.DefaultStateName || name == ""
name = backend.DefaultStateName
baseDir := ""
if !isDefault {
baseDir = filepath.Join(b.stateWorkspaceDir(), name)
} }
if name == backend.DefaultStateName {
if statePath == "" { if statePath == "" {
statePath = DefaultStateFilename if isDefault {
statePath = b.StatePath // s.StatePath applies only to the default workspace, since StateWorkspaceDir is used otherwise
}
if statePath == "" {
statePath = filepath.Join(baseDir, DefaultStateFilename)
} }
} else {
statePath = filepath.Join(b.stateWorkspaceDir(), name, DefaultStateFilename)
} }
if stateOutPath == "" { if stateOutPath == "" {
stateOutPath = statePath stateOutPath = statePath
} }
if backupPath == "" {
backupPath = b.StateBackupPath
}
switch backupPath { switch backupPath {
case "-": case "-":
backupPath = "" backupPath = ""

View File

@ -1,6 +1,8 @@
package local package local
import ( import (
"log"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
) )
@ -16,15 +18,18 @@ func (b *Local) CLIInit(opts *backend.CLIOpts) error {
// configure any new cli options // configure any new cli options
if opts.StatePath != "" { if opts.StatePath != "" {
b.StatePath = opts.StatePath log.Printf("[TRACE] backend/local: CLI option -state is overriding state path to %s", opts.StatePath)
b.OverrideStatePath = opts.StatePath
} }
if opts.StateOutPath != "" { if opts.StateOutPath != "" {
b.StateOutPath = opts.StateOutPath log.Printf("[TRACE] backend/local: CLI option -state-out is overriding state output path to %s", opts.StateOutPath)
b.OverrideStateOutPath = opts.StateOutPath
} }
if opts.StateBackupPath != "" { if opts.StateBackupPath != "" {
b.StateBackupPath = opts.StateBackupPath log.Printf("[TRACE] backend/local: CLI option -backup is overriding state backup path to %s", opts.StateBackupPath)
b.OverrideStateBackupPath = opts.StateBackupPath
} }
return nil return nil

View File

@ -1154,7 +1154,7 @@ func TestMetaBackend_configuredChangeCopy_multiToMulti(t *testing.T) {
// Verify existing workspaces exist // Verify existing workspaces exist
envPath := filepath.Join(backendLocal.DefaultWorkspaceDir, "env2", backendLocal.DefaultStateFilename) envPath := filepath.Join(backendLocal.DefaultWorkspaceDir, "env2", backendLocal.DefaultStateFilename)
if _, err := os.Stat(envPath); err != nil { if _, err := os.Stat(envPath); err != nil {
t.Fatal("env should exist") t.Fatalf("%s should exist, but does not", envPath)
} }
} }
@ -1162,7 +1162,7 @@ func TestMetaBackend_configuredChangeCopy_multiToMulti(t *testing.T) {
// Verify new workspaces exist // Verify new workspaces exist
envPath := filepath.Join("envdir-new", "env2", backendLocal.DefaultStateFilename) envPath := filepath.Join("envdir-new", "env2", backendLocal.DefaultStateFilename)
if _, err := os.Stat(envPath); err != nil { if _, err := os.Stat(envPath); err != nil {
t.Fatal("env should exist") t.Fatalf("%s should exist, but does not", envPath)
} }
} }
} }

View File

@ -143,6 +143,7 @@ func (s *Filesystem) writeState(state *states.State, meta *SnapshotMeta) error {
// We'll try to write our backup first, so we can be sure we've created // We'll try to write our backup first, so we can be sure we've created
// it successfully before clobbering the original file it came from. // it successfully before clobbering the original file it came from.
if !s.writtenBackup && s.backupFile != nil && s.backupPath != "" && !statefile.StatesMarshalEqual(state, s.backupFile.State) { if !s.writtenBackup && s.backupFile != nil && s.backupPath != "" && !statefile.StatesMarshalEqual(state, s.backupFile.State) {
log.Printf("[TRACE] statemgr.Filesystem: creating backup snapshot at %s", s.backupPath)
bfh, err := os.Create(s.backupPath) bfh, err := os.Create(s.backupPath)
if err != nil { if err != nil {
return fmt.Errorf("failed to create local state backup file: %s", err) return fmt.Errorf("failed to create local state backup file: %s", err)
@ -170,6 +171,7 @@ func (s *Filesystem) writeState(state *states.State, meta *SnapshotMeta) error {
} }
s.file.State = state.DeepCopy() s.file.State = state.DeepCopy()
log.Print("[TRACE] statemgr.Filesystem: truncating the state file")
if _, err := s.stateFileOut.Seek(0, os.SEEK_SET); err != nil { if _, err := s.stateFileOut.Seek(0, os.SEEK_SET); err != nil {
return err return err
} }
@ -179,19 +181,25 @@ func (s *Filesystem) writeState(state *states.State, meta *SnapshotMeta) error {
if state == nil { if state == nil {
// if we have no state, don't write anything else. // if we have no state, don't write anything else.
log.Print("[TRACE] statemgr.Filesystem: state is nil, so leaving the file empty")
return nil return nil
} }
if meta == nil { if meta == nil {
if s.readFile == nil || !statefile.StatesMarshalEqual(s.file.State, s.readFile.State) { if s.readFile == nil || !statefile.StatesMarshalEqual(s.file.State, s.readFile.State) {
s.file.Serial++ s.file.Serial++
log.Printf("[TRACE] statemgr.Filesystem: state has changed since last snapshot, so incrementing serial to %d", s.file.Serial)
} else {
log.Print("[TRACE] statemgr.Filesystem: no state changes since last snapshot")
} }
} else { } else {
// Force new metadata // Force new metadata
s.file.Lineage = meta.Lineage s.file.Lineage = meta.Lineage
s.file.Serial = meta.Serial s.file.Serial = meta.Serial
log.Printf("[TRACE] statemgr.Filesystem: forcing lineage %q serial %d for migration/import", s.file.Lineage, s.file.Serial)
} }
log.Printf("[TRACE] statemgr.Filesystem: writing snapshot at %s", s.path)
if err := statefile.Write(s.file, s.stateFileOut); err != nil { if err := statefile.Write(s.file, s.stateFileOut); err != nil {
return err return err
} }
@ -220,6 +228,8 @@ func (s *Filesystem) RefreshState() error {
// output file, and the output file has been locked already, we can't open // output file, and the output file has been locked already, we can't open
// the file again. // the file again.
if !s.written && (s.stateFileOut == nil || s.readPath != s.path) { if !s.written && (s.stateFileOut == nil || s.readPath != s.path) {
log.Printf("[TRACE] statemgr.Filesystem: reading initial snapshot from %s", s.readPath)
// we haven't written a state file yet, so load from readPath // we haven't written a state file yet, so load from readPath
f, err := os.Open(s.readPath) f, err := os.Open(s.readPath)
if err != nil { if err != nil {
@ -237,8 +247,11 @@ func (s *Filesystem) RefreshState() error {
reader = f reader = f
} }
} else { } else {
log.Printf("[TRACE] statemgr.Filesystem: reading snapshot from %s", s.path)
// no state to refresh // no state to refresh
if s.stateFileOut == nil { if s.stateFileOut == nil {
log.Printf("[TRACE] statemgr.Filesystem: no state snapshot has been written yet")
return nil return nil
} }
@ -251,16 +264,21 @@ func (s *Filesystem) RefreshState() error {
// nothing to backup if there's no initial state // nothing to backup if there's no initial state
if f == nil { if f == nil {
log.Printf("[TRACE] statemgr.Filesystem: no initial state, so will skip writing a backup")
s.writtenBackup = true s.writtenBackup = true
} }
// if there's no state we just assign the nil return value // if there's no state we just assign the nil return value
if err != nil && err != statefile.ErrNoState { if err != nil && err != statefile.ErrNoState {
log.Printf("[TRACE] statemgr.Filesystem: state snapshot is nil")
return err return err
} }
s.file = f s.file = f
s.readFile = s.file.DeepCopy() s.readFile = s.file.DeepCopy()
if s.file != nil {
log.Printf("[TRACE] statemgr.Filesystem: read snapshot with lineage %q serial %d", s.file.Lineage, s.file.Serial)
}
return nil return nil
} }
@ -400,6 +418,7 @@ func (s *Filesystem) WriteStateForMigration(f *statefile.File, force bool) error
// Open the state file, creating the directories and file as needed. // Open the state file, creating the directories and file as needed.
func (s *Filesystem) createStateFiles() error { func (s *Filesystem) createStateFiles() error {
log.Printf("[TRACE] statemgr.Filesystem: preparing to manage state snapshots at %s", s.path)
// This could race, but we only use it to clean up empty files // This could race, but we only use it to clean up empty files
if _, err := os.Stat(s.path); os.IsNotExist(err) { if _, err := os.Stat(s.path); os.IsNotExist(err) {