Skip to content

Commit

Permalink
Use terraform workspace select to select workspace (#580)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nuru committed Apr 10, 2024
1 parent 75d1d5b commit a1218a9
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 18 deletions.
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
TEST?=$$(go list ./... | grep -v 'vendor')
TEST ?= $$(go list ./... | grep -v 'vendor')
SHELL := /bin/bash
#GOOS=darwin
GOOS=linux
GOARCH=amd64
#GOOS=linux
#GOARCH=amd64
VERSION=test

# List of targets the `readme` target should call before generating the readme
Expand All @@ -18,7 +18,7 @@ get:
go get

build: get
env GOOS=${GOOS} GOARCH=${GOARCH} go build -o build/atmos -v -ldflags "-X 'github.com/cloudposse/atmos/cmd.Version=${VERSION}'"
env $(if $(GOOS),GOOS=$(GOOS)) $(if $(GOARCH),GOARCH=$(GOARCH)) go build -o build/atmos -v -ldflags "-X 'github.com/cloudposse/atmos/cmd.Version=${VERSION}'"

version: build
chmod +x ./build/atmos
Expand Down
3 changes: 2 additions & 1 deletion internal/exec/shell_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ func ExecuteShellCommand(
} else if redirectStdError == "" {
cmd.Stderr = os.Stderr
} else {
f, err := os.OpenFile(redirectStdError, os.O_RDWR|os.O_CREATE, 0644)
f, err := os.OpenFile(redirectStdError, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
u.LogWarning(cliConfig, err.Error())
return err
}

Expand Down
46 changes: 41 additions & 5 deletions internal/exec/terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package exec
import (
"fmt"
"os"
osexec "os/exec"
"path"
"strings"

Expand Down Expand Up @@ -318,11 +319,6 @@ func ExecuteTerraform(info schema.ConfigAndStacksInfo) error {

u.LogDebug(cliConfig, fmt.Sprintf("Working dir: %s", workingDir))

// Set terraform workspace via ENV var
if !(info.SubCommand == "workspace" && info.SubCommand2 != "") {
info.ComponentEnvList = append(info.ComponentEnvList, fmt.Sprintf("TF_WORKSPACE=%s", info.TerraformWorkspace))
}

// Print ENV vars if they are found in the component's stack config
if len(info.ComponentEnvList) > 0 {
u.LogDebug(cliConfig, "\nUsing ENV vars:")
Expand Down Expand Up @@ -374,6 +370,46 @@ func ExecuteTerraform(info schema.ConfigAndStacksInfo) error {

allArgsAndFlags = append(allArgsAndFlags, info.AdditionalArgsAndFlags...)

// Run `terraform workspace` before executing other terraform commands
if info.SubCommand != "init" && !(info.SubCommand == "workspace" && info.SubCommand2 != "") {
workspaceSelectRedirectStdErr := "/dev/stdout"

// If `--redirect-stderr` flag is not passed, always redirect `stderr` to `stdout` for `terraform workspace select` command
if info.RedirectStdErr != "" {
workspaceSelectRedirectStdErr = info.RedirectStdErr
}

err = ExecuteShellCommand(
cliConfig,
info.Command,
[]string{"workspace", "select", info.TerraformWorkspace},
componentPath,
info.ComponentEnvList,
info.DryRun,
workspaceSelectRedirectStdErr,
)
if err != nil {
var osErr *osexec.ExitError
ok := errors.As(err, &osErr)
if !ok || osErr.ExitCode() != 1 {
// err is not a non-zero exit code or err is not exit code 1, which we are expecting
return err
}
err = ExecuteShellCommand(
cliConfig,
info.Command,
[]string{"workspace", "new", info.TerraformWorkspace},
componentPath,
info.ComponentEnvList,
info.DryRun,
info.RedirectStdErr,
)
if err != nil {
return err
}
}
}

// Check if the terraform command requires a user interaction,
// but it's running in a scripted environment (where a `tty` is not attached or `stdin` is not attached)
if os.Stdin == nil && !u.SliceContainsString(info.AdditionalArgsAndFlags, autoApproveFlag) {
Expand Down
17 changes: 9 additions & 8 deletions website/docs/core-concepts/components/terraform-workspaces.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,11 @@ For example, you might use separate workspaces for blue-green deployments or can

To work with workspaces in Terraform, you can use commands like `terraform workspace new`, `terraform workspace select`,
and `terraform workspace delete` to create, switch between, and delete workspaces respectively.
Additionally, the ENV variable [`TF_WORKSPACE`](https://developer.hashicorp.com/terraform/cli/config/environment-variables#tf_workspace)
can be used to create and select Terraform workspaces.
Atmos automatically manages Terraform workspaces for you when you provision components in a stack.

## Terraform Workspaces in Atmos

Atmos uses and automatically calculates Terraform workspaces to manage top-level stacks. By default, Atmos uses the stack
Atmos automatically calculates Terraform workspace names and uses workspaces to manage top-level stacks. By default, Atmos uses the stack
name as the Terraform workspace when provisioning components in the stack. For example, consider the following manifest
for the component `vpc` in the stack `ue2-dev`:

Expand Down Expand Up @@ -72,8 +71,10 @@ When you provision the `vpc` component in the stack `ue2-dev` by executing the f

<br/>

Atmos sets the [`TF_WORKSPACE`](https://developer.hashicorp.com/terraform/cli/config/environment-variables#tf_workspace)
ENV variable to the name of the stack `ue2-dev`, and Terraform then selects this workspace or creates it if it does not exist.
Atmos computes the workspace name to be `ue2-dev`. Any Atmos Terraform command other than `init`, using this stack,
will cause Atmos to select this workspace, creating it if needed. (This leaves the workspace selected as a side effect
for subsequent Terraform commands run outside of Atmos. Atmos version 1.55 took away this side effect, but it was
restored in version 1.69.)

The exception to the default rule (using the stack name as Terraform workspace) is when we provision more than one
instance of the same Terraform component (with the same or different settings) into the same stack by defining multiple
Expand Down Expand Up @@ -133,9 +134,9 @@ When you provision the components by executing the commands:

<br/>

Atmos sets the [`TF_WORKSPACE`](https://developer.hashicorp.com/terraform/cli/config/environment-variables#tf_workspace)
environment variable to `ue2-dev-vpc-1` and `ue2-dev-vpc-2` respectively, and Terraform selects (or creates) different workspaces
for the components. This is done because the same Terraform component `vpc` is used as the workspace prefix
Atmos computes the workspace names as `ue2-dev-vpc-1` and `ue2-dev-vpc-2` respectively,
and selects the appropriate workspace for each component (again, creating it if needed).
This is done because the same Terraform component `vpc` is used as the workspace prefix
(in case of [AWS S3 backend](https://developer.hashicorp.com/terraform/language/settings/backends/s3),
folder in the S3 state bucket), and it's necessary to have different subfolders (`ue2-dev-vpc-1`
and `ue2-dev-vpc-2` instead of just `ue2-dev`) to store the Terraform state files.
Expand Down

0 comments on commit a1218a9

Please sign in to comment.