Skip to content

Commit 2b6d8d9

Browse files
authored
perf(misconf): optimize work with context (#6968)
Signed-off-by: nikpivkin <[email protected]>
1 parent 65d991c commit 2b6d8d9

File tree

4 files changed

+113
-34
lines changed

4 files changed

+113
-34
lines changed

pkg/iac/scanners/terraform/parser/evaluator.go

+41-15
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,8 @@ func (e *evaluator) evaluateStep() {
9696
e.ctx.Set(e.getValuesByBlockType("locals"), "local")
9797
e.ctx.Set(e.getValuesByBlockType("provider"), "provider")
9898

99-
resources := e.getValuesByBlockType("resource")
100-
for key, resource := range resources.AsValueMap() {
101-
e.ctx.Set(resource, key)
99+
for typ, resource := range e.getResources() {
100+
e.ctx.Set(resource, typ)
102101
}
103102

104103
e.ctx.Set(e.getValuesByBlockType("data"), "data")
@@ -224,10 +223,12 @@ func (e *evaluator) evaluateSteps() {
224223
var lastContext hcl.EvalContext
225224
for i := 0; i < maxContextIterations; i++ {
226225

226+
e.debug.Log("Starting iteration %d", i)
227227
e.evaluateStep()
228228

229229
// if ctx matches the last evaluation, we can bail, nothing left to resolve
230230
if i > 0 && reflect.DeepEqual(lastContext.Variables, e.ctx.Inner().Variables) {
231+
e.debug.Log("Context unchanged at i=%d", i)
231232
break
232233
}
233234
if len(e.ctx.Inner().Variables) != len(lastContext.Variables) {
@@ -330,15 +331,16 @@ func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks, isDynamic bool
330331
}
331332

332333
clone := block.Clone(idx)
333-
334334
ctx := clone.Context()
335-
336335
e.copyVariables(block, clone)
337336

338-
ctx.SetByDot(idx, "each.key")
339-
ctx.SetByDot(val, "each.value")
340-
ctx.Set(idx, block.TypeLabel(), "key")
341-
ctx.Set(val, block.TypeLabel(), "value")
337+
eachObj := cty.ObjectVal(map[string]cty.Value{
338+
"key": idx,
339+
"value": val,
340+
})
341+
342+
ctx.Set(eachObj, "each")
343+
ctx.Set(eachObj, block.TypeLabel())
342344

343345
if isDynamic {
344346
if iterAttr := block.GetAttribute("iterator"); iterAttr.IsNotNil() {
@@ -354,9 +356,7 @@ func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks, isDynamic bool
354356

355357
forEachFiltered = append(forEachFiltered, clone)
356358

357-
values := clone.Values()
358-
clones[idx.AsString()] = values
359-
e.ctx.SetByDot(values, clone.GetMetadata().Reference())
359+
clones[idx.AsString()] = clone.Values()
360360
})
361361

362362
metadata := block.GetMetadata()
@@ -434,11 +434,12 @@ func (e *evaluator) copyVariables(from, to *terraform.Block) {
434434
return
435435
}
436436

437-
srcValue := e.ctx.Root().Get(fromBase, fromRel)
437+
rootCtx := e.ctx.Root()
438+
srcValue := rootCtx.Get(fromBase, fromRel)
438439
if srcValue == cty.NilVal {
439440
return
440441
}
441-
e.ctx.Root().Set(srcValue, fromBase, toRel)
442+
rootCtx.Set(srcValue, fromBase, toRel)
442443
}
443444

444445
func (e *evaluator) evaluateVariable(b *terraform.Block) (cty.Value, error) {
@@ -530,7 +531,7 @@ func (e *evaluator) getValuesByBlockType(blockType string) cty.Value {
530531
continue
531532
}
532533
values[b.Label()] = b.Values()
533-
case "resource", "data":
534+
case "data":
534535
if len(b.Labels()) < 2 {
535536
continue
536537
}
@@ -553,3 +554,28 @@ func (e *evaluator) getValuesByBlockType(blockType string) cty.Value {
553554

554555
return cty.ObjectVal(values)
555556
}
557+
558+
func (e *evaluator) getResources() map[string]cty.Value {
559+
values := make(map[string]map[string]cty.Value)
560+
561+
for _, b := range e.blocks {
562+
if b.Type() != "resource" {
563+
continue
564+
}
565+
566+
if len(b.Labels()) < 2 {
567+
continue
568+
}
569+
570+
val, exists := values[b.Labels()[0]]
571+
if !exists {
572+
val = make(map[string]cty.Value)
573+
values[b.Labels()[0]] = val
574+
}
575+
val[b.Labels()[1]] = b.Values()
576+
}
577+
578+
return lo.MapValues(values, func(v map[string]cty.Value, _ string) cty.Value {
579+
return cty.ObjectVal(v)
580+
})
581+
}

pkg/iac/terraform/block.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func NewBlock(hclBlock *hcl.Block, ctx *context.Context, moduleBlock *Block, par
8585
}
8686

8787
b := Block{
88-
id: uuid.New().String(),
88+
id: uuid.NewString(),
8989
context: ctx,
9090
hclBlock: hclBlock,
9191
moduleBlock: moduleBlock,
@@ -446,6 +446,9 @@ func (b *Block) Attributes() map[string]*Attribute {
446446
func (b *Block) Values() cty.Value {
447447
values := createPresetValues(b)
448448
for _, attribute := range b.GetAttributes() {
449+
if attribute.Name() == "for_each" {
450+
continue
451+
}
449452
values[attribute.Name()] = attribute.Value()
450453
}
451454
return cty.ObjectVal(postProcessValues(b, values))

pkg/iac/terraform/context/context.go

+31-18
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,29 @@ func (c *Context) Get(parts ...string) cty.Value {
4646
if len(parts) == 0 {
4747
return cty.NilVal
4848
}
49-
src := c.ctx.Variables
50-
for i, part := range parts {
51-
if i == len(parts)-1 {
52-
return src[part]
49+
50+
curr := c.ctx.Variables[parts[0]]
51+
if len(parts) == 1 {
52+
return curr
53+
}
54+
55+
for i, part := range parts[1:] {
56+
if !curr.Type().HasAttribute(part) {
57+
return cty.NilVal
58+
}
59+
60+
attr := curr.GetAttr(part)
61+
62+
if i == len(parts)-2 { // iteration from the first element
63+
return attr
5364
}
54-
nextPart := src[part]
55-
if nextPart == cty.NilVal {
65+
66+
if !(attr.IsKnown() && attr.Type().IsObjectType()) {
5667
return cty.NilVal
5768
}
58-
src = nextPart.AsValueMap()
69+
curr = attr
5970
}
71+
6072
return cty.NilVal
6173
}
6274

@@ -97,13 +109,12 @@ func mergeVars(src cty.Value, parts []string, value cty.Value) cty.Value {
97109
}
98110

99111
data := make(map[string]cty.Value)
100-
if src.Type().IsObjectType() && !src.IsNull() && src.LengthInt() > 0 {
112+
if isNotEmptyObject(src) {
101113
data = src.AsValueMap()
102-
tmp, ok := src.AsValueMap()[parts[0]]
103-
if !ok {
104-
src = cty.ObjectVal(make(map[string]cty.Value))
114+
if attr, ok := data[parts[0]]; ok {
115+
src = attr
105116
} else {
106-
src = tmp
117+
src = cty.EmptyObjectVal
107118
}
108119
}
109120

@@ -118,14 +129,16 @@ func mergeObjects(a, b cty.Value) cty.Value {
118129
for key, val := range a.AsValueMap() {
119130
output[key] = val
120131
}
121-
for key, val := range b.AsValueMap() {
122-
old, exists := output[key]
123-
if exists && isNotEmptyObject(old) && isNotEmptyObject(val) {
124-
output[key] = mergeObjects(old, val)
132+
b.ForEachElement(func(key, val cty.Value) (stop bool) {
133+
k := key.AsString()
134+
old := output[k]
135+
if old.IsKnown() && isNotEmptyObject(old) && isNotEmptyObject(val) {
136+
output[k] = mergeObjects(old, val)
125137
} else {
126-
output[key] = val
138+
output[k] = val
127139
}
128-
}
140+
return false
141+
})
129142
return cty.ObjectVal(output)
130143
}
131144

pkg/iac/terraform/context/context_test.go

+37
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,43 @@ func Test_ContextVariablesPreservation(t *testing.T) {
5252

5353
}
5454

55+
func Test_SetWithMerge(t *testing.T) {
56+
hctx := hcl.EvalContext{
57+
Variables: map[string]cty.Value{
58+
"my": cty.ObjectVal(map[string]cty.Value{
59+
"someValue": cty.ObjectVal(map[string]cty.Value{
60+
"foo": cty.StringVal("test"),
61+
"bar": cty.ObjectVal(map[string]cty.Value{
62+
"foo": cty.StringVal("test"),
63+
}),
64+
}),
65+
}),
66+
},
67+
}
68+
69+
ctx := NewContext(&hctx, nil)
70+
71+
val := cty.ObjectVal(map[string]cty.Value{
72+
"foo2": cty.StringVal("test2"),
73+
"bar": cty.ObjectVal(map[string]cty.Value{
74+
"foo2": cty.StringVal("test2"),
75+
}),
76+
})
77+
78+
ctx.Set(val, "my", "someValue")
79+
got := ctx.Get("my", "someValue")
80+
expected := cty.ObjectVal(map[string]cty.Value{
81+
"foo": cty.StringVal("test"),
82+
"foo2": cty.StringVal("test2"),
83+
"bar": cty.ObjectVal(map[string]cty.Value{
84+
"foo": cty.StringVal("test"),
85+
"foo2": cty.StringVal("test2"),
86+
}),
87+
})
88+
89+
assert.Equal(t, expected, got)
90+
}
91+
5592
func Test_ContextVariablesPreservationByDot(t *testing.T) {
5693

5794
underlying := &hcl.EvalContext{}

0 commit comments

Comments
 (0)