Skip to content

Commit 97c921a

Browse files
committed
fix: Fail apply command if the plan file was generated for a workspace that isn't the selected workspace.
1 parent f1bb033 commit 97c921a

File tree

3 files changed

+82
-0
lines changed

3 files changed

+82
-0
lines changed

internal/command/meta_backend.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,21 @@ func (m *Meta) selectWorkspace(b backend.Backend) error {
333333
func (m *Meta) BackendForLocalPlan(settings plans.Backend) (backendrun.OperationsBackend, tfdiags.Diagnostics) {
334334
var diags tfdiags.Diagnostics
335335

336+
// Check the workspace name in the plan matches the current workspace
337+
w, err := m.Workspace()
338+
if err != nil {
339+
diags = diags.Append(fmt.Errorf("error determining current workspace when initializing a backend from the plan file: %w", err))
340+
return nil, diags
341+
}
342+
if w != settings.Workspace {
343+
diags = diags.Append(&errWrongWorkspaceForPlan{
344+
currentWorkspace: w,
345+
plannedWorkspace: settings.Workspace,
346+
})
347+
return nil, diags
348+
}
349+
350+
// Proceed with initializing the backend from the configuration in the plan file
336351
f := backendInit.Backend(settings.Type)
337352
if f == nil {
338353
diags = diags.Append(errBackendSavedUnknown{settings.Type})

internal/command/meta_backend_errors.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,27 @@ import (
99
"github.com/hashicorp/terraform/internal/tfdiags"
1010
)
1111

12+
// errWrongWorkspaceForPlan is a custom error used to alert users that the plan file they are applying
13+
// describes a workspace that doesn't match the currently selected workspace.
14+
type errWrongWorkspaceForPlan struct {
15+
plannedWorkspace string
16+
currentWorkspace string
17+
}
18+
19+
func (e *errWrongWorkspaceForPlan) Error() string {
20+
return fmt.Sprintf(`The plan file describes changes to the %q workspace, but the %q workspace is currently selected in the working directory.
21+
22+
Applying this plan with the incorrect workspace selected could result in state being stored in an unexpected location, or a downstream error
23+
when Terraform attempts apply a plan using the other workspace's state.
24+
25+
If you'd like to continue to use the plan file, you must run "terraform workspace select %s" to select the correct workspace.
26+
In future make sure the selected workspace is not changed between creating and applying a plan file.`,
27+
e.plannedWorkspace,
28+
e.currentWorkspace,
29+
e.plannedWorkspace,
30+
)
31+
}
32+
1233
// errBackendLocalRead is a custom error used to alert users that state
1334
// files on their local filesystem were not erased successfully after
1435
// migrating that state to a remote-state backend.

internal/command/meta_backend_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1837,6 +1837,52 @@ func TestMetaBackend_planLocalMatch(t *testing.T) {
18371837
}
18381838
}
18391839

1840+
// A plan that contains a workspace that isn't the currently selected workspace
1841+
func TestMetaBackend_planLocal_mismatchedWorkspace(t *testing.T) {
1842+
// Create a temporary working directory that is empty
1843+
td := t.TempDir()
1844+
testCopyDir(t, testFixturePath("backend-plan-local"), td)
1845+
t.Chdir(td)
1846+
1847+
backendConfigBlock := cty.ObjectVal(map[string]cty.Value{
1848+
"path": cty.NullVal(cty.String),
1849+
"workspace_dir": cty.NullVal(cty.String),
1850+
})
1851+
backendConfigRaw, err := plans.NewDynamicValue(backendConfigBlock, backendConfigBlock.Type())
1852+
if err != nil {
1853+
t.Fatal(err)
1854+
}
1855+
defaultWorkspace := "default"
1856+
backendConfig := plans.Backend{
1857+
Type: "local",
1858+
Config: backendConfigRaw,
1859+
Workspace: defaultWorkspace,
1860+
}
1861+
1862+
// Setup the meta
1863+
m := testMetaBackend(t, nil)
1864+
selectedWorkspace := "foobar"
1865+
err = m.SetWorkspace(selectedWorkspace)
1866+
if err != nil {
1867+
t.Fatalf("error in test setup: %s", err)
1868+
}
1869+
1870+
// Get the backend
1871+
_, diags := m.BackendForLocalPlan(backendConfig)
1872+
if !diags.HasErrors() {
1873+
t.Fatalf("expected an error but got none: %s", diags.ErrWithWarnings())
1874+
}
1875+
expectedMsg := fmt.Sprintf("The plan file describes changes to the %q workspace, but the %q workspace is currently selected in the working directory",
1876+
defaultWorkspace,
1877+
selectedWorkspace,
1878+
)
1879+
if !strings.Contains(diags.Err().Error(), expectedMsg) {
1880+
t.Fatalf("expected error to include %q, but got:\n%s",
1881+
expectedMsg,
1882+
diags.Err())
1883+
}
1884+
}
1885+
18401886
// init a backend using -backend-config options multiple times
18411887
func TestMetaBackend_configureBackendWithExtra(t *testing.T) {
18421888
// Create a temporary working directory that is empty

0 commit comments

Comments
 (0)