Merge pull request #26788 from hashicorp/jbardin/eval-self-block
allow path and terraform in self-block eval
This commit is contained in:
commit
6bb79b7714
52
lang/eval.go
52
lang/eval.go
|
@ -72,8 +72,13 @@ func (s *Scope) EvalBlock(body hcl.Body, schema *configschema.Block) (cty.Value,
|
||||||
|
|
||||||
// EvalSelfBlock evaluates the given body only within the scope of the provided
|
// EvalSelfBlock evaluates the given body only within the scope of the provided
|
||||||
// object and instance key data. References to the object must use self, and the
|
// object and instance key data. References to the object must use self, and the
|
||||||
// key data will only contain count.index or each.key.
|
// key data will only contain count.index or each.key. The static values for
|
||||||
|
// terraform and path will also be available in this context.
|
||||||
func (s *Scope) EvalSelfBlock(body hcl.Body, self cty.Value, schema *configschema.Block, keyData instances.RepetitionData) (cty.Value, tfdiags.Diagnostics) {
|
func (s *Scope) EvalSelfBlock(body hcl.Body, self cty.Value, schema *configschema.Block, keyData instances.RepetitionData) (cty.Value, tfdiags.Diagnostics) {
|
||||||
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
|
spec := schema.DecoderSpec()
|
||||||
|
|
||||||
vals := make(map[string]cty.Value)
|
vals := make(map[string]cty.Value)
|
||||||
vals["self"] = self
|
vals["self"] = self
|
||||||
|
|
||||||
|
@ -88,12 +93,55 @@ func (s *Scope) EvalSelfBlock(body hcl.Body, self cty.Value, schema *configschem
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refs, refDiags := References(hcldec.Variables(body, spec))
|
||||||
|
diags = diags.Append(refDiags)
|
||||||
|
|
||||||
|
terraformAttrs := map[string]cty.Value{}
|
||||||
|
pathAttrs := map[string]cty.Value{}
|
||||||
|
|
||||||
|
// We could always load the static values for Path and Terraform values,
|
||||||
|
// but we want to parse the references so that we can get source ranges for
|
||||||
|
// user diagnostics.
|
||||||
|
for _, ref := range refs {
|
||||||
|
// we already loaded the self value
|
||||||
|
if ref.Subject == addrs.Self {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch subj := ref.Subject.(type) {
|
||||||
|
case addrs.PathAttr:
|
||||||
|
val, valDiags := normalizeRefValue(s.Data.GetPathAttr(subj, ref.SourceRange))
|
||||||
|
diags = diags.Append(valDiags)
|
||||||
|
pathAttrs[subj.Name] = val
|
||||||
|
|
||||||
|
case addrs.TerraformAttr:
|
||||||
|
val, valDiags := normalizeRefValue(s.Data.GetTerraformAttr(subj, ref.SourceRange))
|
||||||
|
diags = diags.Append(valDiags)
|
||||||
|
terraformAttrs[subj.Name] = val
|
||||||
|
|
||||||
|
case addrs.CountAttr, addrs.ForEachAttr:
|
||||||
|
// each and count have already been handled.
|
||||||
|
|
||||||
|
default:
|
||||||
|
// This should have been caught in validation, but point the user
|
||||||
|
// to the correct location in case something slipped through.
|
||||||
|
diags = diags.Append(&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: `Invalid reference`,
|
||||||
|
Detail: fmt.Sprintf("The reference to %q is not valid in this context", ref.Subject),
|
||||||
|
Subject: ref.SourceRange.ToHCL().Ptr(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vals["path"] = cty.ObjectVal(pathAttrs)
|
||||||
|
vals["terraform"] = cty.ObjectVal(terraformAttrs)
|
||||||
|
|
||||||
ctx := &hcl.EvalContext{
|
ctx := &hcl.EvalContext{
|
||||||
Variables: vals,
|
Variables: vals,
|
||||||
Functions: s.Functions(),
|
Functions: s.Functions(),
|
||||||
}
|
}
|
||||||
|
|
||||||
var diags tfdiags.Diagnostics
|
|
||||||
val, decDiags := hcldec.Decode(body, schema.DecoderSpec(), ctx)
|
val, decDiags := hcldec.Decode(body, schema.DecoderSpec(), ctx)
|
||||||
diags = diags.Append(decDiags)
|
diags = diags.Append(decDiags)
|
||||||
return val, diags
|
return val, diags
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
|
"github.com/hashicorp/terraform/instances"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
|
@ -642,3 +643,128 @@ func formattedJSONValue(val cty.Value) string {
|
||||||
json.Indent(&buf, j, "", " ")
|
json.Indent(&buf, j, "", " ")
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestScopeEvalSelfBlock(t *testing.T) {
|
||||||
|
data := &dataForTests{
|
||||||
|
PathAttrs: map[string]cty.Value{
|
||||||
|
"module": cty.StringVal("foo/bar"),
|
||||||
|
"cwd": cty.StringVal("/home/foo/bar"),
|
||||||
|
"root": cty.StringVal("/home/foo"),
|
||||||
|
},
|
||||||
|
TerraformAttrs: map[string]cty.Value{
|
||||||
|
"workspace": cty.StringVal("default"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
schema := &configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"attr": {
|
||||||
|
Type: cty.String,
|
||||||
|
},
|
||||||
|
"num": {
|
||||||
|
Type: cty.Number,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
Config string
|
||||||
|
Self cty.Value
|
||||||
|
KeyData instances.RepetitionData
|
||||||
|
Want map[string]cty.Value
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Config: `attr = self.foo`,
|
||||||
|
Self: cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.StringVal("bar"),
|
||||||
|
}),
|
||||||
|
KeyData: instances.RepetitionData{
|
||||||
|
CountIndex: cty.NumberIntVal(0),
|
||||||
|
},
|
||||||
|
Want: map[string]cty.Value{
|
||||||
|
"attr": cty.StringVal("bar"),
|
||||||
|
"num": cty.NullVal(cty.Number),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: `num = count.index`,
|
||||||
|
KeyData: instances.RepetitionData{
|
||||||
|
CountIndex: cty.NumberIntVal(0),
|
||||||
|
},
|
||||||
|
Want: map[string]cty.Value{
|
||||||
|
"attr": cty.NullVal(cty.String),
|
||||||
|
"num": cty.NumberIntVal(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: `attr = each.key`,
|
||||||
|
KeyData: instances.RepetitionData{
|
||||||
|
EachKey: cty.StringVal("a"),
|
||||||
|
},
|
||||||
|
Want: map[string]cty.Value{
|
||||||
|
"attr": cty.StringVal("a"),
|
||||||
|
"num": cty.NullVal(cty.Number),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: `attr = path.cwd`,
|
||||||
|
Want: map[string]cty.Value{
|
||||||
|
"attr": cty.StringVal("/home/foo/bar"),
|
||||||
|
"num": cty.NullVal(cty.Number),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: `attr = path.module`,
|
||||||
|
Want: map[string]cty.Value{
|
||||||
|
"attr": cty.StringVal("foo/bar"),
|
||||||
|
"num": cty.NullVal(cty.Number),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: `attr = path.root`,
|
||||||
|
Want: map[string]cty.Value{
|
||||||
|
"attr": cty.StringVal("/home/foo"),
|
||||||
|
"num": cty.NullVal(cty.Number),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: `attr = terraform.workspace`,
|
||||||
|
Want: map[string]cty.Value{
|
||||||
|
"attr": cty.StringVal("default"),
|
||||||
|
"num": cty.NullVal(cty.Number),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.Config, func(t *testing.T) {
|
||||||
|
file, parseDiags := hclsyntax.ParseConfig([]byte(test.Config), "", hcl.Pos{Line: 1, Column: 1})
|
||||||
|
if len(parseDiags) != 0 {
|
||||||
|
t.Errorf("unexpected diagnostics during parse")
|
||||||
|
for _, diag := range parseDiags {
|
||||||
|
t.Errorf("- %s", diag)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body := file.Body
|
||||||
|
|
||||||
|
scope := &Scope{
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
gotVal, ctxDiags := scope.EvalSelfBlock(body, test.Self, schema, test.KeyData)
|
||||||
|
if ctxDiags.HasErrors() {
|
||||||
|
t.Fatal(ctxDiags.Err())
|
||||||
|
}
|
||||||
|
|
||||||
|
wantVal := cty.ObjectVal(test.Want)
|
||||||
|
|
||||||
|
if !gotVal.RawEquals(wantVal) {
|
||||||
|
t.Errorf(
|
||||||
|
"wrong result\nexpr: %s\ngot: %#v\nwant: %#v",
|
||||||
|
test.Config, gotVal, wantVal,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue