Skip to content

Commit

Permalink
stackeval: terraform.applying symbol
Browse files Browse the repository at this point in the history
This symbol produces an ephemeral boolean value that's true when the
stack evaluator is configured for applying and false otherwise.

The primary purpose of this is to allow specifying a more privileged auth
role in a provider configuration during the apply phase, while allowing
the plan phase to use a read-only or otherwise-less-privileged role.
  • Loading branch information
apparentlymart committed Jun 17, 2024
1 parent e74896b commit 1714729
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 0 deletions.
21 changes: 21 additions & 0 deletions internal/stacks/stackaddrs/reference.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,27 @@ func ParseReference(traversal hcl.Traversal) (Reference, hcl.Traversal, tfdiags.
ret.SourceRange = tfdiags.SourceRangeFromHCL(traversal[0].SourceRange())
return ret, traversal[1:], diags

case "terraform":
attrName, rng, remain, diags := parseSingleAttrRef(traversal)
if diags.HasErrors() {
return ret, nil, diags
}
ret.SourceRange = tfdiags.SourceRangeFromHCL(rng)

switch attrName {
case "applying":
ret.Target = TerraformApplying
return ret, remain, diags
default:
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Reference to unknown symbol",
Detail: fmt.Sprintf("The object %q has no attribute named %q.", rootName, attrName),
Subject: traversal[1].SourceRange().Ptr(),
})
return ret, remain, diags
}

case "_test_only_global":
name, rng, remain, diags := parseSingleAttrRef(traversal)
ret.Target = TestOnlyGlobal{Name: name}
Expand Down
3 changes: 3 additions & 0 deletions internal/stacks/stackaddrs/referenceable.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const EachValue = ContextualRef('v')
const EachKey = ContextualRef('k')
const CountIndex = ContextualRef('i')
const Self = ContextualRef('s')
const TerraformApplying = ContextualRef('a')

// String implements Referenceable.
func (e ContextualRef) String() string {
Expand All @@ -65,6 +66,8 @@ func (e ContextualRef) String() string {
return "count.index"
case Self:
return "self"
case TerraformApplying:
return "terraform.applying"
default:
// The four constants in this package are the only valid values of this type
panic("invalid ContextualRef instance")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func evalContextForTraversals(ctx context.Context, traversals []hcl.Traversal, p
providerVals := make(map[string]map[string]cty.Value)
eachVals := make(map[string]cty.Value)
countVals := make(map[string]cty.Value)
terraformVals := make(map[string]cty.Value)
var selfVal cty.Value
var testOnlyGlobals map[string]cty.Value // allocated only when needed (see below)

Expand Down Expand Up @@ -147,6 +148,8 @@ func evalContextForTraversals(ctx context.Context, traversals []hcl.Traversal, p
countVals["index"] = val
case stackaddrs.Self:
selfVal = val
case stackaddrs.TerraformApplying:
terraformVals["applying"] = val
default:
// The above should be exhaustive for all values of this enumeration
panic(fmt.Sprintf("unsupported ContextualRef %#v", addr))
Expand Down Expand Up @@ -200,6 +203,9 @@ func evalContextForTraversals(ctx context.Context, traversals []hcl.Traversal, p
if len(countVals) != 0 {
hclCtx.Variables["count"] = cty.ObjectVal(countVals)
}
if len(terraformVals) != 0 {
hclCtx.Variables["terraform"] = cty.ObjectVal(terraformVals)
}
if selfVal != cty.NilVal {
hclCtx.Variables["self"] = selfVal
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func TestEvalExpr(t *testing.T) {
hcltest.MockExprTraversalSrc(`stack.multi["bar"]`),
hcltest.MockExprTraversalSrc("provider.beep.boop"),
hcltest.MockExprTraversalSrc(`provider.beep.boops["baz"]`),
hcltest.MockExprTraversalSrc(`terraform.applying`),
})

scope := newStaticExpressionScope()
Expand All @@ -96,6 +97,7 @@ func TestEvalExpr(t *testing.T) {
scope.AddVal(stackaddrs.ProviderConfigRef{ProviderLocalName: "beep", Name: "boops"}, cty.ObjectVal(map[string]cty.Value{
"baz": cty.StringVal("provider config from for_each"),
}))
scope.AddVal(stackaddrs.TerraformApplying, cty.StringVal("terraform.applying value")) // NOTE: Not a realistic terraform.applying value; just a placeholder to help exercise EvalExpr

got, diags := EvalExpr(ctx, expr, PlanPhase, scope)
if diags.HasErrors() {
Expand All @@ -110,6 +112,7 @@ func TestEvalExpr(t *testing.T) {
cty.StringVal("stack call from for_each"),
cty.StringVal("provider config singleton"),
cty.StringVal("provider config from for_each"),
cty.StringVal("terraform.applying value"),
})
if diff := cmp.Diff(want, got, ctydebug.CmpOptions); diff != "" {
t.Errorf("wrong result\n%s", diff)
Expand Down Expand Up @@ -178,6 +181,12 @@ func TestReferencesInExpr(t *testing.T) {
},
},
},
{
`terraform.applying`,
[]stackaddrs.Referenceable{
stackaddrs.TerraformApplying,
},
},
}

for _, test := range tests {
Expand Down
2 changes: 2 additions & 0 deletions internal/stacks/stackruntime/internal/stackeval/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,8 @@ func (s *Stack) resolveExpressionReference(ctx context.Context, ref stackaddrs.R
})
return nil, diags
}
case stackaddrs.TerraformApplying:
return JustValue{cty.BoolVal(s.main.Applying()).Mark(marks.Ephemeral)}, diags
default:
// The above should be exhaustive for all defined values of this type.
panic(fmt.Sprintf("unsupported ContextualRef %#v", addr))
Expand Down

0 comments on commit 1714729

Please sign in to comment.