opentofu/internal/tofu/evaluate_triggers.go

149 lines
4.5 KiB
Go
Raw Normal View History

// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
2023-09-20 07:16:53 -05:00
package tofu
import (
"strings"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/instances"
"github.com/opentofu/opentofu/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
)
func evalReplaceTriggeredByExpr(expr hcl.Expression, keyData instances.RepetitionData) (*addrs.Reference, tfdiags.Diagnostics) {
var ref *addrs.Reference
var diags tfdiags.Diagnostics
traversal, diags := triggersExprToTraversal(expr, keyData)
if diags.HasErrors() {
return nil, diags
}
// We now have a static traversal, so we can just turn it into an addrs.Reference.
ref, ds := addrs.ParseRef(traversal)
diags = diags.Append(ds)
return ref, diags
}
// trggersExprToTraversal takes an hcl expression limited to the syntax allowed
// in replace_triggered_by, and converts it to a static traversal. The
// RepetitionData contains the data necessary to evaluate the only allowed
// variables in the expression, count.index and each.key.
func triggersExprToTraversal(expr hcl.Expression, keyData instances.RepetitionData) (hcl.Traversal, tfdiags.Diagnostics) {
var trav hcl.Traversal
var diags tfdiags.Diagnostics
switch e := expr.(type) {
case *hclsyntax.RelativeTraversalExpr:
t, d := triggersExprToTraversal(e.Source, keyData)
diags = diags.Append(d)
trav = append(trav, t...)
trav = append(trav, e.Traversal...)
case *hclsyntax.ScopeTraversalExpr:
// a static reference, we can just append the traversal
trav = append(trav, e.Traversal...)
case *hclsyntax.IndexExpr:
// Get the collection from the index expression
t, d := triggersExprToTraversal(e.Collection, keyData)
diags = diags.Append(d)
if diags.HasErrors() {
return nil, diags
}
trav = append(trav, t...)
// The index key is the only place where we could have variables that
// reference count and each, so we need to parse those independently.
idx, hclDiags := parseIndexKeyExpr(e.Key, keyData)
diags = diags.Append(hclDiags)
trav = append(trav, idx)
default:
// Something unexpected got through config validation. We're not sure
// what it is, but we'll point it out in the diagnostics for the user
// to fix.
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid replace_triggered_by expression",
Detail: "Unexpected expression found in replace_triggered_by.",
Subject: e.Range().Ptr(),
})
}
return trav, diags
}
// parseIndexKeyExpr takes an hcl.Expression and parses it as an index key, while
// evaluating any references to count.index or each.key.
func parseIndexKeyExpr(expr hcl.Expression, keyData instances.RepetitionData) (hcl.TraverseIndex, hcl.Diagnostics) {
idx := hcl.TraverseIndex{
SrcRange: expr.Range(),
}
trav, diags := hcl.RelTraversalForExpr(expr)
if diags.HasErrors() {
return idx, diags
}
keyParts := []string{}
for _, t := range trav {
attr, ok := t.(hcl.TraverseAttr)
if !ok {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid index expression",
Detail: "Only constant values, count.index or each.key are allowed in index expressions.",
Subject: expr.Range().Ptr(),
})
return idx, diags
}
keyParts = append(keyParts, attr.Name)
}
switch strings.Join(keyParts, ".") {
case "count.index":
if keyData.CountIndex == cty.NilVal {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: `Reference to "count" in non-counted context`,
Detail: `The "count" object can only be used in "resource" blocks when the "count" argument is set.`,
Subject: expr.Range().Ptr(),
})
}
idx.Key = keyData.CountIndex
case "each.key":
if keyData.EachKey == cty.NilVal {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: `Reference to "each" in context without for_each`,
Detail: `The "each" object can be used only in "resource" blocks when the "for_each" argument is set.`,
Subject: expr.Range().Ptr(),
})
}
idx.Key = keyData.EachKey
default:
// Something may have slipped through validation, probably from a json
// configuration.
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid index expression",
Detail: "Only constant values, count.index or each.key are allowed in index expressions.",
Subject: expr.Range().Ptr(),
})
}
return idx, diags
}