mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-23 23:50:12 -06:00
refactor Scope.evalContext to meet linting criteria (#1750)
Signed-off-by: ollevche <ollevche@gmail.com>
This commit is contained in:
parent
e2b6b46d43
commit
374c9eedd3
@ -7,6 +7,7 @@ package lang
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
@ -300,7 +301,6 @@ func (s *Scope) EvalContextWithParent(p *hcl.EvalContext, refs []*addrs.Referenc
|
||||
return s.evalContext(p, refs, s.SelfAddr)
|
||||
}
|
||||
|
||||
//nolint:funlen,gocyclo,cyclop // TODO: refactor this function to match linting requirements
|
||||
func (s *Scope) evalContext(parent *hcl.EvalContext, refs []*addrs.Reference, selfAddr addrs.Referenceable) (*hcl.EvalContext, tfdiags.Diagnostics) {
|
||||
if s == nil {
|
||||
panic("attempt to construct EvalContext for nil Scope")
|
||||
@ -308,21 +308,18 @@ func (s *Scope) evalContext(parent *hcl.EvalContext, refs []*addrs.Reference, se
|
||||
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
vals := make(map[string]cty.Value)
|
||||
funcs := make(map[string]function.Function)
|
||||
|
||||
// Calling NewChild() on a nil parent will
|
||||
// produce an EvalContext with no parent.
|
||||
ctx := parent.NewChild()
|
||||
ctx.Variables = vals
|
||||
ctx.Functions = funcs
|
||||
ctx.Functions = make(map[string]function.Function)
|
||||
ctx.Variables = make(map[string]cty.Value)
|
||||
|
||||
for name, fn := range s.Functions() {
|
||||
funcs[name] = fn
|
||||
ctx.Functions[name] = fn
|
||||
}
|
||||
|
||||
// Easy path for common case where there are no references at all.
|
||||
if len(refs) == 0 {
|
||||
// Easy path for common case where there are no references at all.
|
||||
return ctx, diags
|
||||
}
|
||||
|
||||
@ -343,196 +340,239 @@ func (s *Scope) evalContext(parent *hcl.EvalContext, refs []*addrs.Reference, se
|
||||
// it, since that allows us to gather a full set of any errors and
|
||||
// warnings, but once we've gathered all the data we'll then skip anything
|
||||
// that's redundant in the process of populating our values map.
|
||||
dataResources := map[string]map[string]cty.Value{}
|
||||
managedResources := map[string]map[string]cty.Value{}
|
||||
wholeModules := map[string]cty.Value{}
|
||||
inputVariables := map[string]cty.Value{}
|
||||
localValues := map[string]cty.Value{}
|
||||
outputValues := map[string]cty.Value{}
|
||||
pathAttrs := map[string]cty.Value{}
|
||||
terraformAttrs := map[string]cty.Value{}
|
||||
countAttrs := map[string]cty.Value{}
|
||||
forEachAttrs := map[string]cty.Value{}
|
||||
checkBlocks := map[string]cty.Value{}
|
||||
var self cty.Value
|
||||
varBuilder := s.newEvalVarBuilder()
|
||||
|
||||
for _, ref := range refs {
|
||||
rng := ref.SourceRange
|
||||
|
||||
rawSubj := ref.Subject
|
||||
if rawSubj == addrs.Self {
|
||||
if selfAddr == nil {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: `Invalid "self" reference`,
|
||||
// This detail message mentions some current practice that
|
||||
// this codepath doesn't really "know about". If the "self"
|
||||
// object starts being supported in more contexts later then
|
||||
// we'll need to adjust this message.
|
||||
Detail: `The "self" object is not available in this context. This object can be used only in resource provisioner, connection, and postcondition blocks.`,
|
||||
Subject: ref.SourceRange.ToHCL().Ptr(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if selfAddr == addrs.Self {
|
||||
// Programming error: the self address cannot alias itself.
|
||||
panic("scope SelfAddr attempting to alias itself")
|
||||
}
|
||||
|
||||
// self can only be used within a resource instance
|
||||
subj := selfAddr.(addrs.ResourceInstance)
|
||||
|
||||
val, valDiags := normalizeRefValue(s.Data.GetResource(subj.ContainingResource(), rng))
|
||||
|
||||
diags = diags.Append(valDiags)
|
||||
|
||||
// Self is an exception in that it must always resolve to a
|
||||
// particular instance. We will still insert the full resource into
|
||||
// the context below.
|
||||
var hclDiags hcl.Diagnostics
|
||||
// We should always have a valid self index by this point, but in
|
||||
// the case of an error, self may end up as a cty.DynamicValue.
|
||||
switch k := subj.Key.(type) {
|
||||
case addrs.IntKey:
|
||||
self, hclDiags = hcl.Index(val, cty.NumberIntVal(int64(k)), ref.SourceRange.ToHCL().Ptr())
|
||||
diags = diags.Append(hclDiags)
|
||||
case addrs.StringKey:
|
||||
self, hclDiags = hcl.Index(val, cty.StringVal(string(k)), ref.SourceRange.ToHCL().Ptr())
|
||||
diags = diags.Append(hclDiags)
|
||||
default:
|
||||
self = val
|
||||
}
|
||||
if ref.Subject == addrs.Self {
|
||||
diags.Append(varBuilder.putSelfValue(selfAddr, ref))
|
||||
continue
|
||||
}
|
||||
|
||||
// This type switch must cover all of the "Referenceable" implementations
|
||||
// in package addrs, however we are removing the possibility of
|
||||
// Instances beforehand.
|
||||
switch addr := rawSubj.(type) {
|
||||
case addrs.ResourceInstance:
|
||||
rawSubj = addr.ContainingResource()
|
||||
case addrs.ModuleCallInstance:
|
||||
rawSubj = addr.Call
|
||||
case addrs.ModuleCallInstanceOutput:
|
||||
rawSubj = addr.Call.Call
|
||||
}
|
||||
|
||||
switch subj := rawSubj.(type) {
|
||||
case addrs.Resource:
|
||||
var into map[string]map[string]cty.Value
|
||||
switch subj.Mode {
|
||||
case addrs.ManagedResourceMode:
|
||||
into = managedResources
|
||||
case addrs.DataResourceMode:
|
||||
into = dataResources
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported ResourceMode %s", subj.Mode))
|
||||
}
|
||||
|
||||
val, valDiags := normalizeRefValue(s.Data.GetResource(subj, rng))
|
||||
diags = diags.Append(valDiags)
|
||||
|
||||
r := subj
|
||||
if into[r.Type] == nil {
|
||||
into[r.Type] = make(map[string]cty.Value)
|
||||
}
|
||||
into[r.Type][r.Name] = val
|
||||
|
||||
case addrs.ModuleCall:
|
||||
val, valDiags := normalizeRefValue(s.Data.GetModule(subj, rng))
|
||||
diags = diags.Append(valDiags)
|
||||
wholeModules[subj.Name] = val
|
||||
|
||||
case addrs.InputVariable:
|
||||
val, valDiags := normalizeRefValue(s.Data.GetInputVariable(subj, rng))
|
||||
diags = diags.Append(valDiags)
|
||||
inputVariables[subj.Name] = val
|
||||
|
||||
case addrs.LocalValue:
|
||||
val, valDiags := normalizeRefValue(s.Data.GetLocalValue(subj, rng))
|
||||
diags = diags.Append(valDiags)
|
||||
localValues[subj.Name] = val
|
||||
|
||||
case addrs.PathAttr:
|
||||
val, valDiags := normalizeRefValue(s.Data.GetPathAttr(subj, rng))
|
||||
diags = diags.Append(valDiags)
|
||||
pathAttrs[subj.Name] = val
|
||||
|
||||
case addrs.TerraformAttr:
|
||||
val, valDiags := normalizeRefValue(s.Data.GetTerraformAttr(subj, rng))
|
||||
diags = diags.Append(valDiags)
|
||||
terraformAttrs[subj.Name] = val
|
||||
|
||||
case addrs.CountAttr:
|
||||
val, valDiags := normalizeRefValue(s.Data.GetCountAttr(subj, rng))
|
||||
diags = diags.Append(valDiags)
|
||||
countAttrs[subj.Name] = val
|
||||
|
||||
case addrs.ForEachAttr:
|
||||
val, valDiags := normalizeRefValue(s.Data.GetForEachAttr(subj, rng))
|
||||
diags = diags.Append(valDiags)
|
||||
forEachAttrs[subj.Name] = val
|
||||
|
||||
case addrs.OutputValue:
|
||||
val, valDiags := normalizeRefValue(s.Data.GetOutput(subj, rng))
|
||||
diags = diags.Append(valDiags)
|
||||
outputValues[subj.Name] = val
|
||||
|
||||
case addrs.Check:
|
||||
val, valDiags := normalizeRefValue(s.Data.GetCheckBlock(subj, rng))
|
||||
diags = diags.Append(valDiags)
|
||||
outputValues[subj.Name] = val
|
||||
case addrs.ProviderFunction:
|
||||
if subj, ok := ref.Subject.(addrs.ProviderFunction); ok {
|
||||
// Inject function directly into context
|
||||
if _, ok := ctx.Functions[subj.String()]; !ok {
|
||||
fn, fnDiags := s.ProviderFunctions(subj, rng)
|
||||
fn, fnDiags := s.ProviderFunctions(subj, ref.SourceRange)
|
||||
diags = diags.Append(fnDiags)
|
||||
|
||||
if !fnDiags.HasErrors() {
|
||||
ctx.Functions[subj.String()] = *fn
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Should never happen
|
||||
panic(fmt.Errorf("Scope.buildEvalContext cannot handle address type %T", rawSubj))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
diags = diags.Append(varBuilder.putValueBySubject(ref))
|
||||
}
|
||||
|
||||
varBuilder.buildAllVariablesInto(ctx.Variables)
|
||||
|
||||
return ctx, diags
|
||||
}
|
||||
|
||||
type evalVarBuilder struct {
|
||||
s *Scope
|
||||
|
||||
dataResources map[string]map[string]cty.Value
|
||||
managedResources map[string]map[string]cty.Value
|
||||
wholeModules map[string]cty.Value
|
||||
inputVariables map[string]cty.Value
|
||||
localValues map[string]cty.Value
|
||||
outputValues map[string]cty.Value
|
||||
pathAttrs map[string]cty.Value
|
||||
terraformAttrs map[string]cty.Value
|
||||
countAttrs map[string]cty.Value
|
||||
forEachAttrs map[string]cty.Value
|
||||
checkBlocks map[string]cty.Value
|
||||
self cty.Value
|
||||
}
|
||||
|
||||
func (s *Scope) newEvalVarBuilder() *evalVarBuilder {
|
||||
return &evalVarBuilder{
|
||||
s: s,
|
||||
|
||||
dataResources: map[string]map[string]cty.Value{},
|
||||
managedResources: map[string]map[string]cty.Value{},
|
||||
wholeModules: map[string]cty.Value{},
|
||||
inputVariables: map[string]cty.Value{},
|
||||
localValues: map[string]cty.Value{},
|
||||
outputValues: map[string]cty.Value{},
|
||||
pathAttrs: map[string]cty.Value{},
|
||||
terraformAttrs: map[string]cty.Value{},
|
||||
countAttrs: map[string]cty.Value{},
|
||||
forEachAttrs: map[string]cty.Value{},
|
||||
checkBlocks: map[string]cty.Value{},
|
||||
}
|
||||
}
|
||||
|
||||
func (b *evalVarBuilder) putSelfValue(selfAddr addrs.Referenceable, ref *addrs.Reference) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
if selfAddr == nil {
|
||||
return diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: `Invalid "self" reference`,
|
||||
// This detail message mentions some current practice that
|
||||
// this codepath doesn't really "know about". If the "self"
|
||||
// object starts being supported in more contexts later then
|
||||
// we'll need to adjust this message.
|
||||
Detail: `The "self" object is not available in this context. This object can be used only in resource provisioner, connection, and postcondition blocks.`,
|
||||
Subject: ref.SourceRange.ToHCL().Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
if selfAddr == addrs.Self {
|
||||
// Programming error: the self address cannot alias itself.
|
||||
panic("scope SelfAddr attempting to alias itself")
|
||||
}
|
||||
|
||||
// self can only be used within a resource instance
|
||||
subj, ok := selfAddr.(addrs.ResourceInstance)
|
||||
if !ok {
|
||||
panic("BUG: self addr must be a resource instance, got " + reflect.TypeOf(selfAddr).String())
|
||||
}
|
||||
|
||||
val, valDiags := normalizeRefValue(b.s.Data.GetResource(subj.ContainingResource(), ref.SourceRange))
|
||||
|
||||
diags = diags.Append(valDiags)
|
||||
|
||||
// Self is an exception in that it must always resolve to a
|
||||
// particular instance. We will still insert the full resource into
|
||||
// the context below.
|
||||
var hclDiags hcl.Diagnostics
|
||||
// We should always have a valid self index by this point, but in
|
||||
// the case of an error, self may end up as a cty.DynamicValue.
|
||||
switch k := subj.Key.(type) {
|
||||
case addrs.IntKey:
|
||||
b.self, hclDiags = hcl.Index(val, cty.NumberIntVal(int64(k)), ref.SourceRange.ToHCL().Ptr())
|
||||
case addrs.StringKey:
|
||||
b.self, hclDiags = hcl.Index(val, cty.StringVal(string(k)), ref.SourceRange.ToHCL().Ptr())
|
||||
default:
|
||||
b.self = val
|
||||
}
|
||||
diags = diags.Append(hclDiags)
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
func (b *evalVarBuilder) putValueBySubject(ref *addrs.Reference) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
rawSubj := ref.Subject
|
||||
rng := ref.SourceRange
|
||||
|
||||
// This type switch must cover all of the "Referenceable" implementations
|
||||
// in package addrs, however we are removing the possibility of
|
||||
// Instances beforehand.
|
||||
switch addr := rawSubj.(type) {
|
||||
case addrs.ResourceInstance:
|
||||
rawSubj = addr.ContainingResource()
|
||||
case addrs.ModuleCallInstance:
|
||||
rawSubj = addr.Call
|
||||
case addrs.ModuleCallInstanceOutput:
|
||||
rawSubj = addr.Call.Call
|
||||
}
|
||||
|
||||
var normDiags tfdiags.Diagnostics
|
||||
|
||||
switch subj := rawSubj.(type) {
|
||||
case addrs.Resource:
|
||||
diags = diags.Append(b.putResourceValue(subj, rng))
|
||||
|
||||
case addrs.ModuleCall:
|
||||
b.wholeModules[subj.Name], normDiags = normalizeRefValue(b.s.Data.GetModule(subj, rng))
|
||||
|
||||
case addrs.InputVariable:
|
||||
b.inputVariables[subj.Name], normDiags = normalizeRefValue(b.s.Data.GetInputVariable(subj, rng))
|
||||
|
||||
case addrs.LocalValue:
|
||||
b.localValues[subj.Name], normDiags = normalizeRefValue(b.s.Data.GetLocalValue(subj, rng))
|
||||
|
||||
case addrs.PathAttr:
|
||||
b.pathAttrs[subj.Name], normDiags = normalizeRefValue(b.s.Data.GetPathAttr(subj, rng))
|
||||
|
||||
case addrs.TerraformAttr:
|
||||
b.terraformAttrs[subj.Name], normDiags = normalizeRefValue(b.s.Data.GetTerraformAttr(subj, rng))
|
||||
|
||||
case addrs.CountAttr:
|
||||
b.countAttrs[subj.Name], normDiags = normalizeRefValue(b.s.Data.GetCountAttr(subj, rng))
|
||||
|
||||
case addrs.ForEachAttr:
|
||||
b.forEachAttrs[subj.Name], normDiags = normalizeRefValue(b.s.Data.GetForEachAttr(subj, rng))
|
||||
|
||||
case addrs.OutputValue:
|
||||
b.outputValues[subj.Name], normDiags = normalizeRefValue(b.s.Data.GetOutput(subj, rng))
|
||||
|
||||
case addrs.Check:
|
||||
b.outputValues[subj.Name], normDiags = normalizeRefValue(b.s.Data.GetCheckBlock(subj, rng))
|
||||
|
||||
default:
|
||||
// Should never happen
|
||||
panic(fmt.Errorf("Scope.buildEvalContext cannot handle address type %T", rawSubj))
|
||||
}
|
||||
|
||||
diags = diags.Append(normDiags)
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
func (b *evalVarBuilder) putResourceValue(res addrs.Resource, rng tfdiags.SourceRange) tfdiags.Diagnostics {
|
||||
var into map[string]map[string]cty.Value
|
||||
|
||||
switch res.Mode {
|
||||
case addrs.ManagedResourceMode:
|
||||
into = b.managedResources
|
||||
case addrs.DataResourceMode:
|
||||
into = b.dataResources
|
||||
case addrs.InvalidResourceMode:
|
||||
panic("BUG: got invalid resource mode")
|
||||
default:
|
||||
panic(fmt.Errorf("BUG: got undefined ResourceMode %s", res.Mode))
|
||||
}
|
||||
|
||||
val, diags := normalizeRefValue(b.s.Data.GetResource(res, rng))
|
||||
|
||||
if into[res.Type] == nil {
|
||||
into[res.Type] = make(map[string]cty.Value)
|
||||
}
|
||||
into[res.Type][res.Name] = val
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
func (b *evalVarBuilder) buildAllVariablesInto(vals map[string]cty.Value) {
|
||||
// Managed resources are exposed in two different locations. The primary
|
||||
// is at the top level where the resource type name is the root of the
|
||||
// traversal, but we also expose them under "resource" as an escaping
|
||||
// technique if we add a reserved name in a future language edition which
|
||||
// conflicts with someone's existing provider.
|
||||
for k, v := range buildResourceObjects(managedResources) {
|
||||
for k, v := range buildResourceObjects(b.managedResources) {
|
||||
vals[k] = v
|
||||
}
|
||||
vals["resource"] = cty.ObjectVal(buildResourceObjects(managedResources))
|
||||
vals["resource"] = cty.ObjectVal(buildResourceObjects(b.managedResources))
|
||||
|
||||
vals["data"] = cty.ObjectVal(buildResourceObjects(dataResources))
|
||||
vals["module"] = cty.ObjectVal(wholeModules)
|
||||
vals["var"] = cty.ObjectVal(inputVariables)
|
||||
vals["local"] = cty.ObjectVal(localValues)
|
||||
vals["path"] = cty.ObjectVal(pathAttrs)
|
||||
vals["terraform"] = cty.ObjectVal(terraformAttrs)
|
||||
vals["count"] = cty.ObjectVal(countAttrs)
|
||||
vals["each"] = cty.ObjectVal(forEachAttrs)
|
||||
vals["data"] = cty.ObjectVal(buildResourceObjects(b.dataResources))
|
||||
vals["module"] = cty.ObjectVal(b.wholeModules)
|
||||
vals["var"] = cty.ObjectVal(b.inputVariables)
|
||||
vals["local"] = cty.ObjectVal(b.localValues)
|
||||
vals["path"] = cty.ObjectVal(b.pathAttrs)
|
||||
vals["terraform"] = cty.ObjectVal(b.terraformAttrs)
|
||||
vals["count"] = cty.ObjectVal(b.countAttrs)
|
||||
vals["each"] = cty.ObjectVal(b.forEachAttrs)
|
||||
|
||||
// Checks and outputs are conditionally included in the available scope, so
|
||||
// we'll only write out their values if we actually have something for them.
|
||||
if len(checkBlocks) > 0 {
|
||||
vals["check"] = cty.ObjectVal(checkBlocks)
|
||||
if len(b.checkBlocks) > 0 {
|
||||
vals["check"] = cty.ObjectVal(b.checkBlocks)
|
||||
}
|
||||
|
||||
if len(outputValues) > 0 {
|
||||
vals["output"] = cty.ObjectVal(outputValues)
|
||||
if len(b.outputValues) > 0 {
|
||||
vals["output"] = cty.ObjectVal(b.outputValues)
|
||||
}
|
||||
|
||||
if self != cty.NilVal {
|
||||
vals["self"] = self
|
||||
if b.self != cty.NilVal {
|
||||
vals["self"] = b.self
|
||||
}
|
||||
|
||||
return ctx, diags
|
||||
}
|
||||
|
||||
func buildResourceObjects(resources map[string]map[string]cty.Value) map[string]cty.Value {
|
||||
|
Loading…
Reference in New Issue
Block a user