-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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. |
// lastState needs to be captured after applying outputs – so that they | ||
// don't get treated as changes – but before running post-submodule | ||
// evaluation, so that changes from that can trigger re-evaluations of | ||
// 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 👍 |
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
data
blocks set their values in theEvalContext
with the keyb.Labels()[0]
. If the block is an instance of a block (viacount
param), the label looks like<name>[<idx>]
.This is different to how
expandBlockCounts
handles this.https://github.com/aquasecurity/trivy/blob/main/pkg/iac/scanners/terraform/parser/evaluator.go#L422-L422
expandBlockCounts
handles this correctly, as anhcl.TraverseIndex
expects a tuple (assuming the key is a number).This PR updates
evaluateStep
to override the<name>
in theEvalContext
with a new tuple containing every instance of the data block that is found.TODO:
This same fix probably needs to be applied for
for_each
and maybedynamic
blocks.Testing when
count = 0
likely also needs to be tested. I think this is already ok, because ifcount = 0
, thenexpandBlockCounts
will not set theEvalContext
. And the block is omitted from the list with any key index. Worst case, the keyisNull
and it is saved under thename
without a tuple. So the context has a value, but not a tuple type, so traversals will still fail.Checklist