-
Notifications
You must be signed in to change notification settings - Fork 2.8k
fix(terraform): evaluateStep to correctly set EvalContext for multiple instances of blocks
#8555
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(terraform): evaluateStep to correctly set EvalContext for multiple instances of blocks
#8555
Conversation
Referencing a block that uses `count`, and accessing it's arguments returns a nil value.
Instances of a block were being saved as "<name>[<idx>] = val" to the
eval context. `expandBlocks` correctly saves it as a tuple
"<name> = [val, ...]"
| if rawKey.Type().Equals(cty.Number) { | ||
| existing := valueMap[ref.NameLabel()] | ||
| asInt, _ := rawKey.AsBigFloat().Int64() | ||
|
|
||
| valueMap[ref.NameLabel()] = insertTupleElement(existing, int(asInt), b.Values()) | ||
| } | ||
|
|
||
| // The default behavior, that being no key or an unimplemented key type, is to | ||
| // save the block as the name value. The name label value contains the key. | ||
| // | ||
| // Eg: If the block has the index '4', the string [4] is contained | ||
| // within 'b.Labels()[1]' | ||
| // TODO: This is always done to maintain backwards compatibility. | ||
| // I think this can be omitted if the switch statement above | ||
| // sets the appropriate tuple value in the valueMap. | ||
| valueMap[b.Labels()[1]] = b.Values() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really think if this if statement branch is executed, we should not do valueMap[b.Labels()[1]] = b.Values().
In an abundance of caution, I kept the previous context setting as well. The previous context is incorrect for an hcl traversal though. So I feel like it was never being hit.
How the code would look if we remove the current behavior entirely.
if rawKey.Type().Equals(cty.Number) {
existing := valueMap[ref.NameLabel()]
asInt, _ := rawKey.AsBigFloat().Int64()
valueMap[ref.NameLabel()] = insertTupleElement(existing, int(asInt), b.Values())
} else {
valueMap[b.Labels()[1]] = b.Values()
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the old behavior should be removed after adding the handling of all cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nikpivkin by "all the cases", do you mean handling for_each and possibly dynamic cases as well? (TBD if dynamic needs any changes, I am unsure off the top of my head)
|
Hi @Emyrk ! Indeed, I was not even aware of this problem. Now the last part of the path when inserting values into a context contains an index, which is incorrect. Your approach looks good, but this problem applies not only to data blocks, but to all types of blocks with If you don't mind I can change this PR to add support for the rest of the cases. |
Yes! I was aware this problem is larger than my fix. I'm still getting my footing/bearings in this codebase, so just wanted to tackle this in the smallest incremental improvement I could contribute. Feel free to contribute to my branch directly if you want to fix it all in 1 go. We could also solve the |
Signed-off-by: nikpivkin <[email protected]>
…for_each Signed-off-by: nikpivkin <[email protected]>
|
@nikpivkin This also exists with module outputs: |
|
@Emyrk Yes, I will make the fixes for the modules in the other PR as it requires a slightly different approach. I already have a draft. |
Whoops! @nikpivkin I will revert my test and change, and keep this PR to what it was before my comment. |
| // the submodule if/when they feed back into inputs. | ||
| e.ctx.Set(outputs, "module", sm.definition.Name) | ||
| ref := sm.definition.Definition.Reference() | ||
| e.ctx.Set(blockInstanceValues(sm.definition.Definition, valueMap, outputs), "module", ref.NameLabel()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My draft has exactly the same implementation :)
|
@simar7 Can you take a look? |
| } | ||
|
|
||
| for _, tt := range tests { | ||
| tt := tt |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
capturing loop variable is no longer required https://go.dev/blog/loopvar-preview
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL ❤️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Emyrk can we remove this then?
| idx, _ := key.AsBigFloat().Int64() | ||
| return insertTupleElement(typeValues[ref.NameLabel()], int(idx), b.Values()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we check if idx is within bounds before we invoke insertTupleElement?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AsBigFloat() will panic if it is not valid type. Maybe we should assert it first?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Element may be larger than the tuple capacity when the context does not yet contain all instances of the block, and then we need to grow it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AsBigFloat()will panic if it is not valid type. Maybe we should assert it first?
We check that it is a number. Also the key cannot be unknown, it is either Nil or a known value.
25daecd to
d2fff87
Compare
|
Really sorry, I made a mistake and force pushed to this branch by accident. I got the branch back to the state that was reviewed 🤦♂️ |
No worries @Emyrk - were there any other updates to the comments I had left or you have pushed everything you had? |
|
@simar7 just read through all the comments. Added 1 little test syntax tweak. Everything I have for this issue is pushed up 👍 |
simar7
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm, thanks for the PR! @nikpivkin could you also take another look at it?
Before this change, all
datablocks set their values in theEvalContextwith the keyb.Labels()[0]. If the block is an instance of a block (viacountparam), the label looks like<name>[<idx>].This is different to how
expandBlockCountshandles this.https://github.com/aquasecurity/trivy/blob/main/pkg/iac/scanners/terraform/parser/evaluator.go#L422-L422
expandBlockCountshandles this correctly, as anhcl.TraverseIndexexpects a tuple (assuming the key is a number).This PR updates
evaluateStepto override the<name>in theEvalContextwith a new tuple containing every instance of the data block that is found.TODO:
This same fix probably needs to be applied for
for_eachand maybedynamicblocks.Testing when
count = 0likely also needs to be tested. I think this is already ok, because ifcount = 0, thenexpandBlockCountswill not set theEvalContext. And the block is omitted from the list with any key index. Worst case, the keyisNulland it is saved under thenamewithout a tuple. So the context has a value, but not a tuple type, so traversals will still fail.Checklist