opentofu/internal/configs/static_evaluator.go
Christian Mesh 0d1e6cd5f0
Handle static variable secret flag (#2045)
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
2024-10-03 10:46:58 -04:00

141 lines
4.1 KiB
Go

// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package configs
import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/lang"
"github.com/opentofu/opentofu/internal/lang/marks"
"github.com/zclconf/go-cty/cty"
)
// StaticIdentifier holds a Referenceable item and where it was declared
type StaticIdentifier struct {
Module addrs.Module
Subject string
DeclRange hcl.Range
}
func (ref StaticIdentifier) String() string {
val := ref.Subject
if len(ref.Module) != 0 {
val = ref.Module.String() + ":" + val
}
return val
}
type StaticModuleVariables func(v *Variable) (cty.Value, hcl.Diagnostics)
// StaticModuleCall contains the information required to call a given module
type StaticModuleCall struct {
addr addrs.Module
vars StaticModuleVariables
rootPath string
workspace string
}
func NewStaticModuleCall(addr addrs.Module, vars StaticModuleVariables, rootPath string, workspace string) StaticModuleCall {
return StaticModuleCall{
addr: addr,
vars: vars,
rootPath: rootPath,
workspace: workspace,
}
}
func (s StaticModuleCall) WithVariables(vars StaticModuleVariables) StaticModuleCall {
return StaticModuleCall{
addr: s.addr,
vars: vars,
rootPath: s.rootPath,
workspace: s.workspace,
}
}
// only used in testing
func RootModuleCallForTesting() StaticModuleCall {
return NewStaticModuleCall(addrs.RootModule, func(_ *Variable) (cty.Value, hcl.Diagnostics) {
panic("Variables have not been configured for this test!")
}, "<testing>", "")
}
// A static evaluator contains the information required to build a EvalContext
// which only understands "static" (non-state) data. Internally, it relies
// on staticData
type StaticEvaluator struct {
call StaticModuleCall
cfg *Module
}
// Creates a static evaluator based from the given module and module call
func NewStaticEvaluator(mod *Module, call StaticModuleCall) *StaticEvaluator {
return &StaticEvaluator{
call: call,
cfg: mod,
}
}
func (s *StaticEvaluator) scope(ident StaticIdentifier) *lang.Scope {
return newStaticScope(s, ident)
}
func (s StaticEvaluator) Evaluate(expr hcl.Expression, ident StaticIdentifier) (cty.Value, hcl.Diagnostics) {
val, diags := s.scope(ident).EvalExpr(expr, cty.DynamicPseudoType)
return val, diags.ToHCL()
}
func (s StaticEvaluator) DecodeExpression(expr hcl.Expression, ident StaticIdentifier, val any) hcl.Diagnostics {
srcVal, diags := s.Evaluate(expr, ident)
if diags.HasErrors() {
return diags
}
if marks.Contains(srcVal, marks.Sensitive) {
return diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Sensitive value not allowed",
Detail: fmt.Sprintf("Sensitive values, or values derived from sensitive values, cannot be used as %s.", ident.String()),
Subject: expr.Range().Ptr(),
})
}
return diags.Extend(gohcl.DecodeValue(srcVal, expr.StartRange(), expr.Range(), val))
}
func (s StaticEvaluator) DecodeBlock(body hcl.Body, spec hcldec.Spec, ident StaticIdentifier) (cty.Value, hcl.Diagnostics) {
var diags hcl.Diagnostics
refs, refsDiags := lang.References(addrs.ParseRef, hcldec.Variables(body, spec))
diags = append(diags, refsDiags.ToHCL()...)
if diags.HasErrors() {
return cty.DynamicVal, diags
}
ctx, ctxDiags := s.scope(ident).EvalContext(refs)
diags = append(diags, ctxDiags.ToHCL()...)
if diags.HasErrors() {
return cty.DynamicVal, diags
}
val, valDiags := hcldec.Decode(body, spec, ctx)
diags = append(diags, valDiags...)
return val, diags
}
func (s StaticEvaluator) EvalContext(ident StaticIdentifier, refs []*addrs.Reference) (*hcl.EvalContext, hcl.Diagnostics) {
return s.EvalContextWithParent(nil, ident, refs)
}
func (s StaticEvaluator) EvalContextWithParent(parent *hcl.EvalContext, ident StaticIdentifier, refs []*addrs.Reference) (*hcl.EvalContext, hcl.Diagnostics) {
evalCtx, diags := s.scope(ident).EvalContextWithParent(parent, refs)
return evalCtx, diags.ToHCL()
}