stacks: The terraform.workspace attr is not available in Stacks

The terraform.workspace attribute is a rare example of a CLI- and Cloud-
specific concern bleeding into the Terraform language, and it can only
really have meaning when used in the traditional Terraform workflow
because otherwise there's no workspace to return the name of.

In Stacks any variations between instances of a module must be created
through input variables. Within Terraform Cloud in particular it's also
possible to use stack-level input variables that are assigned different
values from different stack deployments, and thus an author can recreate
the effect of terraform.workspace using a stack-level input variable that
has a different value for each deployment.

This is one of the few cases where the Terraform module language differs
in stacks compared to traditional Terraform. Any module that makes use of
terraform.workspace will need to be generalized to use input variables
instead before it can be used within a stack component.

Prior to this change, references to terraform.workspace from a module used
in a stack component would just panic altogether, because the stacks
runtime doesn't provide the object that the workspace name would be taken
from. Now we'll return a user-oriented error instead.
RK/fix-nav-upgrade-name
Martin Atkins 2 years ago
parent 9851cd456e
commit 0994e6fbf9

@ -6,8 +6,10 @@ package stackeval
import (
"context"
"fmt"
"strings"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/google/go-cmp/cmp"
"github.com/zclconf/go-cty/cty"
"google.golang.org/protobuf/reflect/protoreflect"
@ -23,6 +25,7 @@ import (
"github.com/hashicorp/terraform/internal/stacks/stackstate/statekeys"
"github.com/hashicorp/terraform/internal/stacks/tfstackdata1"
"github.com/hashicorp/terraform/internal/terraform"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestPlanning_DestroyMode(t *testing.T) {
@ -577,3 +580,44 @@ func TestPlanning_RequiredComponents(t *testing.T) {
}
})
}
func TestPlanning_NoWorkspaceNameRef(t *testing.T) {
// This test verifies that a reference to terraform.workspace is treated
// as invalid for modules used in a stacks context, because there's
// no comparable single string to use in stacks context and we expect
// modules used in stack components to vary declarations based only
// on their input variables.
//
// (If something needs to vary between stack deployments then that's
// a good candidate for an input variable on the root stack configuration,
// set differently for each deployment, and then passed in to the
// components that need it.)
cfg := testStackConfig(t, "planning", "no_workspace_name_ref")
main := NewForPlanning(cfg, stackstate.NewState(), PlanOpts{
PlanningMode: plans.NormalMode,
})
inPromisingTask(t, func(ctx context.Context, t *testing.T) {
_, diags := testPlan(t, main)
if !diags.HasErrors() {
t.Fatal("success; want error about invalid terraform.workspace reference")
}
// At least one of the diagnostics must mention the terraform.workspace
// attribute in its detail.
seenRelevantDiag := false
for _, diag := range diags {
if diag.Severity() != tfdiags.Error {
continue
}
if strings.Contains(diag.Description().Detail, "terraform.workspace") {
seenRelevantDiag = true
break
}
}
if !seenRelevantDiag {
t.Fatalf("none of the error diagnostics mentions terraform.workspace\n%s", spew.Sdump(diags.ForRPC()))
}
})
}

@ -0,0 +1,6 @@
output "invalid" {
# terraform.workspace is not available when this module is used as part
# of a stack component, so this should produce an error during
# planning.
value = terraform.workspace
}

@ -811,6 +811,37 @@ func (d *evaluationStateData) getResourceSchema(addr addrs.Resource, providerAdd
func (d *evaluationStateData) GetTerraformAttr(addr addrs.TerraformAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
if d.Evaluator.Meta == nil || d.Evaluator.Meta.Env == "" {
// The absense of an "env" (really: workspace) name suggests that
// we're running in a non-workspace context, such as in a component
// of a stack. terraform.workspace -- and the terraform symbol in
// general -- is a legacy thing from workspaces mode that isn't
// carried forward to stacks, because stack configurations can instead
// vary their behavior based on input variables provided in the
// deployment configuration.
switch addr.Name {
case "workspace":
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: `Invalid reference`,
Detail: `The terraform.workspace attribute is only available for modules used in Terraform workspaces. Use input variables instead to create variations between different instances of this module.`,
Subject: rng.ToHCL().Ptr(),
})
default:
// A more generic error for any other attribute name, since no
// others are valid anyway but it would be confusing to mention
// terraform.workspace here.
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: `Invalid reference`,
Detail: `The "terraform" object is only available for modules used in Terraform workspaces.`,
Subject: rng.ToHCL().Ptr(),
})
}
return cty.DynamicVal, diags
}
switch addr.Name {
case "workspace":

Loading…
Cancel
Save