diff --git a/backend/local/backend.go b/backend/local/backend.go index a6329404c..84965850b 100644 --- a/backend/local/backend.go +++ b/backend/local/backend.go @@ -307,7 +307,7 @@ func (b *Local) StatePaths(name string) (string, string, string, error) { if name == backend.DefaultStateName { if statePath == "" { - statePath = name + statePath = DefaultStateFilename } } else { statePath = filepath.Join(DefaultEnvDir, name, DefaultStateFilename) diff --git a/command/env_command.go b/command/env_command.go index 7701a2b78..425013401 100644 --- a/command/env_command.go +++ b/command/env_command.go @@ -64,5 +64,10 @@ anyways and risk dangling resources, use the '-force' flag. The resources managed by the deleted environment may still exist, but are no longer manageable by Terraform since the state has been deleted. +` + + envDelCurrent = `Environment %[1]q is your active environment! +You cannot delete the currently active environment. Please switch +to another environment and try again. ` ) diff --git a/command/env_command_test.go b/command/env_command_test.go index 7caa11932..356c8d66a 100644 --- a/command/env_command_test.go +++ b/command/env_command_test.go @@ -4,7 +4,6 @@ import ( "io/ioutil" "os" "path/filepath" - "sort" "strings" "testing" @@ -24,10 +23,7 @@ func TestEnv_createAndChange(t *testing.T) { newCmd := &EnvNewCommand{} - current, err := currentEnv() - if err != nil { - t.Fatal(err) - } + current := newCmd.Env() if current != backend.DefaultStateName { t.Fatal("current env should be 'default'") } @@ -39,12 +35,9 @@ func TestEnv_createAndChange(t *testing.T) { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter) } - current, err = currentEnv() - if err != nil { - t.Fatal(err) - } + current = newCmd.Env() if current != "test" { - t.Fatal("current env should be 'test'") + t.Fatalf("current env should be 'test', got %q", current) } selCmd := &EnvSelectCommand{} @@ -55,11 +48,7 @@ func TestEnv_createAndChange(t *testing.T) { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter) } - current, err = currentEnv() - if err != nil { - t.Fatal(err) - } - + current = newCmd.Env() if current != backend.DefaultStateName { t.Fatal("current env should be 'default'") } @@ -173,29 +162,35 @@ func TestEnv_delete(t *testing.T) { t.Fatal(err) } - current, err := currentEnv() - if err != nil { - t.Fatal(err) - } - - if current != "test" { - t.Fatal("wrong env:", current) - } - ui := new(cli.MockUi) delCmd := &EnvDeleteCommand{ Meta: Meta{Ui: ui}, } - args := []string{"test"} - if code := delCmd.Run(args); code != 0 { - t.Fatalf("failure: %s", ui.ErrorWriter) + + current := delCmd.Env() + if current != "test" { + t.Fatal("wrong env:", current) } - current, err = currentEnv() - if err != nil { + // we can't delete out current environment + args := []string{"test"} + if code := delCmd.Run(args); code == 0 { + t.Fatal("expected error deleting current env") + } + + // change back to default + if err := delCmd.SetEnv(backend.DefaultStateName); err != nil { t.Fatal(err) } + // try the delete again + ui = new(cli.MockUi) + delCmd.Meta.Ui = ui + if code := delCmd.Run(args); code != 0 { + t.Fatalf("error deleting env: %s", ui.ErrorWriter) + } + + current = delCmd.Env() if current != backend.DefaultStateName { t.Fatalf("wrong env: %q", current) } @@ -255,58 +250,3 @@ func TestEnv_deleteWithState(t *testing.T) { t.Fatal("env 'test' still exists!") } } - -func currentEnv() (string, error) { - contents, err := ioutil.ReadFile(filepath.Join(DefaultDataDir, local.DefaultEnvFile)) - if os.IsNotExist(err) { - return backend.DefaultStateName, nil - } - if err != nil { - return "", err - } - - current := strings.TrimSpace(string(contents)) - if current == "" { - current = backend.DefaultStateName - } - - return current, nil -} - -func envStatePath() (string, error) { - currentEnv, err := currentEnv() - if err != nil { - return "", err - } - - if currentEnv == backend.DefaultStateName { - return DefaultStateFilename, nil - } - - return filepath.Join(local.DefaultEnvDir, currentEnv, DefaultStateFilename), nil -} - -func listEnvs() ([]string, error) { - entries, err := ioutil.ReadDir(local.DefaultEnvDir) - // no error if there's no envs configured - if os.IsNotExist(err) { - return []string{backend.DefaultStateName}, nil - } - if err != nil { - return nil, err - } - - var envs []string - for _, entry := range entries { - if entry.IsDir() { - envs = append(envs, filepath.Base(entry.Name())) - } - } - - sort.Strings(envs) - - // always start with "default" - envs = append([]string{backend.DefaultStateName}, envs...) - - return envs, nil -} diff --git a/command/env_delete.go b/command/env_delete.go index b573e57d5..1a773c0cc 100644 --- a/command/env_delete.go +++ b/command/env_delete.go @@ -4,7 +4,6 @@ import ( "fmt" "strings" - "github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/state" "github.com/mitchellh/cli" @@ -46,13 +45,7 @@ func (c *EnvDeleteCommand) Run(args []string) int { return 1 } - multi, ok := b.(backend.MultiState) - if !ok { - c.Ui.Error(envNotSupported) - return 1 - } - - states, current, err := multi.States() + states, err := b.States() if err != nil { c.Ui.Error(err.Error()) return 1 @@ -71,24 +64,13 @@ func (c *EnvDeleteCommand) Run(args []string) int { return 1 } - // In order to check if the state being deleted is empty, we need to change - // to that state and load it. - if current != delEnv { - if err := multi.ChangeState(delEnv); err != nil { - c.Ui.Error(err.Error()) - return 1 - } - - // always try to change back after - defer func() { - if err := multi.ChangeState(current); err != nil { - c.Ui.Error(err.Error()) - } - }() + if delEnv == c.Env() { + c.Ui.Error(fmt.Sprintf(envDelCurrent, delEnv)) + return 1 } // we need the actual state to see if it's empty - sMgr, err := b.State() + sMgr, err := b.State(delEnv) if err != nil { c.Ui.Error(err.Error()) return 1 @@ -116,7 +98,7 @@ func (c *EnvDeleteCommand) Run(args []string) int { } defer clistate.Unlock(sMgr, lockID, c.Ui, c.Colorize()) - err = multi.DeleteState(delEnv) + err = b.DeleteState(delEnv) if err != nil { c.Ui.Error(err.Error()) return 1 diff --git a/command/env_list.go b/command/env_list.go index cffeb38f3..219b32bd0 100644 --- a/command/env_list.go +++ b/command/env_list.go @@ -4,8 +4,6 @@ import ( "bytes" "fmt" "strings" - - "github.com/hashicorp/terraform/backend" ) type EnvListCommand struct { @@ -34,21 +32,17 @@ func (c *EnvListCommand) Run(args []string) int { return 1 } - multi, ok := b.(backend.MultiState) - if !ok { - c.Ui.Error(envNotSupported) - return 1 - } - - states, current, err := multi.States() + states, err := b.States() if err != nil { c.Ui.Error(err.Error()) return 1 } + env := c.Env() + var out bytes.Buffer for _, s := range states { - if s == current { + if s == env { out.WriteString("* ") } else { out.WriteString(" ") diff --git a/command/env_new.go b/command/env_new.go index eb5c99076..47457a0c1 100644 --- a/command/env_new.go +++ b/command/env_new.go @@ -5,7 +5,6 @@ import ( "os" "strings" - "github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/terraform" "github.com/mitchellh/cli" @@ -49,13 +48,7 @@ func (c *EnvNewCommand) Run(args []string) int { return 1 } - multi, ok := b.(backend.MultiState) - if !ok { - c.Ui.Error(envNotSupported) - return 1 - } - - states, _, err := multi.States() + states, err := b.States() for _, s := range states { if newEnv == s { c.Ui.Error(fmt.Sprintf(envExists, newEnv)) @@ -63,12 +56,18 @@ func (c *EnvNewCommand) Run(args []string) int { } } - err = multi.ChangeState(newEnv) + _, err = b.State(newEnv) if err != nil { c.Ui.Error(err.Error()) return 1 } + // now save the current env locally + if err := c.SetEnv(newEnv); err != nil { + c.Ui.Error(fmt.Sprintf("error saving new environment name: %s", err)) + return 1 + } + c.Ui.Output( c.Colorize().Color( fmt.Sprintf(envCreated, newEnv), @@ -81,7 +80,7 @@ func (c *EnvNewCommand) Run(args []string) int { } // load the new Backend state - sMgr, err := b.State() + sMgr, err := b.State(newEnv) if err != nil { c.Ui.Error(err.Error()) return 1 diff --git a/command/env_select.go b/command/env_select.go index adfd2f056..d13bdd556 100644 --- a/command/env_select.go +++ b/command/env_select.go @@ -4,7 +4,6 @@ import ( "fmt" "strings" - "github.com/hashicorp/terraform/backend" "github.com/mitchellh/cli" ) @@ -41,19 +40,14 @@ func (c *EnvSelectCommand) Run(args []string) int { name := args[0] - multi, ok := b.(backend.MultiState) - if !ok { - c.Ui.Error(envNotSupported) - return 1 - } - - states, current, err := multi.States() + states, err := b.States() if err != nil { c.Ui.Error(err.Error()) return 1 } - if current == name { + if name == c.Env() { + // already using this env return 0 } @@ -70,7 +64,7 @@ func (c *EnvSelectCommand) Run(args []string) int { return 1 } - err = multi.ChangeState(name) + err = c.SetEnv(name) if err != nil { c.Ui.Error(err.Error()) return 1 diff --git a/command/meta.go b/command/meta.go index cc29b2aaf..12f767d2a 100644 --- a/command/meta.go +++ b/command/meta.go @@ -2,6 +2,7 @@ package command import ( "bufio" + "bytes" "flag" "fmt" "io" @@ -14,6 +15,8 @@ import ( "time" "github.com/hashicorp/go-getter" + "github.com/hashicorp/terraform/backend" + "github.com/hashicorp/terraform/backend/local" "github.com/hashicorp/terraform/helper/experiment" "github.com/hashicorp/terraform/helper/variables" "github.com/hashicorp/terraform/helper/wrappedstreams" @@ -406,3 +409,44 @@ func (m *Meta) outputShadowError(err error, output bool) bool { return true } + +// Env returns the name of the currently configured environment, corresponding +// to the desired named state. +func (m *Meta) Env() string { + dataDir := m.dataDir + if m.dataDir == "" { + dataDir = DefaultDataDir + } + + envData, err := ioutil.ReadFile(filepath.Join(dataDir, local.DefaultEnvFile)) + current := string(bytes.TrimSpace(envData)) + if current == "" { + current = backend.DefaultStateName + } + + // return default if the file simply doesn't exist + if err != nil && !os.IsNotExist(err) { + log.Printf("[ERROR] failed to read current environment: %s", err) + } + + return current +} + +// SetEnv saves the named environment to the local filesystem. +func (m *Meta) SetEnv(name string) error { + dataDir := m.dataDir + if m.dataDir == "" { + dataDir = DefaultDataDir + } + + err := os.MkdirAll(dataDir, 0755) + if err != nil { + return err + } + + err = ioutil.WriteFile(filepath.Join(dataDir, local.DefaultEnvFile), []byte(name), 0644) + if err != nil { + return err + } + return nil +} diff --git a/command/meta_backend.go b/command/meta_backend.go index 629fc9642..d47d6c513 100644 --- a/command/meta_backend.go +++ b/command/meta_backend.go @@ -148,6 +148,7 @@ func (m *Meta) Operation() *backend.Operation { PlanOutBackend: m.backendState, Targets: m.targets, UIIn: m.UIInput(), + Environment: m.Env(), } } @@ -526,8 +527,10 @@ func (m *Meta) backendFromPlan(opts *BackendOpts) (backend.Backend, error) { return nil, err } + env := m.Env() + // Get the state so we can determine the effect of using this plan - realMgr, err := b.State() + realMgr, err := b.State(env) if err != nil { return nil, fmt.Errorf("Error reading state: %s", err) } @@ -642,7 +645,10 @@ func (m *Meta) backend_c_r_S( if err != nil { return nil, fmt.Errorf(strings.TrimSpace(errBackendLocalRead), err) } - localState, err := localB.State() + + env := m.Env() + + localState, err := localB.State(env) if err != nil { return nil, fmt.Errorf(strings.TrimSpace(errBackendLocalRead), err) } @@ -656,7 +662,7 @@ func (m *Meta) backend_c_r_S( return nil, fmt.Errorf( strings.TrimSpace(errBackendSavedUnsetConfig), s.Backend.Type, err) } - backendState, err := b.State() + backendState, err := b.State(env) if err != nil { return nil, fmt.Errorf( strings.TrimSpace(errBackendSavedUnsetConfig), s.Backend.Type, err) @@ -751,7 +757,10 @@ func (m *Meta) backend_c_R_S( if err != nil { return nil, fmt.Errorf(errBackendLocalRead, err) } - localState, err := localB.State() + + env := m.Env() + + localState, err := localB.State(env) if err != nil { return nil, fmt.Errorf(errBackendLocalRead, err) } @@ -782,7 +791,7 @@ func (m *Meta) backend_c_R_S( if err != nil { return nil, err } - oldState, err := oldB.State() + oldState, err := oldB.State(env) if err != nil { return nil, fmt.Errorf( strings.TrimSpace(errBackendSavedUnsetConfig), s.Backend.Type, err) @@ -884,7 +893,10 @@ func (m *Meta) backend_C_R_s( if err != nil { return nil, err } - oldState, err := oldB.State() + + env := m.Env() + + oldState, err := oldB.State(env) if err != nil { return nil, fmt.Errorf( strings.TrimSpace(errBackendSavedUnsetConfig), s.Backend.Type, err) @@ -895,7 +907,7 @@ func (m *Meta) backend_C_R_s( } // Get the new state - newState, err := b.State() + newState, err := b.State(env) if err != nil { return nil, fmt.Errorf(strings.TrimSpace(errBackendNewRead), err) } @@ -949,7 +961,10 @@ func (m *Meta) backend_C_r_s( if err != nil { return nil, fmt.Errorf(errBackendLocalRead, err) } - localState, err := localB.State() + + env := m.Env() + + localState, err := localB.State(env) if err != nil { return nil, fmt.Errorf(errBackendLocalRead, err) } @@ -960,7 +975,7 @@ func (m *Meta) backend_C_r_s( // If the local state is not empty, we need to potentially do a // state migration to the new backend (with user permission). if localS := localState.State(); !localS.Empty() { - backendState, err := b.State() + backendState, err := b.State(env) if err != nil { return nil, fmt.Errorf(errBackendRemoteRead, err) } @@ -1065,7 +1080,9 @@ func (m *Meta) backend_C_r_S_changed( "Error loading previously configured backend: %s", err) } - oldState, err := oldB.State() + env := m.Env() + + oldState, err := oldB.State(env) if err != nil { return nil, fmt.Errorf( strings.TrimSpace(errBackendSavedUnsetConfig), s.Backend.Type, err) @@ -1076,7 +1093,7 @@ func (m *Meta) backend_C_r_S_changed( } // Get the new state - newState, err := b.State() + newState, err := b.State(env) if err != nil { return nil, fmt.Errorf(strings.TrimSpace(errBackendNewRead), err) } @@ -1226,7 +1243,10 @@ func (m *Meta) backend_C_R_S_unchanged( if err != nil { return nil, err } - oldState, err := oldB.State() + + env := m.Env() + + oldState, err := oldB.State(env) if err != nil { return nil, fmt.Errorf( strings.TrimSpace(errBackendSavedUnsetConfig), s.Remote.Type, err) @@ -1237,7 +1257,7 @@ func (m *Meta) backend_C_R_S_unchanged( } // Get the new state - newState, err := b.State() + newState, err := b.State(env) if err != nil { return nil, fmt.Errorf(strings.TrimSpace(errBackendNewRead), err) } diff --git a/command/meta_backend_test.go b/command/meta_backend_test.go index fc9cfb5b9..4ce7c1b80 100644 --- a/command/meta_backend_test.go +++ b/command/meta_backend_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/helper/copy" "github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/terraform" @@ -29,7 +30,7 @@ func TestMetaBackend_emptyDir(t *testing.T) { } // Write some state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -99,7 +100,7 @@ func TestMetaBackend_emptyWithDefaultState(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -172,7 +173,7 @@ func TestMetaBackend_emptyWithExplicitState(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -231,7 +232,7 @@ func TestMetaBackend_emptyLegacyRemote(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -280,7 +281,7 @@ func TestMetaBackend_configureNew(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -349,7 +350,7 @@ func TestMetaBackend_configureNewWithState(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -425,7 +426,7 @@ func TestMetaBackend_configureNewWithStateNoMigrate(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -470,7 +471,7 @@ func TestMetaBackend_configureNewWithStateExisting(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -544,7 +545,7 @@ func TestMetaBackend_configureNewWithStateExistingNoMigrate(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -618,7 +619,7 @@ func TestMetaBackend_configureNewLegacy(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -712,7 +713,7 @@ func TestMetaBackend_configureNewLegacyCopy(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -798,7 +799,7 @@ func TestMetaBackend_configuredUnchanged(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -845,7 +846,7 @@ func TestMetaBackend_configuredChange(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -924,7 +925,7 @@ func TestMetaBackend_configuredChangeCopy(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -971,7 +972,7 @@ func TestMetaBackend_configuredUnset(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -1055,7 +1056,7 @@ func TestMetaBackend_configuredUnsetCopy(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -1134,7 +1135,7 @@ func TestMetaBackend_configuredUnchangedLegacy(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -1237,7 +1238,7 @@ func TestMetaBackend_configuredUnchangedLegacyCopy(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -1340,7 +1341,7 @@ func TestMetaBackend_configuredChangedLegacy(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -1440,7 +1441,7 @@ func TestMetaBackend_configuredChangedLegacyCopyBackend(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -1543,7 +1544,7 @@ func TestMetaBackend_configuredChangedLegacyCopyLegacy(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -1646,7 +1647,7 @@ func TestMetaBackend_configuredChangedLegacyCopyBoth(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -1749,7 +1750,7 @@ func TestMetaBackend_configuredUnsetWithLegacyNoCopy(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -1839,7 +1840,7 @@ func TestMetaBackend_configuredUnsetWithLegacyCopyBackend(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -1937,7 +1938,7 @@ func TestMetaBackend_configuredUnsetWithLegacyCopyLegacy(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -2035,7 +2036,7 @@ func TestMetaBackend_configuredUnsetWithLegacyCopyBoth(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -2136,7 +2137,7 @@ func TestMetaBackend_planLocal(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -2233,7 +2234,7 @@ func TestMetaBackend_planLocalStatePath(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -2319,7 +2320,7 @@ func TestMetaBackend_planLocalMatch(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -2519,7 +2520,7 @@ func TestMetaBackend_planBackendEmptyDir(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -2621,7 +2622,7 @@ func TestMetaBackend_planBackendMatch(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } @@ -2784,7 +2785,7 @@ func TestMetaBackend_planLegacy(t *testing.T) { } // Check the state - s, err := b.State() + s, err := b.State(backend.DefaultStateName) if err != nil { t.Fatalf("bad: %s", err) } diff --git a/command/meta_test.go b/command/meta_test.go index 5dde0f1ff..1a74484fb 100644 --- a/command/meta_test.go +++ b/command/meta_test.go @@ -8,6 +8,7 @@ import ( "reflect" "testing" + "github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/terraform" ) @@ -272,3 +273,37 @@ func TestMeta_addModuleDepthFlag(t *testing.T) { } } } + +func TestMeta_Env(t *testing.T) { + td := tempDir(t) + os.MkdirAll(td, 0755) + defer os.RemoveAll(td) + defer testChdir(t, td)() + + m := new(Meta) + + env := m.Env() + + if env != backend.DefaultStateName { + t.Fatalf("expected env %q, got env %q", backend.DefaultStateName, env) + } + + testEnv := "test_env" + if err := m.SetEnv(testEnv); err != nil { + t.Fatal("error setting env:", err) + } + + env = m.Env() + if env != testEnv { + t.Fatalf("expected env %q, got env %q", testEnv, env) + } + + if err := m.SetEnv(backend.DefaultStateName); err != nil { + t.Fatal("error setting env:", err) + } + + env = m.Env() + if env != backend.DefaultStateName { + t.Fatalf("expected env %q, got env %q", backend.DefaultStateName, env) + } +} diff --git a/command/output.go b/command/output.go index 2b12dee62..2f5f6468b 100644 --- a/command/output.go +++ b/command/output.go @@ -50,8 +50,10 @@ func (c *OutputCommand) Run(args []string) int { return 1 } + env := c.Env() + // Get the state - stateStore, err := b.State() + stateStore, err := b.State(env) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) return 1 diff --git a/command/show.go b/command/show.go index 07afae053..9be482811 100644 --- a/command/show.go +++ b/command/show.go @@ -74,8 +74,10 @@ func (c *ShowCommand) Run(args []string) int { return 1 } + env := c.Env() + // Get the state - stateStore, err := b.State() + stateStore, err := b.State(env) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) return 1 diff --git a/command/state_command.go b/command/state_command.go index ce4e0a2ec..7e3a6af1b 100644 --- a/command/state_command.go +++ b/command/state_command.go @@ -9,7 +9,7 @@ import ( // StateCommand is a Command implementation that just shows help for // the subcommands nested below it. type StateCommand struct { - Meta + StateMeta } func (c *StateCommand) Run(args []string) int { diff --git a/command/state_list.go b/command/state_list.go index afc5c9889..0e7436397 100644 --- a/command/state_list.go +++ b/command/state_list.go @@ -11,7 +11,7 @@ import ( // StateListCommand is a Command implementation that lists the resources // within a state file. type StateListCommand struct { - Meta + StateMeta } func (c *StateListCommand) Run(args []string) int { @@ -31,8 +31,9 @@ func (c *StateListCommand) Run(args []string) int { return 1 } + env := c.Env() // Get the state - state, err := b.State() + state, err := b.State(env) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) return 1 diff --git a/command/state_list_test.go b/command/state_list_test.go index 86c4f7194..f2c85e26f 100644 --- a/command/state_list_test.go +++ b/command/state_list_test.go @@ -16,9 +16,11 @@ func TestStateList(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &StateListCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + StateMeta: StateMeta{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, }, } @@ -47,9 +49,11 @@ func TestStateList_backendState(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &StateListCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + StateMeta: StateMeta{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, }, } diff --git a/command/state_meta.go b/command/state_meta.go index eccfba447..0e31b874b 100644 --- a/command/state_meta.go +++ b/command/state_meta.go @@ -11,7 +11,9 @@ import ( ) // StateMeta is the meta struct that should be embedded in state subcommands. -type StateMeta struct{} +type StateMeta struct { + Meta +} // State returns the state for this meta. This is different then Meta.State // in the way that backups are done. This configures backups to be timestamped @@ -23,8 +25,9 @@ func (c *StateMeta) State(m *Meta) (state.State, error) { return nil, err } + env := c.Env() // Get the state - s, err := b.State() + s, err := b.State(env) if err != nil { return nil, err } @@ -36,7 +39,7 @@ func (c *StateMeta) State(m *Meta) (state.State, error) { panic(err) } localB := localRaw.(*backendlocal.Local) - _, stateOutPath, _, err := localB.StatePaths() + _, stateOutPath, _, err := localB.StatePaths(env) if err != nil { return nil, err } diff --git a/command/state_mv.go b/command/state_mv.go index 7982d7b92..ad6b5c048 100644 --- a/command/state_mv.go +++ b/command/state_mv.go @@ -10,7 +10,6 @@ import ( // StateMvCommand is a Command implementation that shows a single resource. type StateMvCommand struct { - Meta StateMeta } diff --git a/command/state_mv_test.go b/command/state_mv_test.go index d479b4ccb..9fff6ed12 100644 --- a/command/state_mv_test.go +++ b/command/state_mv_test.go @@ -46,9 +46,11 @@ func TestStateMv(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &StateMvCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + StateMeta: StateMeta{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, }, } @@ -113,9 +115,11 @@ func TestStateMv_backupExplicit(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &StateMvCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + StateMeta: StateMeta{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, }, } @@ -168,9 +172,11 @@ func TestStateMv_stateOutNew(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &StateMvCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + StateMeta: StateMeta{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, }, } @@ -240,9 +246,11 @@ func TestStateMv_stateOutExisting(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &StateMvCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + StateMeta: StateMeta{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, }, } @@ -281,9 +289,11 @@ func TestStateMv_noState(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &StateMvCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + StateMeta: StateMeta{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, }, } @@ -342,9 +352,11 @@ func TestStateMv_stateOutNew_count(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &StateMvCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + StateMeta: StateMeta{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, }, } diff --git a/command/state_pull.go b/command/state_pull.go index 73059c6cc..a3679a26a 100644 --- a/command/state_pull.go +++ b/command/state_pull.go @@ -11,7 +11,6 @@ import ( // StatePullCommand is a Command implementation that shows a single resource. type StatePullCommand struct { - Meta StateMeta } @@ -32,7 +31,8 @@ func (c *StatePullCommand) Run(args []string) int { } // Get the state - state, err := b.State() + env := c.Env() + state, err := b.State(env) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) return 1 diff --git a/command/state_pull_test.go b/command/state_pull_test.go index 3176a4c54..d468dbae4 100644 --- a/command/state_pull_test.go +++ b/command/state_pull_test.go @@ -20,9 +20,11 @@ func TestStatePull(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &StatePullCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + StateMeta: StateMeta{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, }, } diff --git a/command/state_push.go b/command/state_push.go index a2b130f30..b6e7a90d6 100644 --- a/command/state_push.go +++ b/command/state_push.go @@ -11,7 +11,6 @@ import ( // StatePushCommand is a Command implementation that shows a single resource. type StatePushCommand struct { - Meta StateMeta } @@ -52,7 +51,8 @@ func (c *StatePushCommand) Run(args []string) int { } // Get the state - state, err := b.State() + env := c.Env() + state, err := b.State(env) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err)) return 1 diff --git a/command/state_push_test.go b/command/state_push_test.go index d63b193f6..43e0e14c6 100644 --- a/command/state_push_test.go +++ b/command/state_push_test.go @@ -20,9 +20,11 @@ func TestStatePush_empty(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &StatePushCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + StateMeta: StateMeta{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, }, } @@ -49,9 +51,11 @@ func TestStatePush_replaceMatch(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &StatePushCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + StateMeta: StateMeta{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, }, } @@ -78,9 +82,11 @@ func TestStatePush_lineageMismatch(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &StatePushCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + StateMeta: StateMeta{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, }, } @@ -107,9 +113,11 @@ func TestStatePush_serialNewer(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &StatePushCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + StateMeta: StateMeta{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, }, } @@ -136,9 +144,11 @@ func TestStatePush_serialOlder(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &StatePushCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + StateMeta: StateMeta{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, }, } diff --git a/command/state_rm.go b/command/state_rm.go index b2491c4e8..a80a0d80d 100644 --- a/command/state_rm.go +++ b/command/state_rm.go @@ -9,7 +9,6 @@ import ( // StateRmCommand is a Command implementation that shows a single resource. type StateRmCommand struct { - Meta StateMeta } diff --git a/command/state_rm_test.go b/command/state_rm_test.go index bb3734502..0c3eacb3a 100644 --- a/command/state_rm_test.go +++ b/command/state_rm_test.go @@ -46,9 +46,11 @@ func TestStateRm(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &StateRmCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + StateMeta: StateMeta{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, }, } @@ -112,9 +114,11 @@ func TestStateRm_backupExplicit(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &StateRmCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + StateMeta: StateMeta{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, }, } diff --git a/command/state_show.go b/command/state_show.go index be6df8bf9..a7a939bff 100644 --- a/command/state_show.go +++ b/command/state_show.go @@ -12,7 +12,6 @@ import ( // StateShowCommand is a Command implementation that shows a single resource. type StateShowCommand struct { - Meta StateMeta } @@ -34,7 +33,8 @@ func (c *StateShowCommand) Run(args []string) int { } // Get the state - state, err := b.State() + env := c.Env() + state, err := b.State(env) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) return 1 diff --git a/command/state_show_test.go b/command/state_show_test.go index 9e0ede396..f8b56c25f 100644 --- a/command/state_show_test.go +++ b/command/state_show_test.go @@ -34,9 +34,11 @@ func TestStateShow(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &StateShowCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + StateMeta: StateMeta{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, }, } @@ -92,9 +94,11 @@ func TestStateShow_multi(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &StateShowCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + StateMeta: StateMeta{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, }, } @@ -114,9 +118,11 @@ func TestStateShow_noState(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &StateShowCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + StateMeta: StateMeta{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, }, } @@ -134,9 +140,11 @@ func TestStateShow_emptyState(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &StateShowCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + StateMeta: StateMeta{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, }, } @@ -163,9 +171,11 @@ func TestStateShow_emptyStateWithModule(t *testing.T) { p := testProvider() ui := new(cli.MockUi) c := &StateShowCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + StateMeta: StateMeta{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, }, } diff --git a/command/taint.go b/command/taint.go index 0fd2b4de9..487099187 100644 --- a/command/taint.go +++ b/command/taint.go @@ -67,7 +67,8 @@ func (c *TaintCommand) Run(args []string) int { } // Get the state - st, err := b.State() + env := c.Env() + st, err := b.State(env) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) return 1 diff --git a/command/unlock.go b/command/unlock.go index b50713aaa..010fd9332 100644 --- a/command/unlock.go +++ b/command/unlock.go @@ -52,7 +52,8 @@ func (c *UnlockCommand) Run(args []string) int { return 1 } - st, err := b.State() + env := c.Env() + st, err := b.State(env) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) return 1 @@ -102,7 +103,6 @@ func (c *UnlockCommand) Run(args []string) int { } } - // FIXME: unlock should require the lock ID if err := s.Unlock(lockID); err != nil { c.Ui.Error(fmt.Sprintf("Failed to unlock state: %s", err)) return 1 diff --git a/command/untaint.go b/command/untaint.go index c3b413252..ab697b823 100644 --- a/command/untaint.go +++ b/command/untaint.go @@ -55,7 +55,8 @@ func (c *UntaintCommand) Run(args []string) int { } // Get the state - st, err := b.State() + env := c.Env() + st, err := b.State(env) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) return 1 diff --git a/commands.go b/commands.go index f2f7b4eda..e1618003e 100644 --- a/commands.go +++ b/commands.go @@ -46,6 +46,11 @@ func init() { "debug": struct{}{}, // includes all subcommands } + // meta struct used in state commands + stateMeta := command.StateMeta{ + Meta: meta, + } + Commands = map[string]cli.CommandFactory{ "apply": func() (cli.Command, error) { return &command.ApplyCommand{ @@ -217,43 +222,43 @@ func init() { "state": func() (cli.Command, error) { return &command.StateCommand{ - Meta: meta, + StateMeta: stateMeta, }, nil }, "state list": func() (cli.Command, error) { return &command.StateListCommand{ - Meta: meta, + StateMeta: stateMeta, }, nil }, "state rm": func() (cli.Command, error) { return &command.StateRmCommand{ - Meta: meta, + StateMeta: stateMeta, }, nil }, "state mv": func() (cli.Command, error) { return &command.StateMvCommand{ - Meta: meta, + StateMeta: stateMeta, }, nil }, "state pull": func() (cli.Command, error) { return &command.StatePullCommand{ - Meta: meta, + StateMeta: stateMeta, }, nil }, "state push": func() (cli.Command, error) { return &command.StatePushCommand{ - Meta: meta, + StateMeta: stateMeta, }, nil }, "state show": func() (cli.Command, error) { return &command.StateShowCommand{ - Meta: meta, + StateMeta: stateMeta, }, nil }, }