Skip to content
Next Next commit
fix: Fail apply command if the plan file was generated for a workspac…
…e that isn't the selected workspace.
  • Loading branch information
SarahFrench committed Dec 1, 2025
commit 97c921ab225b88f8542bcc1e88a6173fb0d1633c
15 changes: 15 additions & 0 deletions internal/command/meta_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,21 @@ func (m *Meta) selectWorkspace(b backend.Backend) error {
func (m *Meta) BackendForLocalPlan(settings plans.Backend) (backendrun.OperationsBackend, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics

// Check the workspace name in the plan matches the current workspace
w, err := m.Workspace()
if err != nil {
diags = diags.Append(fmt.Errorf("error determining current workspace when initializing a backend from the plan file: %w", err))
return nil, diags
}
if w != settings.Workspace {
diags = diags.Append(&errWrongWorkspaceForPlan{
currentWorkspace: w,
plannedWorkspace: settings.Workspace,
})
return nil, diags
}

// Proceed with initializing the backend from the configuration in the plan file
f := backendInit.Backend(settings.Type)
if f == nil {
diags = diags.Append(errBackendSavedUnknown{settings.Type})
Expand Down
21 changes: 21 additions & 0 deletions internal/command/meta_backend_errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,27 @@ import (
"github.com/hashicorp/terraform/internal/tfdiags"
)

// errWrongWorkspaceForPlan is a custom error used to alert users that the plan file they are applying
// describes a workspace that doesn't match the currently selected workspace.
type errWrongWorkspaceForPlan struct {
plannedWorkspace string
currentWorkspace string
}

func (e *errWrongWorkspaceForPlan) Error() string {
return fmt.Sprintf(`The plan file describes changes to the %q workspace, but the %q workspace is currently selected in the working directory.

Applying this plan with the incorrect workspace selected could result in state being stored in an unexpected location, or a downstream error
when Terraform attempts apply a plan using the other workspace's state.

If you'd like to continue to use the plan file, you must run "terraform workspace select %s" to select the correct workspace.
In future make sure the selected workspace is not changed between creating and applying a plan file.`,
e.plannedWorkspace,
e.currentWorkspace,
e.plannedWorkspace,
)
}

// errBackendLocalRead is a custom error used to alert users that state
// files on their local filesystem were not erased successfully after
// migrating that state to a remote-state backend.
Expand Down
46 changes: 46 additions & 0 deletions internal/command/meta_backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1837,6 +1837,52 @@ func TestMetaBackend_planLocalMatch(t *testing.T) {
}
}

// A plan that contains a workspace that isn't the currently selected workspace
func TestMetaBackend_planLocal_mismatchedWorkspace(t *testing.T) {
// Create a temporary working directory that is empty
td := t.TempDir()
testCopyDir(t, testFixturePath("backend-plan-local"), td)
t.Chdir(td)

backendConfigBlock := cty.ObjectVal(map[string]cty.Value{
"path": cty.NullVal(cty.String),
"workspace_dir": cty.NullVal(cty.String),
})
backendConfigRaw, err := plans.NewDynamicValue(backendConfigBlock, backendConfigBlock.Type())
if err != nil {
t.Fatal(err)
}
defaultWorkspace := "default"
backendConfig := plans.Backend{
Type: "local",
Config: backendConfigRaw,
Workspace: defaultWorkspace,
}

// Setup the meta
m := testMetaBackend(t, nil)
selectedWorkspace := "foobar"
err = m.SetWorkspace(selectedWorkspace)
if err != nil {
t.Fatalf("error in test setup: %s", err)
}

// Get the backend
_, diags := m.BackendForLocalPlan(backendConfig)
if !diags.HasErrors() {
t.Fatalf("expected an error but got none: %s", diags.ErrWithWarnings())
}
expectedMsg := fmt.Sprintf("The plan file describes changes to the %q workspace, but the %q workspace is currently selected in the working directory",
defaultWorkspace,
selectedWorkspace,
)
if !strings.Contains(diags.Err().Error(), expectedMsg) {
t.Fatalf("expected error to include %q, but got:\n%s",
expectedMsg,
diags.Err())
}
}

// init a backend using -backend-config options multiple times
func TestMetaBackend_configureBackendWithExtra(t *testing.T) {
// Create a temporary working directory that is empty
Expand Down
Loading