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
|
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 = ""
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue