Skip to content

Commit

Permalink
ext/dynblock: Allow callers to veto for_each values
Browse files Browse the repository at this point in the history
Callers might have additional rules for what's acceptable in a for_each
value for a dynamic block. For example, Terraform wants to forbid using
sensitive values here because it would cause the expansion to disclose the
length of the given collection.

Therefore this provides a hook point for callers to insert additional
checks just after the for_each expression has been evaluated and before
any of the built-in checks are run.

This introduces the "functional options" pattern for ExpandBlock for the
first time, as a way to extend the API without breaking compatibility with
existing callers. There is currently only this one option.
  • Loading branch information
apparentlymart committed Oct 12, 2023
1 parent 4945193 commit 56f45ea
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 16 deletions.
4 changes: 4 additions & 0 deletions ext/dynblock/expand_body.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ type expandBody struct {
forEachCtx *hcl.EvalContext
iteration *iteration // non-nil if we're nested inside another "dynamic" block

checkForEach []func(cty.Value, hcl.Expression, *hcl.EvalContext) hcl.Diagnostics

// These are used with PartialContent to produce a "remaining items"
// body to return. They are nil on all bodies fresh out of the transformer.
//
Expand Down Expand Up @@ -66,6 +68,7 @@ func (b *expandBody) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, h
original: b.original,
forEachCtx: b.forEachCtx,
iteration: b.iteration,
checkForEach: b.checkForEach,
hiddenAttrs: make(map[string]struct{}),
hiddenBlocks: make(map[string]hcl.BlockHeaderSchema),
}
Expand Down Expand Up @@ -236,6 +239,7 @@ func (b *expandBody) expandChild(child hcl.Body, i *iteration) hcl.Body {
chiCtx := i.EvalContext(b.forEachCtx)
ret := Expand(child, chiCtx)
ret.(*expandBody).iteration = i
ret.(*expandBody).checkForEach = b.checkForEach
return ret
}

Expand Down
10 changes: 10 additions & 0 deletions ext/dynblock/expand_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ func (b *expandBody) decodeSpec(blockS *hcl.BlockHeaderSchema, rawSpec *hcl.Bloc
eachAttr := specContent.Attributes["for_each"]
eachVal, eachDiags := eachAttr.Expr.Value(b.forEachCtx)
diags = append(diags, eachDiags...)
if diags.HasErrors() {
return nil, diags
}
for _, check := range b.checkForEach {
moreDiags := check(eachVal, eachAttr.Expr, b.forEachCtx)
diags = append(diags, moreDiags...)
if moreDiags.HasErrors() {
return nil, diags
}
}

if !eachVal.CanIterateElements() && eachVal.Type() != cty.DynamicPseudoType {
// We skip this error for DynamicPseudoType because that means we either
Expand Down
23 changes: 23 additions & 0 deletions ext/dynblock/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package dynblock

import (
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
)

type ExpandOption interface {
applyExpandOption(*expandBody)
}

type optCheckForEach struct {
check func(cty.Value, hcl.Expression, *hcl.EvalContext) hcl.Diagnostics
}

func OptCheckForEach(check func(cty.Value, hcl.Expression, *hcl.EvalContext) hcl.Diagnostics) ExpandOption {
return optCheckForEach{check}
}

// applyExpandOption implements ExpandOption.
func (o optCheckForEach) applyExpandOption(body *expandBody) {
body.checkForEach = append(body.checkForEach, o.check)
}
32 changes: 16 additions & 16 deletions ext/dynblock/public.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,22 @@ import (
// multi-dimensional iteration. However, it is not possible to
// dynamically-generate the "dynamic" blocks themselves except through nesting.
//
// parent {
// dynamic "child" {
// for_each = child_objs
// content {
// dynamic "grandchild" {
// for_each = child.value.children
// labels = [grandchild.key]
// content {
// parent_key = child.key
// value = grandchild.value
// }
// }
// }
// }
// }
func Expand(body hcl.Body, ctx *hcl.EvalContext) hcl.Body {
// parent {
// dynamic "child" {
// for_each = child_objs
// content {
// dynamic "grandchild" {
// for_each = child.value.children
// labels = [grandchild.key]
// content {
// parent_key = child.key
// value = grandchild.value
// }
// }
// }
// }
// }
func Expand(body hcl.Body, ctx *hcl.EvalContext, opts ...ExpandOption) hcl.Body {
return &expandBody{
original: body,
forEachCtx: ctx,
Expand Down

0 comments on commit 56f45ea

Please sign in to comment.