-
Notifications
You must be signed in to change notification settings - Fork 2.6k
fix(terraform): 'count' meta argument sourced from submodule output #8899
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
base: main
Are you sure you want to change the base?
Conversation
Do not expand unknown `count` blocks. Run `expandBlocks` in eval to allow submodule returns to affect the `count` when using module outputs.
The depth of submodule evalutation is still limited
Module counts are incorrectly being handled
func TestBlockCountModules(t *testing.T) { | ||
t.Skip( | ||
"This test is currently failing. " + | ||
"The count passed to `module bar` is not being set correctly. " + | ||
"The count value is sourced from the output of `module foo`. " + | ||
"Submodules cannot be dependent on the output of other submodules right now. ", | ||
) | ||
// `count` meta attributes are incorrectly handled when referencing | ||
// a module output. | ||
files := map[string]string{ | ||
"main.tf": ` | ||
module "foo" { | ||
source = "./modules/foo" | ||
} | ||
module "bar" { | ||
source = "./modules/foo" | ||
count = module.foo.staticZero | ||
} | ||
`, | ||
"modules/foo/main.tf": ` | ||
output "staticZero" { | ||
value = 0 | ||
} | ||
`, | ||
} | ||
|
||
modules := parse(t, files) | ||
require.Len(t, modules, 2) | ||
} |
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.
Does evaluating sub-modules also need to occur in the inner loop? Defering them if their count/for_each is unknown?
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.
in the inner loop
What do you mean?
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 mean evaluateSteps
when I referred to the "inner loop".
Right now the procedure is:
evaluateSteps
expandBlocks
x2evaluateSubmodules
evaluateSteps
The test case I referenced here has the count for the submodule only being available after step 3. So evaluateSubmodules
needs to occur again.
But you could imagine a nested submodule with depth >1, so it needs to be done in the loop with maxContextIterations
All of this is related to your graph suggestion.
// Always attempt to expand any blocks that might now be expandable | ||
// due to new context being set. | ||
e.blocks = e.expandBlocks(e.blocks) |
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.
This is the biggest change. It allows expandBlocks
to occur in evaluateSteps
.
From my understanding of expandBlocks
this should be a safe operation. Is there a reason it is currently called outside this context? @nikpivkin ?
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 this just wasn't thought of in the initial development of the evaluator. I want to test this on a repository with a large number of modules and check the performance.
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 let me know if I can do anything to support you 👍
@simar7 The Terraform configuration evaluation logic is getting more and more complicated and confusing. What if we apply graph theory to solve this problem, as done in terraform or in infracost? To do this, we would need to walk through all the blocks, extract references from expressions, and populate the graph. Then apply topological sorting to get the traversal order and after that iterate over the blocks, expand the blocks and evaluate the values. Wdyt? |
Yes I agree in principle but at this time, we have no set priorities to do so. I suggest we start an issue and document such shortfalls as part of it so we can start tracking the importance of having a graph based engine. |
if !forEachVal.IsKnown() { | ||
// Defer the expansion of the block if it is unknown. It might be known at a later | ||
// execution step. | ||
forEachFiltered = append(forEachFiltered, block) |
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.
Need to check a case where for_each/count
refers to an unknown value, such as a data source. In such a case one instance of the block will be created?
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.
For count
, the previous behavior was to assume count = 1
. So this would change the count index value from 1
-> unknown
and leave the block unexpanded.
For for_each
, the previous behavior was to remove the block and log a debug message that it failed to expand
. The new behavior is to leave the block with the index as the unknown.
In both cases, the new behavior has the key being unknown and I would imagine references to the block using a key would break. This feels correct though?
The count one assuming count = 1
feels incorrect. And the for_each
just leaves the remaining block.
To get the same behavior as before, a last pass would have to be done to either remove the blocks or set count to 1
once the eval is done.
@nikpivkin this is ready again for review |
Description
count
&for_each
meta argument is incorrectly handled when referencing a submodule's output. The expansion arguments only work when referencing within the same scope.This is being caused because
expandBlockCounts
defaults to acount = 1
for unknown values.ExpandingBlocks happens before submodule evaluation:
https://github.com/aquasecurity/trivy/blob/main/pkg/iac/scanners/terraform/parser/evaluator.go#L139-L142
So expanded blocks cannot reference submodule exports. This is inconsistent with
terraform plan/apply
which renders 0data
blocks in this unit test.The solution presented allows
expandBlocks
to be called duringevaluateSteps
, which occurs again after submodules are expanded.There is still an issue if the
count
/for_each
argument is used in a module block, meaning a submodule depends on another module output. Since submodule evaluation will not happen again. See the testTestBlockCountModules
. It is skipped for now.Checklist
Note: I updated this PR to the latest
main
, and moved the fork origin to a new organization. I was previously using my personal github org.