Merge #14952: rename "state environments" to "workspaces"
This commit is contained in:
commit
698c22c924
|
@ -136,8 +136,9 @@ type Operation struct {
|
||||||
// The duration to retry obtaining a State lock.
|
// The duration to retry obtaining a State lock.
|
||||||
StateLockTimeout time.Duration
|
StateLockTimeout time.Duration
|
||||||
|
|
||||||
// Environment is the named state that should be loaded from the Backend.
|
// Workspace is the name of the workspace that this operation should run
|
||||||
Environment string
|
// in, which controls which named state is used.
|
||||||
|
Workspace string
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunningOperation is the result of starting an operation.
|
// RunningOperation is the result of starting an operation.
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
|
@ -20,8 +19,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DefaultEnvDir = "terraform.tfstate.d"
|
DefaultWorkspaceDir = "terraform.tfstate.d"
|
||||||
DefaultEnvFile = "environment"
|
DefaultWorkspaceFile = "environment"
|
||||||
DefaultStateFilename = "terraform.tfstate"
|
DefaultStateFilename = "terraform.tfstate"
|
||||||
DefaultDataDir = ".terraform"
|
DefaultDataDir = ".terraform"
|
||||||
DefaultBackupExtension = ".backup"
|
DefaultBackupExtension = ".backup"
|
||||||
|
@ -36,8 +35,8 @@ type Local struct {
|
||||||
CLI cli.Ui
|
CLI cli.Ui
|
||||||
CLIColor *colorstring.Colorize
|
CLIColor *colorstring.Colorize
|
||||||
|
|
||||||
// The State* paths are set from the CLI options, and may be left blank to
|
// The State* paths are set from the backend config, and may be left blank
|
||||||
// use the defaults. If the actual paths for the local backend state are
|
// to use the defaults. If the actual paths for the local backend state are
|
||||||
// needed, use the StatePaths method.
|
// needed, use the StatePaths method.
|
||||||
//
|
//
|
||||||
// StatePath is the local path where state is read from.
|
// StatePath is the local path where state is read from.
|
||||||
|
@ -48,12 +47,12 @@ type Local struct {
|
||||||
// StateBackupPath is the local path where a backup file will be written.
|
// StateBackupPath is the local path where a backup file will be written.
|
||||||
// Set this to "-" to disable state backup.
|
// Set this to "-" to disable state backup.
|
||||||
//
|
//
|
||||||
// StateEnvPath is the path to the folder containing environments. This
|
// StateWorkspaceDir is the path to the folder containing data for
|
||||||
// defaults to DefaultEnvDir if not set.
|
// non-default workspaces. This defaults to DefaultWorkspaceDir if not set.
|
||||||
StatePath string
|
StatePath string
|
||||||
StateOutPath string
|
StateOutPath string
|
||||||
StateBackupPath string
|
StateBackupPath string
|
||||||
StateEnvDir string
|
StateWorkspaceDir 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.
|
||||||
|
@ -127,7 +126,7 @@ func (b *Local) States() ([]string, error) {
|
||||||
// the listing always start with "default"
|
// the listing always start with "default"
|
||||||
envs := []string{backend.DefaultStateName}
|
envs := []string{backend.DefaultStateName}
|
||||||
|
|
||||||
entries, err := ioutil.ReadDir(b.stateEnvDir())
|
entries, err := ioutil.ReadDir(b.stateWorkspaceDir())
|
||||||
// no error if there's no envs configured
|
// no error if there's no envs configured
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return envs, nil
|
return envs, nil
|
||||||
|
@ -166,7 +165,7 @@ func (b *Local) DeleteState(name string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(b.states, name)
|
delete(b.states, name)
|
||||||
return os.RemoveAll(filepath.Join(b.stateEnvDir(), name))
|
return os.RemoveAll(filepath.Join(b.stateWorkspaceDir(), name))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Local) State(name string) (state.State, error) {
|
func (b *Local) State(name string) (state.State, error) {
|
||||||
|
@ -292,10 +291,19 @@ func (b *Local) init() {
|
||||||
Default: "",
|
Default: "",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"workspace_dir": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Default: "",
|
||||||
|
},
|
||||||
|
|
||||||
"environment_dir": &schema.Schema{
|
"environment_dir": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: "",
|
Default: "",
|
||||||
|
ConflictsWith: []string{"workspace_dir"},
|
||||||
|
|
||||||
|
Deprecated: "workspace_dir should be used instead, with the same meaning",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -318,10 +326,18 @@ func (b *Local) schemaConfigure(ctx context.Context) error {
|
||||||
b.StateOutPath = path
|
b.StateOutPath = path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if raw, ok := d.GetOk("workspace_dir"); ok {
|
||||||
|
path := raw.(string)
|
||||||
|
if path != "" {
|
||||||
|
b.StateWorkspaceDir = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy name, which ConflictsWith workspace_dir
|
||||||
if raw, ok := d.GetOk("environment_dir"); ok {
|
if raw, ok := d.GetOk("environment_dir"); ok {
|
||||||
path := raw.(string)
|
path := raw.(string)
|
||||||
if path != "" {
|
if path != "" {
|
||||||
b.StateEnvDir = path
|
b.StateWorkspaceDir = path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,7 +360,7 @@ func (b *Local) StatePaths(name string) (string, string, string) {
|
||||||
statePath = DefaultStateFilename
|
statePath = DefaultStateFilename
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
statePath = filepath.Join(b.stateEnvDir(), name, DefaultStateFilename)
|
statePath = filepath.Join(b.stateWorkspaceDir(), name, DefaultStateFilename)
|
||||||
}
|
}
|
||||||
|
|
||||||
if stateOutPath == "" {
|
if stateOutPath == "" {
|
||||||
|
@ -367,7 +383,7 @@ func (b *Local) createState(name string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
stateDir := filepath.Join(b.stateEnvDir(), name)
|
stateDir := filepath.Join(b.stateWorkspaceDir(), name)
|
||||||
s, err := os.Stat(stateDir)
|
s, err := os.Stat(stateDir)
|
||||||
if err == nil && s.IsDir() {
|
if err == nil && s.IsDir() {
|
||||||
// no need to check for os.IsNotExist, since that is covered by os.MkdirAll
|
// no need to check for os.IsNotExist, since that is covered by os.MkdirAll
|
||||||
|
@ -383,30 +399,11 @@ func (b *Local) createState(name string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// stateEnvDir returns the directory where state environments are stored.
|
// stateWorkspaceDir returns the directory where state environments are stored.
|
||||||
func (b *Local) stateEnvDir() string {
|
func (b *Local) stateWorkspaceDir() string {
|
||||||
if b.StateEnvDir != "" {
|
if b.StateWorkspaceDir != "" {
|
||||||
return b.StateEnvDir
|
return b.StateWorkspaceDir
|
||||||
}
|
}
|
||||||
|
|
||||||
return DefaultEnvDir
|
return DefaultWorkspaceDir
|
||||||
}
|
|
||||||
|
|
||||||
// currentStateName returns the name of the current named state as set in the
|
|
||||||
// configuration files.
|
|
||||||
// If there are no configured environments, currentStateName returns "default"
|
|
||||||
func (b *Local) currentStateName() (string, error) {
|
|
||||||
contents, err := ioutil.ReadFile(filepath.Join(DefaultDataDir, DefaultEnvFile))
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return backend.DefaultStateName, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if fromFile := strings.TrimSpace(string(contents)); fromFile != "" {
|
|
||||||
return fromFile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return backend.DefaultStateName, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ func (b *Local) Context(op *backend.Operation) (*terraform.Context, state.State,
|
||||||
|
|
||||||
func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State, error) {
|
func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State, error) {
|
||||||
// Get the state.
|
// Get the state.
|
||||||
s, err := b.State(op.Environment)
|
s, err := b.State(op.Workspace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errwrap.Wrapf("Error loading state: {{err}}", err)
|
return nil, nil, errwrap.Wrapf("Error loading state: {{err}}", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@ func TestLocal_StatePaths(t *testing.T) {
|
||||||
testEnv := "test_env"
|
testEnv := "test_env"
|
||||||
path, out, back = b.StatePaths(testEnv)
|
path, out, back = b.StatePaths(testEnv)
|
||||||
|
|
||||||
expectedPath := filepath.Join(DefaultEnvDir, testEnv, DefaultStateFilename)
|
expectedPath := filepath.Join(DefaultWorkspaceDir, testEnv, DefaultStateFilename)
|
||||||
expectedOut := expectedPath
|
expectedOut := expectedPath
|
||||||
expectedBackup := expectedPath + DefaultBackupExtension
|
expectedBackup := expectedPath + DefaultBackupExtension
|
||||||
|
|
||||||
|
@ -261,7 +261,7 @@ func TestLocal_remoteStateBackup(t *testing.T) {
|
||||||
t.Fatal("remote state is not backed up")
|
t.Fatal("remote state is not backed up")
|
||||||
}
|
}
|
||||||
|
|
||||||
if bs.Path != filepath.Join(DefaultEnvDir, "test", DefaultStateFilename+DefaultBackupExtension) {
|
if bs.Path != filepath.Join(DefaultWorkspaceDir, "test", DefaultStateFilename+DefaultBackupExtension) {
|
||||||
t.Fatal("bad backup location:", bs.Path)
|
t.Fatal("bad backup location:", bs.Path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ func TestLocal(t *testing.T) *Local {
|
||||||
StatePath: filepath.Join(tempDir, "state.tfstate"),
|
StatePath: filepath.Join(tempDir, "state.tfstate"),
|
||||||
StateOutPath: filepath.Join(tempDir, "state.tfstate"),
|
StateOutPath: filepath.Join(tempDir, "state.tfstate"),
|
||||||
StateBackupPath: filepath.Join(tempDir, "state.tfstate.bak"),
|
StateBackupPath: filepath.Join(tempDir, "state.tfstate.bak"),
|
||||||
StateEnvDir: filepath.Join(tempDir, "state.tfstate.d"),
|
StateWorkspaceDir: filepath.Join(tempDir, "state.tfstate.d"),
|
||||||
ContextOpts: &terraform.ContextOpts{},
|
ContextOpts: &terraform.ContextOpts{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,14 @@ func dataSourceRemoteState() *schema.Resource {
|
||||||
"environment": {
|
"environment": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
|
||||||
|
ConflictsWith: []string{"workspace"},
|
||||||
|
Deprecated: "use the \"workspace\" argument instead, with the same value",
|
||||||
|
},
|
||||||
|
|
||||||
|
"workspace": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
Default: backend.DefaultStateName,
|
Default: backend.DefaultStateName,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -80,8 +88,12 @@ func dataSourceRemoteStateRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the state
|
// Get the state
|
||||||
env := d.Get("environment").(string)
|
workspace := d.Get("environment").(string)
|
||||||
state, err := b.State(env)
|
if workspace == "" {
|
||||||
|
// This is actually the main path, since "environment" is deprecated.
|
||||||
|
workspace = d.Get("workspace").(string)
|
||||||
|
}
|
||||||
|
state, err := b.State(workspace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error loading the remote state: %s", err)
|
return fmt.Errorf("error loading the remote state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,38 @@ func TestState_complexOutputs(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestState_workspace(t *testing.T) {
|
||||||
|
resource.UnitTest(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccState_workspace,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckStateValue(
|
||||||
|
"data.terraform_remote_state.foo", "foo", "bar"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestState_legacyEnvironment(t *testing.T) {
|
||||||
|
resource.UnitTest(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccState_legacyEnvironment,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckStateValue(
|
||||||
|
"data.terraform_remote_state.foo", "foo", "bar"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func testAccCheckStateValue(id, name, value string) resource.TestCheckFunc {
|
func testAccCheckStateValue(id, name, value string) resource.TestCheckFunc {
|
||||||
return func(s *terraform.State) error {
|
return func(s *terraform.State) error {
|
||||||
rs, ok := s.RootModule().Resources[id]
|
rs, ok := s.RootModule().Resources[id]
|
||||||
|
@ -109,3 +141,23 @@ resource "terraform_remote_state" "foo" {
|
||||||
path = "./test-fixtures/complex_outputs.tfstate"
|
path = "./test-fixtures/complex_outputs.tfstate"
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
|
const testAccState_workspace = `
|
||||||
|
data "terraform_remote_state" "foo" {
|
||||||
|
backend = "local"
|
||||||
|
workspace = "test"
|
||||||
|
|
||||||
|
config {
|
||||||
|
workspace_dir = "./test-fixtures/workspaces"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
const testAccState_legacyEnvironment = `
|
||||||
|
data "terraform_remote_state" "foo" {
|
||||||
|
backend = "local"
|
||||||
|
environment = "test" # old, deprecated name for "workspace"
|
||||||
|
|
||||||
|
config {
|
||||||
|
workspace_dir = "./test-fixtures/workspaces"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"modules": [{
|
||||||
|
"path": ["root"],
|
||||||
|
"outputs": { "foo": "bar" }
|
||||||
|
}]
|
||||||
|
}
|
|
@ -1639,7 +1639,7 @@ func TestApply_terraformEnvNonDefault(t *testing.T) {
|
||||||
// Create new env
|
// Create new env
|
||||||
{
|
{
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
newCmd := &EnvNewCommand{}
|
newCmd := &WorkspaceNewCommand{}
|
||||||
newCmd.Meta = Meta{Ui: ui}
|
newCmd.Meta = Meta{Ui: ui}
|
||||||
if code := newCmd.Run([]string{"test"}); code != 0 {
|
if code := newCmd.Run([]string{"test"}); code != 0 {
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
||||||
|
@ -1650,7 +1650,7 @@ func TestApply_terraformEnvNonDefault(t *testing.T) {
|
||||||
{
|
{
|
||||||
args := []string{"test"}
|
args := []string{"test"}
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
selCmd := &EnvSelectCommand{}
|
selCmd := &WorkspaceSelectCommand{}
|
||||||
selCmd.Meta = Meta{Ui: ui}
|
selCmd.Meta = Meta{Ui: ui}
|
||||||
if code := selCmd.Run(args); code != 0 {
|
if code := selCmd.Run(args); code != 0 {
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
package command
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EnvCommand is a Command Implementation that manipulates local state
|
|
||||||
// environments.
|
|
||||||
type EnvCommand struct {
|
|
||||||
Meta
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *EnvCommand) Run(args []string) int {
|
|
||||||
args = c.Meta.process(args, true)
|
|
||||||
|
|
||||||
cmdFlags := c.Meta.flagSet("env")
|
|
||||||
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
|
||||||
|
|
||||||
c.Ui.Output(c.Help())
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *EnvCommand) Help() string {
|
|
||||||
helpText := `
|
|
||||||
Usage: terraform env
|
|
||||||
|
|
||||||
Create, change and delete Terraform environments.
|
|
||||||
|
|
||||||
|
|
||||||
Subcommands:
|
|
||||||
|
|
||||||
list List environments.
|
|
||||||
select Select an environment.
|
|
||||||
new Create a new environment.
|
|
||||||
delete Delete an existing environment.
|
|
||||||
`
|
|
||||||
return strings.TrimSpace(helpText)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *EnvCommand) Synopsis() string {
|
|
||||||
return "Environment management"
|
|
||||||
}
|
|
||||||
|
|
||||||
// validEnvName returns true is this name is valid to use as an environment name.
|
|
||||||
// Since most named states are accessed via a filesystem path or URL, check if
|
|
||||||
// escaping the name would be required.
|
|
||||||
func validEnvName(name string) bool {
|
|
||||||
return name == url.PathEscape(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
envNotSupported = `Backend does not support environments`
|
|
||||||
|
|
||||||
envExists = `Environment %q already exists`
|
|
||||||
|
|
||||||
envDoesNotExist = `
|
|
||||||
Environment %q doesn't exist!
|
|
||||||
|
|
||||||
You can create this environment with the "new" option.`
|
|
||||||
|
|
||||||
envChanged = `[reset][green]Switched to environment %q!`
|
|
||||||
|
|
||||||
envCreated = `
|
|
||||||
[reset][green][bold]Created and switched to environment %q![reset][green]
|
|
||||||
|
|
||||||
You're now on a new, empty environment. Environments isolate their state,
|
|
||||||
so if you run "terraform plan" Terraform will not see any existing state
|
|
||||||
for this configuration.
|
|
||||||
`
|
|
||||||
|
|
||||||
envDeleted = `[reset][green]Deleted environment %q!`
|
|
||||||
|
|
||||||
envNotEmpty = `
|
|
||||||
Environment %[1]q is not empty!
|
|
||||||
|
|
||||||
Deleting %[1]q can result in dangling resources: resources that
|
|
||||||
exist but are no longer manageable by Terraform. Please destroy
|
|
||||||
these resources first. If you want to delete this environment
|
|
||||||
anyways and risk dangling resources, use the '-force' flag.
|
|
||||||
`
|
|
||||||
|
|
||||||
envWarnNotEmpty = `[reset][yellow]WARNING: %q was non-empty.
|
|
||||||
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.
|
|
||||||
`
|
|
||||||
|
|
||||||
envInvalidName = `
|
|
||||||
The environment name %q is not allowed. The name must contain only URL safe
|
|
||||||
characters, and no path separators.
|
|
||||||
`
|
|
||||||
)
|
|
|
@ -177,7 +177,7 @@ func (c *InitCommand) Run(args []string) int {
|
||||||
|
|
||||||
// Now that we have loaded all modules, check the module tree for missing providers
|
// Now that we have loaded all modules, check the module tree for missing providers
|
||||||
if flagGetPlugins {
|
if flagGetPlugins {
|
||||||
sMgr, err := back.State(c.Env())
|
sMgr, err := back.State(c.Workspace())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf(
|
c.Ui.Error(fmt.Sprintf(
|
||||||
"Error loading state: %s", err))
|
"Error loading state: %s", err))
|
||||||
|
|
|
@ -247,7 +247,7 @@ func (m *Meta) contextOpts() *terraform.ContextOpts {
|
||||||
opts.ProviderSHA256s = m.providerPluginsLock().Read()
|
opts.ProviderSHA256s = m.providerPluginsLock().Read()
|
||||||
|
|
||||||
opts.Meta = &terraform.ContextMeta{
|
opts.Meta = &terraform.ContextMeta{
|
||||||
Env: m.Env(),
|
Env: m.Workspace(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return &opts
|
return &opts
|
||||||
|
@ -454,30 +454,51 @@ func (m *Meta) outputShadowError(err error, output bool) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Env returns the name of the currently configured environment, corresponding
|
// WorkspaceNameEnvVar is the name of the environment variable that can be used
|
||||||
|
// to set the name of the Terraform workspace, overriding the workspace chosen
|
||||||
|
// by `terraform workspace select`.
|
||||||
|
//
|
||||||
|
// Note that this environment variable is ignored by `terraform workspace new`
|
||||||
|
// and `terraform workspace delete`.
|
||||||
|
const WorkspaceNameEnvVar = "TF_WORKSPACE"
|
||||||
|
|
||||||
|
// Workspace returns the name of the currently configured workspace, corresponding
|
||||||
// to the desired named state.
|
// to the desired named state.
|
||||||
func (m *Meta) Env() string {
|
func (m *Meta) Workspace() string {
|
||||||
|
current, _ := m.WorkspaceOverridden()
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
// WorkspaceOverridden returns the name of the currently configured workspace,
|
||||||
|
// corresponding to the desired named state, as well as a bool saying whether
|
||||||
|
// this was set via the TF_WORKSPACE environment variable.
|
||||||
|
func (m *Meta) WorkspaceOverridden() (string, bool) {
|
||||||
|
if envVar := os.Getenv(WorkspaceNameEnvVar); envVar != "" {
|
||||||
|
return envVar, true
|
||||||
|
}
|
||||||
|
|
||||||
dataDir := m.dataDir
|
dataDir := m.dataDir
|
||||||
if m.dataDir == "" {
|
if m.dataDir == "" {
|
||||||
dataDir = DefaultDataDir
|
dataDir = DefaultDataDir
|
||||||
}
|
}
|
||||||
|
|
||||||
envData, err := ioutil.ReadFile(filepath.Join(dataDir, local.DefaultEnvFile))
|
envData, err := ioutil.ReadFile(filepath.Join(dataDir, local.DefaultWorkspaceFile))
|
||||||
current := string(bytes.TrimSpace(envData))
|
current := string(bytes.TrimSpace(envData))
|
||||||
if current == "" {
|
if current == "" {
|
||||||
current = backend.DefaultStateName
|
current = backend.DefaultStateName
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
// always return the default if we can't get an environment name
|
// always return the default if we can't get a workspace name
|
||||||
log.Printf("[ERROR] failed to read current environment: %s", err)
|
log.Printf("[ERROR] failed to read current workspace: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return current
|
return current, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetEnv saves the named environment to the local filesystem.
|
// SetWorkspace saves the given name as the current workspace in the local
|
||||||
func (m *Meta) SetEnv(name string) error {
|
// filesystem.
|
||||||
|
func (m *Meta) SetWorkspace(name string) error {
|
||||||
dataDir := m.dataDir
|
dataDir := m.dataDir
|
||||||
if m.dataDir == "" {
|
if m.dataDir == "" {
|
||||||
dataDir = DefaultDataDir
|
dataDir = DefaultDataDir
|
||||||
|
@ -488,7 +509,7 @@ func (m *Meta) SetEnv(name string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ioutil.WriteFile(filepath.Join(dataDir, local.DefaultEnvFile), []byte(name), 0644)
|
err = ioutil.WriteFile(filepath.Join(dataDir, local.DefaultWorkspaceFile), []byte(name), 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,7 +168,7 @@ func (m *Meta) Operation() *backend.Operation {
|
||||||
PlanOutBackend: m.backendState,
|
PlanOutBackend: m.backendState,
|
||||||
Targets: m.targets,
|
Targets: m.targets,
|
||||||
UIIn: m.UIInput(),
|
UIIn: m.UIInput(),
|
||||||
Environment: m.Env(),
|
Workspace: m.Workspace(),
|
||||||
LockState: m.stateLock,
|
LockState: m.stateLock,
|
||||||
StateLockTimeout: m.stateLockTimeout,
|
StateLockTimeout: m.stateLockTimeout,
|
||||||
}
|
}
|
||||||
|
@ -572,7 +572,7 @@ func (m *Meta) backendFromPlan(opts *BackendOpts) (backend.Backend, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
env := m.Env()
|
env := m.Workspace()
|
||||||
|
|
||||||
// Get the state so we can determine the effect of using this plan
|
// Get the state so we can determine the effect of using this plan
|
||||||
realMgr, err := b.State(env)
|
realMgr, err := b.State(env)
|
||||||
|
@ -967,7 +967,7 @@ func (m *Meta) backend_C_r_s(
|
||||||
return nil, fmt.Errorf(errBackendLocalRead, err)
|
return nil, fmt.Errorf(errBackendLocalRead, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
env := m.Env()
|
env := m.Workspace()
|
||||||
|
|
||||||
localState, err := localB.State(env)
|
localState, err := localB.State(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1341,14 +1341,18 @@ func (m *Meta) backendInitFromConfig(c *config.Backend) (backend.Backend, error)
|
||||||
|
|
||||||
// Validate
|
// Validate
|
||||||
warns, errs := b.Validate(config)
|
warns, errs := b.Validate(config)
|
||||||
|
for _, warning := range warns {
|
||||||
|
// We just write warnings directly to the UI. This isn't great
|
||||||
|
// since we're a bit deep here to be pushing stuff out into the
|
||||||
|
// UI, but sufficient to let us print out deprecation warnings
|
||||||
|
// and the like.
|
||||||
|
m.Ui.Warn(warning)
|
||||||
|
}
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"Error configuring the backend %q: %s",
|
"Error configuring the backend %q: %s",
|
||||||
c.Type, multierror.Append(nil, errs...))
|
c.Type, multierror.Append(nil, errs...))
|
||||||
}
|
}
|
||||||
if len(warns) > 0 {
|
|
||||||
// TODO: warnings are currently ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure
|
// Configure
|
||||||
if err := b.Configure(config); err != nil {
|
if err := b.Configure(config); err != nil {
|
||||||
|
|
|
@ -117,7 +117,7 @@ func (m *Meta) backendMigrateState_S_S(opts *backendMigrateOpts) error {
|
||||||
migrate, err := m.confirm(&terraform.InputOpts{
|
migrate, err := m.confirm(&terraform.InputOpts{
|
||||||
Id: "backend-migrate-multistate-to-multistate",
|
Id: "backend-migrate-multistate-to-multistate",
|
||||||
Query: fmt.Sprintf(
|
Query: fmt.Sprintf(
|
||||||
"Do you want to migrate all environments to %q?",
|
"Do you want to migrate all workspaces to %q?",
|
||||||
opts.TwoType),
|
opts.TwoType),
|
||||||
Description: fmt.Sprintf(
|
Description: fmt.Sprintf(
|
||||||
strings.TrimSpace(inputBackendMigrateMultiToMulti),
|
strings.TrimSpace(inputBackendMigrateMultiToMulti),
|
||||||
|
@ -162,7 +162,7 @@ func (m *Meta) backendMigrateState_S_S(opts *backendMigrateOpts) error {
|
||||||
|
|
||||||
// Multi-state to single state.
|
// Multi-state to single state.
|
||||||
func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error {
|
func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error {
|
||||||
currentEnv := m.Env()
|
currentEnv := m.Workspace()
|
||||||
|
|
||||||
migrate := opts.force
|
migrate := opts.force
|
||||||
if !migrate {
|
if !migrate {
|
||||||
|
@ -171,8 +171,8 @@ func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error {
|
||||||
migrate, err = m.confirm(&terraform.InputOpts{
|
migrate, err = m.confirm(&terraform.InputOpts{
|
||||||
Id: "backend-migrate-multistate-to-single",
|
Id: "backend-migrate-multistate-to-single",
|
||||||
Query: fmt.Sprintf(
|
Query: fmt.Sprintf(
|
||||||
"Destination state %q doesn't support environments (named states).\n"+
|
"Destination state %q doesn't support workspaces.\n"+
|
||||||
"Do you want to copy only your current environment?",
|
"Do you want to copy only your current workspace?",
|
||||||
opts.TwoType),
|
opts.TwoType),
|
||||||
Description: fmt.Sprintf(
|
Description: fmt.Sprintf(
|
||||||
strings.TrimSpace(inputBackendMigrateMultiToSingle),
|
strings.TrimSpace(inputBackendMigrateMultiToSingle),
|
||||||
|
@ -192,7 +192,7 @@ func (m *Meta) backendMigrateState_S_s(opts *backendMigrateOpts) error {
|
||||||
opts.oneEnv = currentEnv
|
opts.oneEnv = currentEnv
|
||||||
|
|
||||||
// now switch back to the default env so we can acccess the new backend
|
// now switch back to the default env so we can acccess the new backend
|
||||||
m.SetEnv(backend.DefaultStateName)
|
m.SetWorkspace(backend.DefaultStateName)
|
||||||
|
|
||||||
return m.backendMigrateState_s_s(opts)
|
return m.backendMigrateState_s_s(opts)
|
||||||
}
|
}
|
||||||
|
@ -458,17 +458,17 @@ above error and try again.
|
||||||
`
|
`
|
||||||
|
|
||||||
const errMigrateMulti = `
|
const errMigrateMulti = `
|
||||||
Error migrating the environment %q from %q to %q:
|
Error migrating the workspace %q from %q to %q:
|
||||||
|
|
||||||
%s
|
%s
|
||||||
|
|
||||||
Terraform copies environments in alphabetical order. Any environments
|
Terraform copies workspaces in alphabetical order. Any workspaces
|
||||||
alphabetically earlier than this one have been copied. Any environments
|
alphabetically earlier than this one have been copied. Any workspaces
|
||||||
later than this haven't been modified in the destination. No environments
|
later than this haven't been modified in the destination. No workspaces
|
||||||
in the source state have been modified.
|
in the source state have been modified.
|
||||||
|
|
||||||
Please resolve the error above and run the initialization command again.
|
Please resolve the error above and run the initialization command again.
|
||||||
This will attempt to copy (with permission) all environments again.
|
This will attempt to copy (with permission) all workspaces again.
|
||||||
`
|
`
|
||||||
|
|
||||||
const errBackendStateCopy = `
|
const errBackendStateCopy = `
|
||||||
|
@ -497,22 +497,22 @@ and "no" to start with the existing state in %[2]q.
|
||||||
`
|
`
|
||||||
|
|
||||||
const inputBackendMigrateMultiToSingle = `
|
const inputBackendMigrateMultiToSingle = `
|
||||||
The existing backend %[1]q supports environments and you currently are
|
The existing backend %[1]q supports workspaces and you currently are
|
||||||
using more than one. The target backend %[2]q doesn't support environments.
|
using more than one. The target backend %[2]q doesn't support workspaces.
|
||||||
If you continue, Terraform will offer to copy your current environment
|
If you continue, Terraform will offer to copy your current workspace
|
||||||
%[3]q to the default environment in the target. Your existing environments
|
%[3]q to the default workspace in the target. Your existing workspaces
|
||||||
in the source backend won't be modified. If you want to switch environments,
|
in the source backend won't be modified. If you want to switch workspaces,
|
||||||
back them up, or cancel altogether, answer "no" and Terraform will abort.
|
back them up, or cancel altogether, answer "no" and Terraform will abort.
|
||||||
`
|
`
|
||||||
|
|
||||||
const inputBackendMigrateMultiToMulti = `
|
const inputBackendMigrateMultiToMulti = `
|
||||||
Both the existing backend %[1]q and the target backend %[2]q support
|
Both the existing backend %[1]q and the target backend %[2]q support
|
||||||
environments. When migrating between backends, Terraform will copy all
|
workspaces. When migrating between backends, Terraform will copy all
|
||||||
environments (with the same names). THIS WILL OVERWRITE any conflicting
|
workspaces (with the same names). THIS WILL OVERWRITE any conflicting
|
||||||
states in the destination.
|
states in the destination.
|
||||||
|
|
||||||
Terraform initialization doesn't currently migrate only select environments.
|
Terraform initialization doesn't currently migrate only select workspaces.
|
||||||
If you want to migrate a select number of environments, you must manually
|
If you want to migrate a select number of workspaces, you must manually
|
||||||
pull and push those states.
|
pull and push those states.
|
||||||
|
|
||||||
If you answer "yes", Terraform will migrate all states. If you answer
|
If you answer "yes", Terraform will migrate all states. If you answer
|
||||||
|
|
|
@ -1249,14 +1249,14 @@ func TestMetaBackend_configuredChangeCopy_multiToSingle(t *testing.T) {
|
||||||
t.Fatal("file should not exist")
|
t.Fatal("file should not exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify existing environments exist
|
// Verify existing workspaces exist
|
||||||
envPath := filepath.Join(backendlocal.DefaultEnvDir, "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.Fatal("env should exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify we are now in the default env, or we may not be able to access the new backend
|
// Verify we are now in the default env, or we may not be able to access the new backend
|
||||||
if env := m.Env(); env != backend.DefaultStateName {
|
if env := m.Workspace(); env != backend.DefaultStateName {
|
||||||
t.Fatal("using non-default env with single-env backend")
|
t.Fatal("using non-default env with single-env backend")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1285,7 +1285,7 @@ func TestMetaBackend_configuredChangeCopy_multiToSingleCurrentEnv(t *testing.T)
|
||||||
m := testMetaBackend(t, nil)
|
m := testMetaBackend(t, nil)
|
||||||
|
|
||||||
// Change env
|
// Change env
|
||||||
if err := m.SetEnv("env2"); err != nil {
|
if err := m.SetWorkspace("env2"); err != nil {
|
||||||
t.Fatalf("bad: %s", err)
|
t.Fatalf("bad: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1321,8 +1321,8 @@ func TestMetaBackend_configuredChangeCopy_multiToSingleCurrentEnv(t *testing.T)
|
||||||
t.Fatal("file should not exist")
|
t.Fatal("file should not exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify existing environments exist
|
// Verify existing workspaces exist
|
||||||
envPath := filepath.Join(backendlocal.DefaultEnvDir, "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.Fatal("env should exist")
|
||||||
}
|
}
|
||||||
|
@ -1406,15 +1406,15 @@ func TestMetaBackend_configuredChangeCopy_multiToMulti(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
// Verify existing environments exist
|
// Verify existing workspaces exist
|
||||||
envPath := filepath.Join(backendlocal.DefaultEnvDir, "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.Fatal("env should exist")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
// Verify new environments 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.Fatal("env should exist")
|
||||||
|
|
|
@ -282,27 +282,27 @@ func TestMeta_Env(t *testing.T) {
|
||||||
|
|
||||||
m := new(Meta)
|
m := new(Meta)
|
||||||
|
|
||||||
env := m.Env()
|
env := m.Workspace()
|
||||||
|
|
||||||
if env != backend.DefaultStateName {
|
if env != backend.DefaultStateName {
|
||||||
t.Fatalf("expected env %q, got env %q", backend.DefaultStateName, env)
|
t.Fatalf("expected env %q, got env %q", backend.DefaultStateName, env)
|
||||||
}
|
}
|
||||||
|
|
||||||
testEnv := "test_env"
|
testEnv := "test_env"
|
||||||
if err := m.SetEnv(testEnv); err != nil {
|
if err := m.SetWorkspace(testEnv); err != nil {
|
||||||
t.Fatal("error setting env:", err)
|
t.Fatal("error setting env:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
env = m.Env()
|
env = m.Workspace()
|
||||||
if env != testEnv {
|
if env != testEnv {
|
||||||
t.Fatalf("expected env %q, got env %q", testEnv, env)
|
t.Fatalf("expected env %q, got env %q", testEnv, env)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.SetEnv(backend.DefaultStateName); err != nil {
|
if err := m.SetWorkspace(backend.DefaultStateName); err != nil {
|
||||||
t.Fatal("error setting env:", err)
|
t.Fatal("error setting env:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
env = m.Env()
|
env = m.Workspace()
|
||||||
if env != backend.DefaultStateName {
|
if env != backend.DefaultStateName {
|
||||||
t.Fatalf("expected env %q, got env %q", backend.DefaultStateName, env)
|
t.Fatalf("expected env %q, got env %q", backend.DefaultStateName, env)
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ func (c *OutputCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
env := c.Env()
|
env := c.Workspace()
|
||||||
|
|
||||||
// Get the state
|
// Get the state
|
||||||
stateStore, err := b.State(env)
|
stateStore, err := b.State(env)
|
||||||
|
|
|
@ -61,7 +61,7 @@ func (c *ProvidersCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the state
|
// Get the state
|
||||||
env := c.Env()
|
env := c.Workspace()
|
||||||
state, err := b.State(env)
|
state, err := b.State(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
|
|
|
@ -51,8 +51,8 @@ func (c *PushCommand) Run(args []string) int {
|
||||||
|
|
||||||
// This is a map of variables specifically from the CLI that we want to overwrite.
|
// This is a map of variables specifically from the CLI that we want to overwrite.
|
||||||
// We need this because there is a chance that the user is trying to modify
|
// We need this because there is a chance that the user is trying to modify
|
||||||
// a variable we don't see in our context, but which exists in this atlas
|
// a variable we don't see in our context, but which exists in this Terraform
|
||||||
// environment.
|
// Enterprise workspace.
|
||||||
cliVars := make(map[string]string)
|
cliVars := make(map[string]string)
|
||||||
for k, v := range c.variables {
|
for k, v := range c.variables {
|
||||||
if _, ok := overwriteMap[k]; ok {
|
if _, ok := overwriteMap[k]; ok {
|
||||||
|
|
|
@ -74,7 +74,7 @@ func (c *ShowCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
env := c.Env()
|
env := c.Workspace()
|
||||||
|
|
||||||
// Get the state
|
// Get the state
|
||||||
stateStore, err := b.State(env)
|
stateStore, err := b.State(env)
|
||||||
|
|
|
@ -32,7 +32,7 @@ func (c *StateListCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
env := c.Env()
|
env := c.Workspace()
|
||||||
// Get the state
|
// Get the state
|
||||||
state, err := b.State(env)
|
state, err := b.State(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -24,7 +24,7 @@ func (c *StateMeta) State(m *Meta) (state.State, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
env := m.Env()
|
env := m.Workspace()
|
||||||
// Get the state
|
// Get the state
|
||||||
s, err := b.State(env)
|
s, err := b.State(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -32,7 +32,7 @@ func (c *StatePullCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the state
|
// Get the state
|
||||||
env := c.Env()
|
env := c.Workspace()
|
||||||
state, err := b.State(env)
|
state, err := b.State(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
|
|
|
@ -67,7 +67,7 @@ func (c *StatePushCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the state
|
// Get the state
|
||||||
env := c.Env()
|
env := c.Workspace()
|
||||||
state, err := b.State(env)
|
state, err := b.State(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
|
||||||
|
|
|
@ -34,7 +34,7 @@ func (c *StateShowCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the state
|
// Get the state
|
||||||
env := c.Env()
|
env := c.Workspace()
|
||||||
state, err := b.State(env)
|
state, err := b.State(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
|
|
|
@ -69,7 +69,7 @@ func (c *TaintCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the state
|
// Get the state
|
||||||
env := c.Env()
|
env := c.Workspace()
|
||||||
st, err := b.State(env)
|
st, err := b.State(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
|
|
|
@ -58,7 +58,7 @@ func (c *UnlockCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
env := c.Env()
|
env := c.Workspace()
|
||||||
st, err := b.State(env)
|
st, err := b.State(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
|
|
|
@ -57,7 +57,7 @@ func (c *UntaintCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the state
|
// Get the state
|
||||||
env := c.Env()
|
env := c.Workspace()
|
||||||
st, err := b.State(env)
|
st, err := b.State(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mitchellh/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WorkspaceCommand is a Command Implementation that manipulates workspaces,
|
||||||
|
// which allow multiple distinct states and variables from a single config.
|
||||||
|
type WorkspaceCommand struct {
|
||||||
|
Meta
|
||||||
|
LegacyName bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WorkspaceCommand) Run(args []string) int {
|
||||||
|
args = c.Meta.process(args, true)
|
||||||
|
|
||||||
|
envCommandShowWarning(c.Ui, c.LegacyName)
|
||||||
|
|
||||||
|
cmdFlags := c.Meta.flagSet("workspace")
|
||||||
|
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||||
|
|
||||||
|
c.Ui.Output(c.Help())
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WorkspaceCommand) Help() string {
|
||||||
|
helpText := `
|
||||||
|
Usage: terraform workspace
|
||||||
|
|
||||||
|
Create, change and delete Terraform workspaces.
|
||||||
|
|
||||||
|
|
||||||
|
Subcommands:
|
||||||
|
|
||||||
|
list List workspaces.
|
||||||
|
select Select a workspace.
|
||||||
|
new Create a new workspace.
|
||||||
|
delete Delete an existing workspace.
|
||||||
|
`
|
||||||
|
return strings.TrimSpace(helpText)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WorkspaceCommand) Synopsis() string {
|
||||||
|
return "Workspace management"
|
||||||
|
}
|
||||||
|
|
||||||
|
// validWorkspaceName returns true is this name is valid to use as a workspace name.
|
||||||
|
// Since most named states are accessed via a filesystem path or URL, check if
|
||||||
|
// escaping the name would be required.
|
||||||
|
func validWorkspaceName(name string) bool {
|
||||||
|
return name == url.PathEscape(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func envCommandShowWarning(ui cli.Ui, show bool) {
|
||||||
|
if !show {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Warn(`Warning: the "terraform env" family of commands is deprecated.
|
||||||
|
|
||||||
|
"Workspace" is now the preferred term for what earlier Terraform versions
|
||||||
|
called "environment", to reduce ambiguity caused by the latter term colliding
|
||||||
|
with other concepts.
|
||||||
|
|
||||||
|
The "terraform workspace" commands should be used instead. "terraform env"
|
||||||
|
will be removed in a future Terraform version.
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
envNotSupported = `Backend does not support multiple workspaces`
|
||||||
|
|
||||||
|
envExists = `Workspace %q already exists`
|
||||||
|
|
||||||
|
envDoesNotExist = `
|
||||||
|
Workspace %q doesn't exist.
|
||||||
|
|
||||||
|
You can create this workspace with the "new" subcommand.`
|
||||||
|
|
||||||
|
envChanged = `[reset][green]Switched to workspace %q.`
|
||||||
|
|
||||||
|
envCreated = `
|
||||||
|
[reset][green][bold]Created and switched to workspace %q![reset][green]
|
||||||
|
|
||||||
|
You're now on a new, empty workspace. Workspaces isolate their state,
|
||||||
|
so if you run "terraform plan" Terraform will not see any existing state
|
||||||
|
for this configuration.
|
||||||
|
`
|
||||||
|
|
||||||
|
envDeleted = `[reset][green]Deleted workspace %q!`
|
||||||
|
|
||||||
|
envNotEmpty = `
|
||||||
|
Workspace %[1]q is not empty.
|
||||||
|
|
||||||
|
Deleting %[1]q can result in dangling resources: resources that
|
||||||
|
exist but are no longer manageable by Terraform. Please destroy
|
||||||
|
these resources first. If you want to delete this workspace
|
||||||
|
anyway and risk dangling resources, use the '-force' flag.
|
||||||
|
`
|
||||||
|
|
||||||
|
envWarnNotEmpty = `[reset][yellow]WARNING: %q was non-empty.
|
||||||
|
The resources managed by the deleted workspace may still exist,
|
||||||
|
but are no longer manageable by Terraform since the state has
|
||||||
|
been deleted.
|
||||||
|
`
|
||||||
|
|
||||||
|
envDelCurrent = `
|
||||||
|
Workspace %[1]q is your active workspace.
|
||||||
|
|
||||||
|
You cannot delete the currently active workspace. Please switch
|
||||||
|
to another workspace and try again.
|
||||||
|
`
|
||||||
|
|
||||||
|
envInvalidName = `
|
||||||
|
The workspace name %q is not allowed. The name must contain only URL safe
|
||||||
|
characters, and no path separators.
|
||||||
|
`
|
||||||
|
|
||||||
|
envIsOverriddenNote = `
|
||||||
|
|
||||||
|
The active workspace is being overridden using the TF_WORKSPACE environment
|
||||||
|
variable.
|
||||||
|
`
|
||||||
|
|
||||||
|
envIsOverriddenSelectError = `
|
||||||
|
The selected workspace is currently overridden using the TF_WORKSPACE
|
||||||
|
environment variable.
|
||||||
|
|
||||||
|
To select a new workspace, either update this environment variable or unset
|
||||||
|
it and then run this command again.
|
||||||
|
`
|
||||||
|
|
||||||
|
envIsOverriddenNewError = `
|
||||||
|
The workspace is currently overridden using the TF_WORKSPACE environment
|
||||||
|
variable. You cannot create a new workspace when using this setting.
|
||||||
|
|
||||||
|
To create a new workspace, either unset this environment variable or update it
|
||||||
|
to match the workspace name you are trying to create, and then run this command
|
||||||
|
again.
|
||||||
|
`
|
||||||
|
)
|
|
@ -14,18 +14,18 @@ import (
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEnv_createAndChange(t *testing.T) {
|
func TestWorkspace_createAndChange(t *testing.T) {
|
||||||
// Create a temporary working directory that is empty
|
// Create a temporary working directory that is empty
|
||||||
td := tempDir(t)
|
td := tempDir(t)
|
||||||
os.MkdirAll(td, 0755)
|
os.MkdirAll(td, 0755)
|
||||||
defer os.RemoveAll(td)
|
defer os.RemoveAll(td)
|
||||||
defer testChdir(t, td)()
|
defer testChdir(t, td)()
|
||||||
|
|
||||||
newCmd := &EnvNewCommand{}
|
newCmd := &WorkspaceNewCommand{}
|
||||||
|
|
||||||
current := newCmd.Env()
|
current := newCmd.Workspace()
|
||||||
if current != backend.DefaultStateName {
|
if current != backend.DefaultStateName {
|
||||||
t.Fatal("current env should be 'default'")
|
t.Fatal("current workspace should be 'default'")
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{"test"}
|
args := []string{"test"}
|
||||||
|
@ -35,12 +35,12 @@ func TestEnv_createAndChange(t *testing.T) {
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
current = newCmd.Env()
|
current = newCmd.Workspace()
|
||||||
if current != "test" {
|
if current != "test" {
|
||||||
t.Fatalf("current env should be 'test', got %q", current)
|
t.Fatalf("current workspace should be 'test', got %q", current)
|
||||||
}
|
}
|
||||||
|
|
||||||
selCmd := &EnvSelectCommand{}
|
selCmd := &WorkspaceSelectCommand{}
|
||||||
args = []string{backend.DefaultStateName}
|
args = []string{backend.DefaultStateName}
|
||||||
ui = new(cli.MockUi)
|
ui = new(cli.MockUi)
|
||||||
selCmd.Meta = Meta{Ui: ui}
|
selCmd.Meta = Meta{Ui: ui}
|
||||||
|
@ -48,16 +48,16 @@ func TestEnv_createAndChange(t *testing.T) {
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
current = newCmd.Env()
|
current = newCmd.Workspace()
|
||||||
if current != backend.DefaultStateName {
|
if current != backend.DefaultStateName {
|
||||||
t.Fatal("current env should be 'default'")
|
t.Fatal("current workspace should be 'default'")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create some environments and test the list output.
|
// Create some workspaces and test the list output.
|
||||||
// This also ensures we switch to the correct env after each call
|
// This also ensures we switch to the correct env after each call
|
||||||
func TestEnv_createAndList(t *testing.T) {
|
func TestWorkspace_createAndList(t *testing.T) {
|
||||||
// Create a temporary working directory that is empty
|
// Create a temporary working directory that is empty
|
||||||
td := tempDir(t)
|
td := tempDir(t)
|
||||||
os.MkdirAll(td, 0755)
|
os.MkdirAll(td, 0755)
|
||||||
|
@ -74,11 +74,11 @@ func TestEnv_createAndList(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
newCmd := &EnvNewCommand{}
|
newCmd := &WorkspaceNewCommand{}
|
||||||
|
|
||||||
envs := []string{"test_a", "test_b", "test_c"}
|
envs := []string{"test_a", "test_b", "test_c"}
|
||||||
|
|
||||||
// create multiple envs
|
// create multiple workspaces
|
||||||
for _, env := range envs {
|
for _, env := range envs {
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
newCmd.Meta = Meta{Ui: ui}
|
newCmd.Meta = Meta{Ui: ui}
|
||||||
|
@ -87,7 +87,7 @@ func TestEnv_createAndList(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
listCmd := &EnvListCommand{}
|
listCmd := &WorkspaceListCommand{}
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
listCmd.Meta = Meta{Ui: ui}
|
listCmd.Meta = Meta{Ui: ui}
|
||||||
|
|
||||||
|
@ -104,18 +104,18 @@ func TestEnv_createAndList(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't allow names that aren't URL safe
|
// Don't allow names that aren't URL safe
|
||||||
func TestEnv_createInvalid(t *testing.T) {
|
func TestWorkspace_createInvalid(t *testing.T) {
|
||||||
// Create a temporary working directory that is empty
|
// Create a temporary working directory that is empty
|
||||||
td := tempDir(t)
|
td := tempDir(t)
|
||||||
os.MkdirAll(td, 0755)
|
os.MkdirAll(td, 0755)
|
||||||
defer os.RemoveAll(td)
|
defer os.RemoveAll(td)
|
||||||
defer testChdir(t, td)()
|
defer testChdir(t, td)()
|
||||||
|
|
||||||
newCmd := &EnvNewCommand{}
|
newCmd := &WorkspaceNewCommand{}
|
||||||
|
|
||||||
envs := []string{"test_a*", "test_b/foo", "../../../test_c", "好_d"}
|
envs := []string{"test_a*", "test_b/foo", "../../../test_c", "好_d"}
|
||||||
|
|
||||||
// create multiple envs
|
// create multiple workspaces
|
||||||
for _, env := range envs {
|
for _, env := range envs {
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
newCmd.Meta = Meta{Ui: ui}
|
newCmd.Meta = Meta{Ui: ui}
|
||||||
|
@ -124,8 +124,8 @@ func TestEnv_createInvalid(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// list envs to make sure none were created
|
// list workspaces to make sure none were created
|
||||||
listCmd := &EnvListCommand{}
|
listCmd := &WorkspaceListCommand{}
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
listCmd.Meta = Meta{Ui: ui}
|
listCmd.Meta = Meta{Ui: ui}
|
||||||
|
|
||||||
|
@ -141,7 +141,7 @@ func TestEnv_createInvalid(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnv_createWithState(t *testing.T) {
|
func TestWorkspace_createWithState(t *testing.T) {
|
||||||
td := tempDir(t)
|
td := tempDir(t)
|
||||||
os.MkdirAll(td, 0755)
|
os.MkdirAll(td, 0755)
|
||||||
defer os.RemoveAll(td)
|
defer os.RemoveAll(td)
|
||||||
|
@ -171,14 +171,14 @@ func TestEnv_createWithState(t *testing.T) {
|
||||||
|
|
||||||
args := []string{"-state", "test.tfstate", "test"}
|
args := []string{"-state", "test.tfstate", "test"}
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
newCmd := &EnvNewCommand{
|
newCmd := &WorkspaceNewCommand{
|
||||||
Meta: Meta{Ui: ui},
|
Meta: Meta{Ui: ui},
|
||||||
}
|
}
|
||||||
if code := newCmd.Run(args); code != 0 {
|
if code := newCmd.Run(args); code != 0 {
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
newPath := filepath.Join(local.DefaultEnvDir, "test", DefaultStateFilename)
|
newPath := filepath.Join(local.DefaultWorkspaceDir, "test", DefaultStateFilename)
|
||||||
envState := state.LocalState{Path: newPath}
|
envState := state.LocalState{Path: newPath}
|
||||||
err = envState.RefreshState()
|
err = envState.RefreshState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -191,43 +191,43 @@ func TestEnv_createWithState(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnv_delete(t *testing.T) {
|
func TestWorkspace_delete(t *testing.T) {
|
||||||
td := tempDir(t)
|
td := tempDir(t)
|
||||||
os.MkdirAll(td, 0755)
|
os.MkdirAll(td, 0755)
|
||||||
defer os.RemoveAll(td)
|
defer os.RemoveAll(td)
|
||||||
defer testChdir(t, td)()
|
defer testChdir(t, td)()
|
||||||
|
|
||||||
// create the env directories
|
// create the workspace directories
|
||||||
if err := os.MkdirAll(filepath.Join(local.DefaultEnvDir, "test"), 0755); err != nil {
|
if err := os.MkdirAll(filepath.Join(local.DefaultWorkspaceDir, "test"), 0755); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the environment file
|
// create the workspace file
|
||||||
if err := os.MkdirAll(DefaultDataDir, 0755); err != nil {
|
if err := os.MkdirAll(DefaultDataDir, 0755); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := ioutil.WriteFile(filepath.Join(DefaultDataDir, local.DefaultEnvFile), []byte("test"), 0644); err != nil {
|
if err := ioutil.WriteFile(filepath.Join(DefaultDataDir, local.DefaultWorkspaceFile), []byte("test"), 0644); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
delCmd := &EnvDeleteCommand{
|
delCmd := &WorkspaceDeleteCommand{
|
||||||
Meta: Meta{Ui: ui},
|
Meta: Meta{Ui: ui},
|
||||||
}
|
}
|
||||||
|
|
||||||
current := delCmd.Env()
|
current := delCmd.Workspace()
|
||||||
if current != "test" {
|
if current != "test" {
|
||||||
t.Fatal("wrong env:", current)
|
t.Fatal("wrong workspace:", current)
|
||||||
}
|
}
|
||||||
|
|
||||||
// we can't delete out current environment
|
// we can't delete our current workspace
|
||||||
args := []string{"test"}
|
args := []string{"test"}
|
||||||
if code := delCmd.Run(args); code == 0 {
|
if code := delCmd.Run(args); code == 0 {
|
||||||
t.Fatal("expected error deleting current env")
|
t.Fatal("expected error deleting current workspace")
|
||||||
}
|
}
|
||||||
|
|
||||||
// change back to default
|
// change back to default
|
||||||
if err := delCmd.SetEnv(backend.DefaultStateName); err != nil {
|
if err := delCmd.SetWorkspace(backend.DefaultStateName); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,22 +235,22 @@ func TestEnv_delete(t *testing.T) {
|
||||||
ui = new(cli.MockUi)
|
ui = new(cli.MockUi)
|
||||||
delCmd.Meta.Ui = ui
|
delCmd.Meta.Ui = ui
|
||||||
if code := delCmd.Run(args); code != 0 {
|
if code := delCmd.Run(args); code != 0 {
|
||||||
t.Fatalf("error deleting env: %s", ui.ErrorWriter)
|
t.Fatalf("error deleting workspace: %s", ui.ErrorWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
current = delCmd.Env()
|
current = delCmd.Workspace()
|
||||||
if current != backend.DefaultStateName {
|
if current != backend.DefaultStateName {
|
||||||
t.Fatalf("wrong env: %q", current)
|
t.Fatalf("wrong workspace: %q", current)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func TestEnv_deleteWithState(t *testing.T) {
|
func TestWorkspace_deleteWithState(t *testing.T) {
|
||||||
td := tempDir(t)
|
td := tempDir(t)
|
||||||
os.MkdirAll(td, 0755)
|
os.MkdirAll(td, 0755)
|
||||||
defer os.RemoveAll(td)
|
defer os.RemoveAll(td)
|
||||||
defer testChdir(t, td)()
|
defer testChdir(t, td)()
|
||||||
|
|
||||||
// create the env directories
|
// create the workspace directories
|
||||||
if err := os.MkdirAll(filepath.Join(local.DefaultEnvDir, "test"), 0755); err != nil {
|
if err := os.MkdirAll(filepath.Join(local.DefaultWorkspaceDir, "test"), 0755); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,14 +271,14 @@ func TestEnv_deleteWithState(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
envStatePath := filepath.Join(local.DefaultEnvDir, "test", DefaultStateFilename)
|
envStatePath := filepath.Join(local.DefaultWorkspaceDir, "test", DefaultStateFilename)
|
||||||
err := (&state.LocalState{Path: envStatePath}).WriteState(originalState)
|
err := (&state.LocalState{Path: envStatePath}).WriteState(originalState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ui := new(cli.MockUi)
|
ui := new(cli.MockUi)
|
||||||
delCmd := &EnvDeleteCommand{
|
delCmd := &WorkspaceDeleteCommand{
|
||||||
Meta: Meta{Ui: ui},
|
Meta: Meta{Ui: ui},
|
||||||
}
|
}
|
||||||
args := []string{"test"}
|
args := []string{"test"}
|
||||||
|
@ -294,7 +294,7 @@ func TestEnv_deleteWithState(t *testing.T) {
|
||||||
t.Fatalf("failure: %s", ui.ErrorWriter)
|
t.Fatalf("failure: %s", ui.ErrorWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stat(filepath.Join(local.DefaultEnvDir, "test")); !os.IsNotExist(err) {
|
if _, err := os.Stat(filepath.Join(local.DefaultWorkspaceDir, "test")); !os.IsNotExist(err) {
|
||||||
t.Fatal("env 'test' still exists!")
|
t.Fatal("env 'test' still exists!")
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -10,16 +10,19 @@ import (
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EnvDeleteCommand struct {
|
type WorkspaceDeleteCommand struct {
|
||||||
Meta
|
Meta
|
||||||
|
LegacyName bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EnvDeleteCommand) Run(args []string) int {
|
func (c *WorkspaceDeleteCommand) Run(args []string) int {
|
||||||
args = c.Meta.process(args, true)
|
args = c.Meta.process(args, true)
|
||||||
|
|
||||||
|
envCommandShowWarning(c.Ui, c.LegacyName)
|
||||||
|
|
||||||
force := false
|
force := false
|
||||||
cmdFlags := c.Meta.flagSet("env")
|
cmdFlags := c.Meta.flagSet("workspace")
|
||||||
cmdFlags.BoolVar(&force, "force", false, "force removal of a non-empty environment")
|
cmdFlags.BoolVar(&force, "force", false, "force removal of a non-empty workspace")
|
||||||
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||||
if err := cmdFlags.Parse(args); err != nil {
|
if err := cmdFlags.Parse(args); err != nil {
|
||||||
return 1
|
return 1
|
||||||
|
@ -32,7 +35,7 @@ func (c *EnvDeleteCommand) Run(args []string) int {
|
||||||
|
|
||||||
delEnv := args[0]
|
delEnv := args[0]
|
||||||
|
|
||||||
if !validEnvName(delEnv) {
|
if !validWorkspaceName(delEnv) {
|
||||||
c.Ui.Error(fmt.Sprintf(envInvalidName, delEnv))
|
c.Ui.Error(fmt.Sprintf(envInvalidName, delEnv))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
@ -78,7 +81,7 @@ func (c *EnvDeleteCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if delEnv == c.Env() {
|
if delEnv == c.Workspace() {
|
||||||
c.Ui.Error(fmt.Sprintf(strings.TrimSpace(envDelCurrent), delEnv))
|
c.Ui.Error(fmt.Sprintf(strings.TrimSpace(envDelCurrent), delEnv))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
@ -108,7 +111,7 @@ func (c *EnvDeleteCommand) Run(args []string) int {
|
||||||
|
|
||||||
// Lock the state if we can
|
// Lock the state if we can
|
||||||
lockInfo := state.NewLockInfo()
|
lockInfo := state.NewLockInfo()
|
||||||
lockInfo.Operation = "env delete"
|
lockInfo.Operation = "workspace delete"
|
||||||
lockID, err := clistate.Lock(lockCtx, sMgr, lockInfo, c.Ui, c.Colorize())
|
lockID, err := clistate.Lock(lockCtx, sMgr, lockInfo, c.Ui, c.Colorize())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
|
||||||
|
@ -139,20 +142,20 @@ func (c *EnvDeleteCommand) Run(args []string) int {
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
func (c *EnvDeleteCommand) Help() string {
|
func (c *WorkspaceDeleteCommand) Help() string {
|
||||||
helpText := `
|
helpText := `
|
||||||
Usage: terraform env delete [OPTIONS] NAME [DIR]
|
Usage: terraform workspace delete [OPTIONS] NAME [DIR]
|
||||||
|
|
||||||
Delete a Terraform environment
|
Delete a Terraform workspace
|
||||||
|
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
-force remove a non-empty environment.
|
-force remove a non-empty workspace.
|
||||||
`
|
`
|
||||||
return strings.TrimSpace(helpText)
|
return strings.TrimSpace(helpText)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EnvDeleteCommand) Synopsis() string {
|
func (c *WorkspaceDeleteCommand) Synopsis() string {
|
||||||
return "Delete an environment"
|
return "Delete a workspace"
|
||||||
}
|
}
|
|
@ -6,14 +6,17 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EnvListCommand struct {
|
type WorkspaceListCommand struct {
|
||||||
Meta
|
Meta
|
||||||
|
LegacyName bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EnvListCommand) Run(args []string) int {
|
func (c *WorkspaceListCommand) Run(args []string) int {
|
||||||
args = c.Meta.process(args, true)
|
args = c.Meta.process(args, true)
|
||||||
|
|
||||||
cmdFlags := c.Meta.flagSet("env list")
|
envCommandShowWarning(c.Ui, c.LegacyName)
|
||||||
|
|
||||||
|
cmdFlags := c.Meta.flagSet("workspace list")
|
||||||
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||||
if err := cmdFlags.Parse(args); err != nil {
|
if err := cmdFlags.Parse(args); err != nil {
|
||||||
return 1
|
return 1
|
||||||
|
@ -48,7 +51,7 @@ func (c *EnvListCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
env := c.Env()
|
env, isOverridden := c.WorkspaceOverridden()
|
||||||
|
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
for _, s := range states {
|
for _, s := range states {
|
||||||
|
@ -61,18 +64,23 @@ func (c *EnvListCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Ui.Output(out.String())
|
c.Ui.Output(out.String())
|
||||||
|
|
||||||
|
if isOverridden {
|
||||||
|
c.Ui.Output(envIsOverriddenNote)
|
||||||
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EnvListCommand) Help() string {
|
func (c *WorkspaceListCommand) Help() string {
|
||||||
helpText := `
|
helpText := `
|
||||||
Usage: terraform env list [DIR]
|
Usage: terraform workspace list [DIR]
|
||||||
|
|
||||||
List Terraform environments.
|
List Terraform workspaces.
|
||||||
`
|
`
|
||||||
return strings.TrimSpace(helpText)
|
return strings.TrimSpace(helpText)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EnvListCommand) Synopsis() string {
|
func (c *WorkspaceListCommand) Synopsis() string {
|
||||||
return "List Environments"
|
return "List Workspaces"
|
||||||
}
|
}
|
|
@ -12,16 +12,19 @@ import (
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EnvNewCommand struct {
|
type WorkspaceNewCommand struct {
|
||||||
Meta
|
Meta
|
||||||
|
LegacyName bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EnvNewCommand) Run(args []string) int {
|
func (c *WorkspaceNewCommand) Run(args []string) int {
|
||||||
args = c.Meta.process(args, true)
|
args = c.Meta.process(args, true)
|
||||||
|
|
||||||
|
envCommandShowWarning(c.Ui, c.LegacyName)
|
||||||
|
|
||||||
statePath := ""
|
statePath := ""
|
||||||
|
|
||||||
cmdFlags := c.Meta.flagSet("env new")
|
cmdFlags := c.Meta.flagSet("workspace new")
|
||||||
cmdFlags.StringVar(&statePath, "state", "", "terraform state file")
|
cmdFlags.StringVar(&statePath, "state", "", "terraform state file")
|
||||||
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||||
if err := cmdFlags.Parse(args); err != nil {
|
if err := cmdFlags.Parse(args); err != nil {
|
||||||
|
@ -35,11 +38,18 @@ func (c *EnvNewCommand) Run(args []string) int {
|
||||||
|
|
||||||
newEnv := args[0]
|
newEnv := args[0]
|
||||||
|
|
||||||
if !validEnvName(newEnv) {
|
if !validWorkspaceName(newEnv) {
|
||||||
c.Ui.Error(fmt.Sprintf(envInvalidName, newEnv))
|
c.Ui.Error(fmt.Sprintf(envInvalidName, newEnv))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// You can't ask to create a workspace when you're overriding the
|
||||||
|
// workspace name to be something different.
|
||||||
|
if current, isOverridden := c.WorkspaceOverridden(); current != newEnv && isOverridden {
|
||||||
|
c.Ui.Error(envIsOverriddenNewError)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
configPath, err := ModulePath(args[1:])
|
configPath, err := ModulePath(args[1:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(err.Error())
|
c.Ui.Error(err.Error())
|
||||||
|
@ -75,9 +85,9 @@ func (c *EnvNewCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// now save the current env locally
|
// now set the current workspace locally
|
||||||
if err := c.SetEnv(newEnv); err != nil {
|
if err := c.SetWorkspace(newEnv); err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("error saving new environment name: %s", err))
|
c.Ui.Error(fmt.Sprintf("Error selecting new workspace: %s", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +112,7 @@ func (c *EnvNewCommand) Run(args []string) int {
|
||||||
|
|
||||||
// Lock the state if we can
|
// Lock the state if we can
|
||||||
lockInfo := state.NewLockInfo()
|
lockInfo := state.NewLockInfo()
|
||||||
lockInfo.Operation = "env new"
|
lockInfo.Operation = "workspace new"
|
||||||
lockID, err := clistate.Lock(lockCtx, sMgr, lockInfo, c.Ui, c.Colorize())
|
lockID, err := clistate.Lock(lockCtx, sMgr, lockInfo, c.Ui, c.Colorize())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
|
||||||
|
@ -139,20 +149,20 @@ func (c *EnvNewCommand) Run(args []string) int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EnvNewCommand) Help() string {
|
func (c *WorkspaceNewCommand) Help() string {
|
||||||
helpText := `
|
helpText := `
|
||||||
Usage: terraform env new [OPTIONS] NAME [DIR]
|
Usage: terraform workspace new [OPTIONS] NAME [DIR]
|
||||||
|
|
||||||
Create a new Terraform environment.
|
Create a new Terraform workspace.
|
||||||
|
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
-state=path Copy an existing state file into the new environment.
|
-state=path Copy an existing state file into the new workspace.
|
||||||
`
|
`
|
||||||
return strings.TrimSpace(helpText)
|
return strings.TrimSpace(helpText)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EnvNewCommand) Synopsis() string {
|
func (c *WorkspaceNewCommand) Synopsis() string {
|
||||||
return "Create a new environment"
|
return "Create a new workspace"
|
||||||
}
|
}
|
|
@ -7,14 +7,17 @@ import (
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EnvSelectCommand struct {
|
type WorkspaceSelectCommand struct {
|
||||||
Meta
|
Meta
|
||||||
|
LegacyName bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EnvSelectCommand) Run(args []string) int {
|
func (c *WorkspaceSelectCommand) Run(args []string) int {
|
||||||
args = c.Meta.process(args, true)
|
args = c.Meta.process(args, true)
|
||||||
|
|
||||||
cmdFlags := c.Meta.flagSet("env select")
|
envCommandShowWarning(c.Ui, c.LegacyName)
|
||||||
|
|
||||||
|
cmdFlags := c.Meta.flagSet("workspace select")
|
||||||
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||||
if err := cmdFlags.Parse(args); err != nil {
|
if err := cmdFlags.Parse(args); err != nil {
|
||||||
return 1
|
return 1
|
||||||
|
@ -34,6 +37,11 @@ func (c *EnvSelectCommand) Run(args []string) int {
|
||||||
conf, err := c.Config(configPath)
|
conf, err := c.Config(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load root config module: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to load root config module: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
current, isOverridden := c.WorkspaceOverridden()
|
||||||
|
if isOverridden {
|
||||||
|
c.Ui.Error(envIsOverriddenSelectError)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +56,7 @@ func (c *EnvSelectCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
if !validEnvName(name) {
|
if !validWorkspaceName(name) {
|
||||||
c.Ui.Error(fmt.Sprintf(envInvalidName, name))
|
c.Ui.Error(fmt.Sprintf(envInvalidName, name))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
@ -59,8 +67,8 @@ func (c *EnvSelectCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if name == c.Env() {
|
if name == current {
|
||||||
// already using this env
|
// already using this workspace
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +85,7 @@ func (c *EnvSelectCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.SetEnv(name)
|
err = c.SetWorkspace(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(err.Error())
|
c.Ui.Error(err.Error())
|
||||||
return 1
|
return 1
|
||||||
|
@ -92,15 +100,15 @@ func (c *EnvSelectCommand) Run(args []string) int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EnvSelectCommand) Help() string {
|
func (c *WorkspaceSelectCommand) Help() string {
|
||||||
helpText := `
|
helpText := `
|
||||||
Usage: terraform env select NAME [DIR]
|
Usage: terraform workspace select NAME [DIR]
|
||||||
|
|
||||||
Change Terraform environment.
|
Select a different Terraform workspace.
|
||||||
`
|
`
|
||||||
return strings.TrimSpace(helpText)
|
return strings.TrimSpace(helpText)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EnvSelectCommand) Synopsis() string {
|
func (c *WorkspaceSelectCommand) Synopsis() string {
|
||||||
return "Change environments"
|
return "Select a workspace"
|
||||||
}
|
}
|
45
commands.go
45
commands.go
|
@ -72,32 +72,37 @@ func init() {
|
||||||
},
|
},
|
||||||
|
|
||||||
"env": func() (cli.Command, error) {
|
"env": func() (cli.Command, error) {
|
||||||
return &command.EnvCommand{
|
return &command.WorkspaceCommand{
|
||||||
Meta: meta,
|
Meta: meta,
|
||||||
|
LegacyName: true,
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
|
||||||
"env list": func() (cli.Command, error) {
|
"env list": func() (cli.Command, error) {
|
||||||
return &command.EnvListCommand{
|
return &command.WorkspaceListCommand{
|
||||||
Meta: meta,
|
Meta: meta,
|
||||||
|
LegacyName: true,
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
|
||||||
"env select": func() (cli.Command, error) {
|
"env select": func() (cli.Command, error) {
|
||||||
return &command.EnvSelectCommand{
|
return &command.WorkspaceSelectCommand{
|
||||||
Meta: meta,
|
Meta: meta,
|
||||||
|
LegacyName: true,
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
|
||||||
"env new": func() (cli.Command, error) {
|
"env new": func() (cli.Command, error) {
|
||||||
return &command.EnvNewCommand{
|
return &command.WorkspaceNewCommand{
|
||||||
Meta: meta,
|
Meta: meta,
|
||||||
|
LegacyName: true,
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
|
||||||
"env delete": func() (cli.Command, error) {
|
"env delete": func() (cli.Command, error) {
|
||||||
return &command.EnvDeleteCommand{
|
return &command.WorkspaceDeleteCommand{
|
||||||
Meta: meta,
|
Meta: meta,
|
||||||
|
LegacyName: true,
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -201,6 +206,36 @@ func init() {
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"workspace": func() (cli.Command, error) {
|
||||||
|
return &command.WorkspaceCommand{
|
||||||
|
Meta: meta,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
|
||||||
|
"workspace list": func() (cli.Command, error) {
|
||||||
|
return &command.WorkspaceListCommand{
|
||||||
|
Meta: meta,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
|
||||||
|
"workspace select": func() (cli.Command, error) {
|
||||||
|
return &command.WorkspaceSelectCommand{
|
||||||
|
Meta: meta,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
|
||||||
|
"workspace new": func() (cli.Command, error) {
|
||||||
|
return &command.WorkspaceNewCommand{
|
||||||
|
Meta: meta,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
|
||||||
|
"workspace delete": func() (cli.Command, error) {
|
||||||
|
return &command.WorkspaceDeleteCommand{
|
||||||
|
Meta: meta,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
|
||||||
//-----------------------------------------------------------
|
//-----------------------------------------------------------
|
||||||
// Plumbing
|
// Plumbing
|
||||||
//-----------------------------------------------------------
|
//-----------------------------------------------------------
|
||||||
|
|
|
@ -317,9 +317,13 @@ func (i *Interpolater) valueTerraformVar(
|
||||||
n string,
|
n string,
|
||||||
v *config.TerraformVariable,
|
v *config.TerraformVariable,
|
||||||
result map[string]ast.Variable) error {
|
result map[string]ast.Variable) error {
|
||||||
if v.Field != "env" {
|
|
||||||
|
// "env" is supported for backward compatibility, but it's deprecated and
|
||||||
|
// so we won't advertise it as being allowed in the error message. It will
|
||||||
|
// be removed in a future version of Terraform.
|
||||||
|
if v.Field != "workspace" && v.Field != "env" {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"%s: only supported key for 'terraform.X' interpolations is 'env'", n)
|
"%s: only supported key for 'terraform.X' interpolations is 'workspace'", n)
|
||||||
}
|
}
|
||||||
|
|
||||||
if i.Meta == nil {
|
if i.Meta == nil {
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
layout: "docs"
|
||||||
|
page_title: "Command: env"
|
||||||
|
sidebar_current: "docs-commands-env"
|
||||||
|
description: |-
|
||||||
|
The terraform env command is a deprecated, legacy form of "terraform workspace".
|
||||||
|
---
|
||||||
|
|
||||||
|
# Command: env
|
||||||
|
|
||||||
|
The `terraform env` command is deprecated.
|
||||||
|
[The `terraform workspace` command](/docs/commands/workspace/)
|
||||||
|
should be used instead.
|
|
@ -1,39 +0,0 @@
|
||||||
---
|
|
||||||
layout: "commands-env"
|
|
||||||
page_title: "Command: env delete"
|
|
||||||
sidebar_current: "docs-env-sub-delete"
|
|
||||||
description: |-
|
|
||||||
The terraform env delete command is used to create a delete state environment.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Command: env delete
|
|
||||||
|
|
||||||
The `terraform env delete` command is used to delete an existing environment.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Usage: `terraform env delete [NAME]`
|
|
||||||
|
|
||||||
This command will delete the specified environment.
|
|
||||||
|
|
||||||
To delete an environment, it must already exist, it must be empty, and
|
|
||||||
it must not be your current environment. If the environment
|
|
||||||
is not empty, Terraform will not allow you to delete it without the
|
|
||||||
`-force` flag.
|
|
||||||
|
|
||||||
If you delete a non-empty state (via force), then resources may become
|
|
||||||
"dangling". These are resources that Terraform no longer manages since
|
|
||||||
a state doesn't point to them, but still physically exist. This is sometimes
|
|
||||||
preferred: you want Terraform to stop managing resources. Most of the time,
|
|
||||||
however, this is not intended so Terraform protects you from doing this.
|
|
||||||
|
|
||||||
The command-line flags are all optional. The list of available flags are:
|
|
||||||
|
|
||||||
* `-force` - Delete the state even if non-empty. Defaults to false.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
```
|
|
||||||
$ terraform env delete example
|
|
||||||
Deleted environment "example"!
|
|
||||||
```
|
|
|
@ -1,21 +0,0 @@
|
||||||
---
|
|
||||||
layout: "commands-env"
|
|
||||||
page_title: "Command: env"
|
|
||||||
sidebar_current: "docs-env-index"
|
|
||||||
description: |-
|
|
||||||
The `terraform env` command is used to manage state environments.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Env Command
|
|
||||||
|
|
||||||
The `terraform env` command is used to manage
|
|
||||||
[state environments](/docs/state/environments.html).
|
|
||||||
|
|
||||||
This command is a nested subcommand, meaning that it has further subcommands.
|
|
||||||
These subcommands are listed to the left.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Usage: `terraform env <subcommand> [options] [args]`
|
|
||||||
|
|
||||||
Please click a subcommand to the left for more information.
|
|
|
@ -1,27 +0,0 @@
|
||||||
---
|
|
||||||
layout: "commands-env"
|
|
||||||
page_title: "Command: env list"
|
|
||||||
sidebar_current: "docs-env-sub-list"
|
|
||||||
description: |-
|
|
||||||
The terraform env list command is used to list all created state environments.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Command: env list
|
|
||||||
|
|
||||||
The `terraform env list` command is used to list all created state environments.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Usage: `terraform env list`
|
|
||||||
|
|
||||||
The command will list all created environments. The current environment
|
|
||||||
will have an asterisk (`*`) next to it.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
```
|
|
||||||
$ terraform env list
|
|
||||||
default
|
|
||||||
* development
|
|
||||||
mitchellh-test
|
|
||||||
```
|
|
|
@ -1,50 +0,0 @@
|
||||||
---
|
|
||||||
layout: "commands-env"
|
|
||||||
page_title: "Command: env new"
|
|
||||||
sidebar_current: "docs-env-sub-new"
|
|
||||||
description: |-
|
|
||||||
The terraform env new command is used to create a new state environment.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Command: env new
|
|
||||||
|
|
||||||
The `terraform env new` command is used to create a new state
|
|
||||||
environment.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Usage: `terraform env new [NAME]`
|
|
||||||
|
|
||||||
This command will create a new environment with the given name. This
|
|
||||||
environment must not already exist.
|
|
||||||
|
|
||||||
If the `-state` flag is given, the state specified by the given path
|
|
||||||
will be copied to initialize the state for this new environment.
|
|
||||||
|
|
||||||
The command-line flags are all optional. The list of available flags are:
|
|
||||||
|
|
||||||
* `-state=path` - Path to a state file to initialize the state of this environment.
|
|
||||||
|
|
||||||
## Example: Create
|
|
||||||
|
|
||||||
```
|
|
||||||
$ terraform env new example
|
|
||||||
Created and switched to environment "example"!
|
|
||||||
|
|
||||||
You're now on a new, empty environment. Environments isolate their state,
|
|
||||||
so if you run "terraform plan" Terraform will not see any existing state
|
|
||||||
for this configuration.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Example: Create from State
|
|
||||||
|
|
||||||
To create a new environment from a pre-existing state path:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ terraform env new -state=old.terraform.tfstate example
|
|
||||||
Created and switched to environment "example"!
|
|
||||||
|
|
||||||
You're now on a new, empty environment. Environments isolate their state,
|
|
||||||
so if you run "terraform plan" Terraform will not see any existing state
|
|
||||||
for this configuration.
|
|
||||||
```
|
|
|
@ -1,31 +0,0 @@
|
||||||
---
|
|
||||||
layout: "commands-env"
|
|
||||||
page_title: "Command: env select"
|
|
||||||
sidebar_current: "docs-env-sub-select"
|
|
||||||
description: |-
|
|
||||||
The terraform env select command is used to select state environments.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Command: env select
|
|
||||||
|
|
||||||
The `terraform env select` command is used to select to a different
|
|
||||||
environment that is already created.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Usage: `terraform env select [NAME]`
|
|
||||||
|
|
||||||
This command will select to another environment. The environment must
|
|
||||||
already be created.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
```
|
|
||||||
$ terraform env list
|
|
||||||
default
|
|
||||||
* development
|
|
||||||
mitchellh-test
|
|
||||||
|
|
||||||
$ terraform env select default
|
|
||||||
Switched to environment "default"!
|
|
||||||
```
|
|
|
@ -33,7 +33,6 @@ Common commands:
|
||||||
apply Builds or changes infrastructure
|
apply Builds or changes infrastructure
|
||||||
console Interactive console for Terraform interpolations
|
console Interactive console for Terraform interpolations
|
||||||
destroy Destroy Terraform-managed infrastructure
|
destroy Destroy Terraform-managed infrastructure
|
||||||
env Environment management
|
|
||||||
fmt Rewrites config files to canonical format
|
fmt Rewrites config files to canonical format
|
||||||
get Download and install modules for the configuration
|
get Download and install modules for the configuration
|
||||||
graph Create a visual graph of Terraform resources
|
graph Create a visual graph of Terraform resources
|
||||||
|
@ -49,6 +48,7 @@ Common commands:
|
||||||
untaint Manually unmark a resource as tainted
|
untaint Manually unmark a resource as tainted
|
||||||
validate Validates the Terraform files
|
validate Validates the Terraform files
|
||||||
version Prints the Terraform version
|
version Prints the Terraform version
|
||||||
|
workspace Workspace management
|
||||||
|
|
||||||
All other commands:
|
All other commands:
|
||||||
debug Debug output management (experimental)
|
debug Debug output management (experimental)
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
---
|
||||||
|
layout: "commands-workspace"
|
||||||
|
page_title: "Command: workspace delete"
|
||||||
|
sidebar_current: "docs-workspace-sub-delete"
|
||||||
|
description: |-
|
||||||
|
The terraform workspace delete command is used to delete a workspace.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Command: workspace delete
|
||||||
|
|
||||||
|
The `terraform workspace delete` command is used to delete an existing workspace.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Usage: `terraform workspace delete [NAME]`
|
||||||
|
|
||||||
|
This command will delete the specified workspace.
|
||||||
|
|
||||||
|
To delete an workspace, it must already exist, it must have an empty state,
|
||||||
|
and it must not be your current workspace. If the workspace state is not empty,
|
||||||
|
Terraform will not allow you to delete it unless the `-force` flag is specified.
|
||||||
|
|
||||||
|
If you delete a workspace with a non-empty state (via `-force`), then resources
|
||||||
|
may become "dangling". These are resources that physically exist but that
|
||||||
|
Terraform can no longer manage. This is sometimes preferred: you want
|
||||||
|
Terraform to stop managing resources so they can be managed some other way.
|
||||||
|
Most of the time, however, this is not intended and so Terraform protects you
|
||||||
|
from getting into this situation.
|
||||||
|
|
||||||
|
The command-line flags are all optional. The only supported flag is:
|
||||||
|
|
||||||
|
* `-force` - Delete the workspace even if its state is not empty. Defaults to false.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```
|
||||||
|
$ terraform workspace delete example
|
||||||
|
Deleted workspace "example".
|
||||||
|
```
|
|
@ -0,0 +1,21 @@
|
||||||
|
---
|
||||||
|
layout: "commands-workspace"
|
||||||
|
page_title: "Command: workspace"
|
||||||
|
sidebar_current: "docs-workspace-index"
|
||||||
|
description: |-
|
||||||
|
The terraform workspace command is used to manage workspaces.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Command: workspace
|
||||||
|
|
||||||
|
The `terraform workspace` command is used to manage
|
||||||
|
[workspaces](/docs/state/workspaces.html).
|
||||||
|
|
||||||
|
This command is a container for further subcommands. These subcommands are
|
||||||
|
listed in the navigation bar.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Usage: `terraform workspace <subcommand> [options] [args]`
|
||||||
|
|
||||||
|
Please choose a subcommand from the navigation for more information.
|
|
@ -0,0 +1,27 @@
|
||||||
|
---
|
||||||
|
layout: "commands-workspace"
|
||||||
|
page_title: "Command: workspace list"
|
||||||
|
sidebar_current: "docs-workspace-sub-list"
|
||||||
|
description: |-
|
||||||
|
The terraform workspace list command is used to list all existing workspaces.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Command: workspace list
|
||||||
|
|
||||||
|
The `terraform workspace list` command is used to list all existing workspaces.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Usage: `terraform workspace list`
|
||||||
|
|
||||||
|
The command will list all existing workspaces. The current workspace is
|
||||||
|
indicated using an asterisk (`*`) marker.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```
|
||||||
|
$ terraform workspace list
|
||||||
|
default
|
||||||
|
* development
|
||||||
|
jsmith-test
|
||||||
|
```
|
|
@ -0,0 +1,49 @@
|
||||||
|
---
|
||||||
|
layout: "commands-workspace"
|
||||||
|
page_title: "Command: workspace new"
|
||||||
|
sidebar_current: "docs-workspace-sub-new"
|
||||||
|
description: |-
|
||||||
|
The terraform workspace new command is used to create a new workspace.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Command: workspace new
|
||||||
|
|
||||||
|
The `terraform workspace new` command is used to create a new workspace.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Usage: `terraform workspace new [NAME]`
|
||||||
|
|
||||||
|
This command will create a new workspace with the given name. A workspace with
|
||||||
|
this name must not already exist.
|
||||||
|
|
||||||
|
If the `-state` flag is given, the state specified by the given path
|
||||||
|
will be copied to initialize the state for this new workspace.
|
||||||
|
|
||||||
|
The command-line flags are all optional. The only supported flag is:
|
||||||
|
|
||||||
|
* `-state=path` - Path to a state file to initialize the state of this environment.
|
||||||
|
|
||||||
|
## Example: Create
|
||||||
|
|
||||||
|
```
|
||||||
|
$ terraform workspace new example
|
||||||
|
Created and switched to workspace "example"!
|
||||||
|
|
||||||
|
You're now on a new, empty workspace. Workspaces isolate their state,
|
||||||
|
so if you run "terraform plan" Terraform will not see any existing state
|
||||||
|
for this configuration.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example: Create from State
|
||||||
|
|
||||||
|
To create a new workspace from a pre-existing local state file:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ terraform workspace new -state=old.terraform.tfstate example
|
||||||
|
Created and switched to workspace "example".
|
||||||
|
|
||||||
|
You're now on a new, empty workspace. Workspaces isolate their state,
|
||||||
|
so if you run "terraform plan" Terraform will not see any existing state
|
||||||
|
for this configuration.
|
||||||
|
```
|
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
layout: "commands-workspace"
|
||||||
|
page_title: "Command: workspace select"
|
||||||
|
sidebar_current: "docs-workspace-sub-select"
|
||||||
|
description: |-
|
||||||
|
The terraform workspace select command is used to choose a workspace.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Command: env select
|
||||||
|
|
||||||
|
The `terraform workspace select` command is used to choose a different
|
||||||
|
workspace to use for further operations.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Usage: `terraform workspace select [NAME]`
|
||||||
|
|
||||||
|
This command will select another workspace. The named workspace must already
|
||||||
|
exist.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```
|
||||||
|
$ terraform workspace list
|
||||||
|
default
|
||||||
|
* development
|
||||||
|
jsmith-test
|
||||||
|
|
||||||
|
$ terraform workspace select default
|
||||||
|
Switched to workspace "default".
|
||||||
|
```
|
|
@ -31,9 +31,9 @@ resource "aws_instance" "foo" {
|
||||||
The following arguments are supported:
|
The following arguments are supported:
|
||||||
|
|
||||||
* `backend` - (Required) The remote backend to use.
|
* `backend` - (Required) The remote backend to use.
|
||||||
* `environment` - (Optional) The Terraform environment to use.
|
* `workspace` - (Optional) The Terraform workspace whose state will be requested. Defaults to "default".
|
||||||
* `config` - (Optional) The configuration of the remote backend.
|
* `config` - (Optional) The configuration of the remote backend. For more information,
|
||||||
* Remote state config docs can be found [here](/docs/backends/types/terraform-enterprise.html)
|
see [the Backend Types documentation](/docs/backends/types/).
|
||||||
|
|
||||||
## Attributes Reference
|
## Attributes Reference
|
||||||
|
|
||||||
|
|
|
@ -3,137 +3,18 @@ layout: "docs"
|
||||||
page_title: "State: Environments"
|
page_title: "State: Environments"
|
||||||
sidebar_current: "docs-state-env"
|
sidebar_current: "docs-state-env"
|
||||||
description: |-
|
description: |-
|
||||||
Terraform stores state which caches the known state of the world the last time Terraform ran.
|
Legacy terminology for "Workspaces".
|
||||||
---
|
---
|
||||||
|
|
||||||
# State Environments
|
# State Environments
|
||||||
|
|
||||||
An environment is a state namespace, allowing a single folder of Terraform
|
The term _state environment_, or just _environment_, was used within the
|
||||||
configurations to manage multiple distinct infrastructure resources.
|
Terraform 0.9 releases to refer to the idea of having multiple distinct,
|
||||||
|
named states associated with a single configuration directory.
|
||||||
|
|
||||||
Terraform state determines what resources it manages based on what
|
After this concept was implemented, we recieved feedback that this terminology
|
||||||
exists in the state. This is how `terraform plan` determines what isn't
|
caused confusion due to other uses of the word "environment", both within
|
||||||
created, what needs to be updated, etc. The full details of state can be
|
Terraform itself and within organizations using Terraform.
|
||||||
found on the [purpose page](/docs/state/purpose.html).
|
|
||||||
|
|
||||||
Environments are a way to create multiple states that contain
|
As of 0.10, the preferred term is "workspace". For more information on
|
||||||
their own data so a single set of Terraform configurations can manage
|
workspaces, see [the main Workspaces page](/docs/state/workspaces.html).
|
||||||
multiple distinct sets of resources.
|
|
||||||
|
|
||||||
Environments are currently supported by the following backends:
|
|
||||||
|
|
||||||
* [Consul](/docs/backends/types/consul.html)
|
|
||||||
* [S3](/docs/backends/types/s3.html)
|
|
||||||
|
|
||||||
## Using Environments
|
|
||||||
|
|
||||||
Terraform starts with a single environment named "default". This
|
|
||||||
environment is special both because it is the default and also because
|
|
||||||
it cannot ever be deleted. If you've never explicitly used environments, then
|
|
||||||
you've only ever worked on the "default" environment.
|
|
||||||
|
|
||||||
Environments are managed with the `terraform env` set of commands. To
|
|
||||||
create a new environment and switch to it, you can use `terraform env new`,
|
|
||||||
to switch environments you can use `terraform env select`, etc.
|
|
||||||
|
|
||||||
For example, creating an environment:
|
|
||||||
|
|
||||||
```text
|
|
||||||
$ terraform env new bar
|
|
||||||
Created and switched to environment "bar"!
|
|
||||||
|
|
||||||
You're now on a new, empty environment. Environments isolate their state,
|
|
||||||
so if you run "terraform plan" Terraform will not see any existing state
|
|
||||||
for this configuration.
|
|
||||||
```
|
|
||||||
|
|
||||||
As the command says, if you run `terraform plan`, Terraform will not see
|
|
||||||
any existing resources that existed on the default (or any other) environment.
|
|
||||||
**These resources still physically exist,** but are managed by another
|
|
||||||
Terraform environment.
|
|
||||||
|
|
||||||
## Current Environment Interpolation
|
|
||||||
|
|
||||||
Within your Terraform configuration, you may reference the current environment
|
|
||||||
using the `${terraform.env}` interpolation variable. This can be used anywhere
|
|
||||||
interpolations are allowed.
|
|
||||||
|
|
||||||
Referencing the current environment is useful for changing behavior based
|
|
||||||
on the environment. For example, for non-default environments, it may be useful
|
|
||||||
to spin up smaller cluster sizes. You can do this:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
resource "aws_instance" "example" {
|
|
||||||
count = "${terraform.env == "default" ? 5 : 1}"
|
|
||||||
|
|
||||||
# ... other fields
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Another popular use case is using the environment as part of naming or
|
|
||||||
tagging behavior:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
resource "aws_instance" "example" {
|
|
||||||
tags { Name = "web - ${terraform.env}" }
|
|
||||||
|
|
||||||
# ... other fields
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
An environment can be used to manage the difference between development,
|
|
||||||
staging, and production, but it **should not** be treated as the only isolation
|
|
||||||
mechanism. As Terraform configurations get larger, it's much more
|
|
||||||
manageable and safer to split one large configuration into many
|
|
||||||
smaller ones linked together with `terraform_remote_state` data sources. This
|
|
||||||
allows teams to delegate ownership and reduce the blast radius of changes.
|
|
||||||
For **each smaller configuration**, you can use environments to model the
|
|
||||||
differences between development, staging, and production. However, if you have
|
|
||||||
one large Terraform configuration, it is riskier and not recommended to use
|
|
||||||
environments to model those differences.
|
|
||||||
|
|
||||||
The [terraform_remote_state](/docs/providers/terraform/d/remote_state.html)
|
|
||||||
resource accepts an `environment` name to target. Therefore, you can link
|
|
||||||
together multiple independently managed Terraform configurations with the same
|
|
||||||
environment easily. But, they can also have different environments.
|
|
||||||
|
|
||||||
While environments are available to all,
|
|
||||||
[Terraform Enterprise](https://www.hashicorp.com/products/terraform/)
|
|
||||||
provides an interface and API for managing sets of configurations linked
|
|
||||||
with `terraform_remote_state` and viewing them all as a single environment.
|
|
||||||
|
|
||||||
Environments alone are useful for isolating a set of resources to test
|
|
||||||
changes during development. For example, it is common to associate a
|
|
||||||
branch in a VCS with an environment so new features can be developed
|
|
||||||
without affecting the default environment.
|
|
||||||
|
|
||||||
Future Terraform versions and environment enhancements will enable
|
|
||||||
Terraform to track VCS branches with an environment to help verify only certain
|
|
||||||
branches can make changes to a Terraform environment.
|
|
||||||
|
|
||||||
## Environments Internals
|
|
||||||
|
|
||||||
Environments are technically equivalent to renaming your state file. They
|
|
||||||
aren't any more complex than that. Terraform wraps this simple notion with
|
|
||||||
a set of protections and support for remote state.
|
|
||||||
|
|
||||||
For local state, Terraform stores the state environments in a folder
|
|
||||||
`terraform.tfstate.d`. This folder should be committed to version control
|
|
||||||
(just like local-only `terraform.tfstate`).
|
|
||||||
|
|
||||||
For [remote state](/docs/state/remote.html), the environments are stored
|
|
||||||
directly in the configured [backend](/docs/backends). For example, if you
|
|
||||||
use [Consul](/docs/backends/types/consul.html), the environments are stored
|
|
||||||
by suffixing the state path with the environment name. To ensure that
|
|
||||||
environment names are stored correctly and safely in all backends, the name
|
|
||||||
must be valid to use in a URL path segment without escaping.
|
|
||||||
|
|
||||||
The important thing about environment internals is that environments are
|
|
||||||
meant to be a shared resource. They aren't a private, local-only notion
|
|
||||||
(unless you're using purely local state and not committing it).
|
|
||||||
|
|
||||||
The "current environment" name is stored only locally in the ignored
|
|
||||||
`.terraform` directory. This allows multiple team members to work on
|
|
||||||
different environments concurrently.
|
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
---
|
||||||
|
layout: "docs"
|
||||||
|
page_title: "State: Workspaces"
|
||||||
|
sidebar_current: "docs-state-workspaces"
|
||||||
|
description: |-
|
||||||
|
Workspaces allow the use of multiple states with a single configuration directory.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Workspaces
|
||||||
|
|
||||||
|
A _workspace_ is a named container for Terraform state. With multiple
|
||||||
|
workspaces, a single directory of Terraform configuration can be used to
|
||||||
|
manage multiple distinct sets of infrastructure resources.
|
||||||
|
|
||||||
|
Terraform state determines what resources it manages based on what
|
||||||
|
exists in the state. This is how `terraform plan` determines what isn't
|
||||||
|
created, what needs to be updated, etc. The full details of state can be
|
||||||
|
found on [the _purpose_ page](/docs/state/purpose.html).
|
||||||
|
|
||||||
|
Multiple workspaces are currently supported by the following backends:
|
||||||
|
|
||||||
|
* [Consul](/docs/backends/types/consul.html)
|
||||||
|
* [S3](/docs/backends/types/s3.html)
|
||||||
|
|
||||||
|
In the 0.9 line of Terraform releases, this concept was known as "environment".
|
||||||
|
It was renamed in 0.10 based on feedback about confusion caused by the
|
||||||
|
overloading of the word "environment" both within Terraform itself and within
|
||||||
|
organizations that use Terraform.
|
||||||
|
|
||||||
|
## Using Workspaces
|
||||||
|
|
||||||
|
Terraform starts with a single workspace named "default". This
|
||||||
|
workspace is special both because it is the default and also because
|
||||||
|
it cannot ever be deleted. If you've never explicitly used workspaces, then
|
||||||
|
you've only ever worked on the "default" workspace.
|
||||||
|
|
||||||
|
Workspaces are managed with the `terraform workspace` set of commands. To
|
||||||
|
create a new workspace and switch to it, you can use `terraform workspace new`;
|
||||||
|
to switch environments you can use `terraform workspace select`; etc.
|
||||||
|
|
||||||
|
For example, creating a new workspace:
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ terraform workspace new bar
|
||||||
|
Created and switched to workspace "bar"!
|
||||||
|
|
||||||
|
You're now on a new, empty workspace. Workspaces isolate their state,
|
||||||
|
so if you run "terraform plan" Terraform will not see any existing state
|
||||||
|
for this configuration.
|
||||||
|
```
|
||||||
|
|
||||||
|
As the command says, if you run `terraform plan`, Terraform will not see
|
||||||
|
any existing resources that existed on the default (or any other) workspace.
|
||||||
|
**These resources still physically exist,** but are managed in another
|
||||||
|
Terraform workspace.
|
||||||
|
|
||||||
|
## Current Workspace Interpolation
|
||||||
|
|
||||||
|
Within your Terraform configuration, you may include the name of the current
|
||||||
|
workspace using the `${terraform.workspace}` interpolation sequence. This can
|
||||||
|
be used anywhere interpolations are allowed.
|
||||||
|
|
||||||
|
Referencing the current workspace is useful for changing behavior based
|
||||||
|
on the workspace. For example, for non-default workspaces, it may be useful
|
||||||
|
to spin up smaller cluster sizes. For example:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
resource "aws_instance" "example" {
|
||||||
|
count = "${terraform.workspace == "default" ? 5 : 1}"
|
||||||
|
|
||||||
|
# ... other arguments
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Another popular use case is using the workspace name as part of naming or
|
||||||
|
tagging behavior:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
resource "aws_instance" "example" {
|
||||||
|
tags {
|
||||||
|
Name = "web - ${terraform.workspace}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ... other arguments
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
Workspaces can be used to manage small differences between development,
|
||||||
|
staging, and production, but they **should not** be treated as the only
|
||||||
|
isolation mechanism. As Terraform configurations get larger, it's much more
|
||||||
|
manageable and safer to split one large configuration into many
|
||||||
|
smaller ones linked together with the `terraform_remote_state` data source.
|
||||||
|
This allows teams to delegate ownership and reduce the potential impact of
|
||||||
|
changes. For *each* smaller configuration, you can use workspaces to model
|
||||||
|
the differences between development, staging, and production. However, if you
|
||||||
|
have one large Terraform configuration, it is riskier and not recommended to
|
||||||
|
use workspaces to handle those differences.
|
||||||
|
|
||||||
|
[The `terraform_remote_state` data source](/docs/providers/terraform/d/remote_state.html)
|
||||||
|
accepts a `workspace` name to target. Therefore, you can link
|
||||||
|
together multiple independently managed Terraform configurations with the same
|
||||||
|
environment easily, with each configuration itself having multiple workspaces.
|
||||||
|
|
||||||
|
While workspaces are available to all,
|
||||||
|
[Terraform Enterprise](https://www.hashicorp.com/products/terraform/)
|
||||||
|
provides an interface and API for managing sets of configurations linked
|
||||||
|
with `terraform_remote_state` and viewing them all as a single environment.
|
||||||
|
|
||||||
|
Workspaces alone are useful for isolating a set of resources to test
|
||||||
|
changes during development. For example, it is common to associate a
|
||||||
|
branch in a VCS with a temporary workspace so new features can be developed
|
||||||
|
without affecting the default workspace.
|
||||||
|
|
||||||
|
Future Terraform versions and workspace enhancements will enable
|
||||||
|
Terraform to track VCS branches with a workspace to help verify only certain
|
||||||
|
branches can make changes to a Terraform workspace.
|
||||||
|
|
||||||
|
## Workspace Internals
|
||||||
|
|
||||||
|
Workspaces are technically equivalent to renaming your state file. They
|
||||||
|
aren't any more complex than that. Terraform wraps this simple notion with
|
||||||
|
a set of protections and support for remote state.
|
||||||
|
|
||||||
|
For local state, Terraform stores the workspace states in a directory called
|
||||||
|
`terraform.tfstate.d`. This directory should be be treated similarly to
|
||||||
|
local-only `terraform.tfstate`); some teams commit these files to version
|
||||||
|
control, although using a remote backend instead is recommended when there are
|
||||||
|
multiple collaborators.
|
||||||
|
|
||||||
|
For [remote state](/docs/state/remote.html), the workspaces are stored
|
||||||
|
directly in the configured [backend](/docs/backends). For example, if you
|
||||||
|
use [Consul](/docs/backends/types/consul.html), the workspaces are stored
|
||||||
|
by appending the environment name to the state path. To ensure that
|
||||||
|
workspace names are stored correctly and safely in all backends, the name
|
||||||
|
must be valid to use in a URL path segment without escaping.
|
||||||
|
|
||||||
|
The important thing about workspace internals is that workspaces are
|
||||||
|
meant to be a shared resource. They aren't a private, local-only notion
|
||||||
|
(unless you're using purely local state and not committing it).
|
||||||
|
|
||||||
|
The "current workspace" name is stored only locally in the ignored
|
||||||
|
`.terraform` directory. This allows multiple team members to work on
|
||||||
|
different workspaces concurrently.
|
|
@ -1,38 +0,0 @@
|
||||||
<% wrap_layout :inner do %>
|
|
||||||
<% content_for :sidebar do %>
|
|
||||||
<div class="docs-sidebar hidden-print affix-top" role="complementary">
|
|
||||||
<ul class="nav docs-sidenav">
|
|
||||||
<li<%= sidebar_current("docs-home") %>>
|
|
||||||
<a href="/docs/commands/index.html">All Providers</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-env-index") %>>
|
|
||||||
<a href="/docs/commands/env/index.html">env Command</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-env-sub") %>>
|
|
||||||
<a href="#">Subcommands</a>
|
|
||||||
<ul class="nav nav-visible">
|
|
||||||
<li<%= sidebar_current("docs-env-sub-list") %>>
|
|
||||||
<a href="/docs/commands/env/list.html">list</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-env-sub-select") %>>
|
|
||||||
<a href="/docs/commands/env/select.html">select</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-env-sub-new") %>>
|
|
||||||
<a href="/docs/commands/env/new.html">new</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-env-sub-delete") %>>
|
|
||||||
<a href="/docs/commands/env/delete.html">delete</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<%= yield %>
|
|
||||||
<% end %>
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
<% wrap_layout :inner do %>
|
||||||
|
<% content_for :sidebar do %>
|
||||||
|
<div class="docs-sidebar hidden-print affix-top" role="complementary">
|
||||||
|
<ul class="nav docs-sidenav">
|
||||||
|
<li<%= sidebar_current("docs-home") %>>
|
||||||
|
<a href="/docs/commands/index.html">All Commands</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-workspace-index") %>>
|
||||||
|
<a href="/docs/commands/env/index.html">workspace Command</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-workspace-sub") %>>
|
||||||
|
<a href="#">Subcommands</a>
|
||||||
|
<ul class="nav nav-visible">
|
||||||
|
<li<%= sidebar_current("docs-workspace-sub-list") %>>
|
||||||
|
<a href="/docs/commands/workspace/list.html">list</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-workspace-sub-select") %>>
|
||||||
|
<a href="/docs/commands/workspace/select.html">select</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-workspace-sub-new") %>>
|
||||||
|
<a href="/docs/commands/workspace/new.html">new</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-workspace-sub-delete") %>>
|
||||||
|
<a href="/docs/commands/workspace/delete.html">delete</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= yield %>
|
||||||
|
<% end %>
|
|
@ -74,7 +74,7 @@
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-commands-env") %>>
|
<li<%= sidebar_current("docs-commands-env") %>>
|
||||||
<a href="/docs/commands/env/index.html">env</a>
|
<a href="/docs/commands/env.html">env</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-commands-fmt") %>>
|
<li<%= sidebar_current("docs-commands-fmt") %>>
|
||||||
|
@ -140,6 +140,10 @@
|
||||||
<li<%= sidebar_current("docs-commands-untaint") %>>
|
<li<%= sidebar_current("docs-commands-untaint") %>>
|
||||||
<a href="/docs/commands/untaint.html">untaint</a>
|
<a href="/docs/commands/untaint.html">untaint</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-commands-workspace") %>>
|
||||||
|
<a href="/docs/commands/workspace/index.html">workspace</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
@ -171,8 +175,8 @@
|
||||||
<a href="/docs/state/locking.html">Locking</a>
|
<a href="/docs/state/locking.html">Locking</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-state-env") %>>
|
<li<%= sidebar_current("docs-state-workspaces") %>>
|
||||||
<a href="/docs/state/environments.html">Environments</a>
|
<a href="/docs/state/workspaces.html">Workspaces</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-state-remote") %>>
|
<li<%= sidebar_current("docs-state-remote") %>>
|
||||||
|
|
Loading…
Reference in New Issue