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:
parent
6c7cecfbd8
commit
ec27526cc3
|
@ -62,6 +62,14 @@ type Local struct {
|
|||
StateBackupPath 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
|
||||
// here as they're loaded.
|
||||
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) {
|
||||
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 b.Backend != nil {
|
||||
|
@ -484,26 +493,31 @@ func (b *Local) schemaConfigure(ctx context.Context) error {
|
|||
// StatePaths returns the StatePath, StateOutPath, and StateBackupPath as
|
||||
// configured from the CLI.
|
||||
func (b *Local) StatePaths(name string) (stateIn, stateOut, backupOut string) {
|
||||
statePath := b.StatePath
|
||||
stateOutPath := b.StateOutPath
|
||||
backupPath := b.StateBackupPath
|
||||
statePath := b.OverrideStatePath
|
||||
stateOutPath := b.OverrideStateOutPath
|
||||
backupPath := b.OverrideStateBackupPath
|
||||
|
||||
if name == "" {
|
||||
name = backend.DefaultStateName
|
||||
isDefault := name == backend.DefaultStateName || name == ""
|
||||
|
||||
baseDir := ""
|
||||
if !isDefault {
|
||||
baseDir = filepath.Join(b.stateWorkspaceDir(), name)
|
||||
}
|
||||
|
||||
if name == backend.DefaultStateName {
|
||||
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 == "" {
|
||||
stateOutPath = statePath
|
||||
}
|
||||
|
||||
if backupPath == "" {
|
||||
backupPath = b.StateBackupPath
|
||||
}
|
||||
switch backupPath {
|
||||
case "-":
|
||||
backupPath = ""
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package local
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
)
|
||||
|
||||
|
@ -16,15 +18,18 @@ func (b *Local) CLIInit(opts *backend.CLIOpts) error {
|
|||
|
||||
// configure any new cli options
|
||||
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 != "" {
|
||||
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 != "" {
|
||||
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
|
||||
|
|
|
@ -1154,7 +1154,7 @@ func TestMetaBackend_configuredChangeCopy_multiToMulti(t *testing.T) {
|
|||
// Verify existing workspaces exist
|
||||
envPath := filepath.Join(backendLocal.DefaultWorkspaceDir, "env2", backendLocal.DefaultStateFilename)
|
||||
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
|
||||
envPath := filepath.Join("envdir-new", "env2", backendLocal.DefaultStateFilename)
|
||||
if _, err := os.Stat(envPath); err != nil {
|
||||
t.Fatal("env should exist")
|
||||
t.Fatalf("%s should exist, but does not", envPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
// it successfully before clobbering the original file it came from.
|
||||
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)
|
||||
if err != nil {
|
||||
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()
|
||||
|
||||
log.Print("[TRACE] statemgr.Filesystem: truncating the state file")
|
||||
if _, err := s.stateFileOut.Seek(0, os.SEEK_SET); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -179,19 +181,25 @@ func (s *Filesystem) writeState(state *states.State, meta *SnapshotMeta) error {
|
|||
|
||||
if state == nil {
|
||||
// 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
|
||||
}
|
||||
|
||||
if meta == nil {
|
||||
if s.readFile == nil || !statefile.StatesMarshalEqual(s.file.State, s.readFile.State) {
|
||||
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 {
|
||||
// Force new metadata
|
||||
s.file.Lineage = meta.Lineage
|
||||
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 {
|
||||
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
|
||||
// the file again.
|
||||
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
|
||||
f, err := os.Open(s.readPath)
|
||||
if err != nil {
|
||||
|
@ -237,8 +247,11 @@ func (s *Filesystem) RefreshState() error {
|
|||
reader = f
|
||||
}
|
||||
} else {
|
||||
log.Printf("[TRACE] statemgr.Filesystem: reading snapshot from %s", s.path)
|
||||
|
||||
// no state to refresh
|
||||
if s.stateFileOut == nil {
|
||||
log.Printf("[TRACE] statemgr.Filesystem: no state snapshot has been written yet")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -251,16 +264,21 @@ func (s *Filesystem) RefreshState() error {
|
|||
|
||||
// nothing to backup if there's no initial state
|
||||
if f == nil {
|
||||
log.Printf("[TRACE] statemgr.Filesystem: no initial state, so will skip writing a backup")
|
||||
s.writtenBackup = true
|
||||
}
|
||||
|
||||
// if there's no state we just assign the nil return value
|
||||
if err != nil && err != statefile.ErrNoState {
|
||||
log.Printf("[TRACE] statemgr.Filesystem: state snapshot is nil")
|
||||
return err
|
||||
}
|
||||
|
||||
s.file = f
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
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
|
||||
if _, err := os.Stat(s.path); os.IsNotExist(err) {
|
||||
|
|
Loading…
Reference in New Issue