Skip to content
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

hcl2template: recursively evaluate local variables #13039

Merged
merged 9 commits into from
Jun 17, 2024

Conversation

lbajolet-hashicorp
Copy link
Contributor

@lbajolet-hashicorp lbajolet-hashicorp commented Jun 12, 2024

The logic for evaluating local variables used to rely on their definition order, with some edge cases.
Typically locals blocks define multiple local variables, which don't necessarily appear in the same order at evaluation as within the template, leading to inconsistent behaviour, as the order in which those are added to the list of local variables is non-deterministic.

To avoid this problem, we change how local variables are evaluated, and we're adopting a workflow similar to datasources, where the local variables first build a list of direct dependencies.
Then when evaluation happens, we evaluate all the dependencies recursively for each local variable, which takes care of this issue.

As with Datasources, we add a cap to the recursion: 10. I.e. if the evaluation of a single variable provokes an infinite recursion, we stop at that point and return an error to the user, telling them to fix their template.

Closes: #13018

@lbajolet-hashicorp lbajolet-hashicorp requested a review from a team as a code owner June 12, 2024 14:06
@lbajolet-hashicorp lbajolet-hashicorp marked this pull request as draft June 12, 2024 20:27
@lbajolet-hashicorp lbajolet-hashicorp changed the base branch from main to split_packer_test_suites June 13, 2024 14:52
@lbajolet-hashicorp lbajolet-hashicorp marked this pull request as ready for review June 13, 2024 14:55
Copy link
Contributor

@nywilken nywilken left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a good approach to this issue and not something users would expect given that Terraform locals honor the dependencies. That said we need to update the documentation for our locals as the current documentation reads

 The locals block is read as a map. Maps are not sorted, and therefore the evaluation time is not deterministic.

To avoid that, singular local blocks should be used instead. These will be evaluated in the order they are defined, and the evaluation order and time will always be the same.

hcl2template/types.packer_config.go Show resolved Hide resolved
@lbajolet-hashicorp lbajolet-hashicorp changed the base branch from split_packer_test_suites to main June 13, 2024 19:36
@lbajolet-hashicorp
Copy link
Contributor Author

I think this is a good approach to this issue and not something users would expect given that Terraform locals honor the dependencies. That said we need to update the documentation for our locals as the current documentation reads

 The locals block is read as a map. Maps are not sorted, and therefore the evaluation time is not deterministic.

To avoid that, singular local blocks should be used instead. These will be evaluated in the order they are defined, and the evaluation order and time will always be the same.

I'm not sure I see where those docs are in the repo, where did you see this?

Copy link
Contributor

@nywilken nywilken left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me. I left one question.

packer_test/commands_test.go Outdated Show resolved Hide resolved
@lbajolet-hashicorp lbajolet-hashicorp force-pushed the fix_locals_eval_order branch 3 times, most recently from ddcf937 to 1fcfafa Compare June 17, 2024 16:30
When a Packer command is created for testing the tool, we generally run
it once, then the command is essentially nooping.

This change allows us to run Packer multiple times with the same
parameters, and make sure all runs conform to a specific list of checks.

This allows us to more reliably test non-deterministic behaviours.
Some commands need to have an input in order to work.
For those, we add the capability for the packerCommand struct to have
their stdin defined from a string, which is then fed to the command
being executed.
When a test fails to exert its assertions on the command-line output, a
test fails, but we don't necessarily can troubleshoot what happened,
especially when this happens in a CI environment.

Therefore, for convenience, we add the faculty for packerCommand.Assert
to automatically dump a command's output (both stdout and stderr) if a
test fails.
The SkipNoAcc function on PackerTestSuite allows to mark a test run as
not to be run every time we run `make test`, but only when PACKER_ACC=1
is set in the environment.

This allows us to skip executing tests that are either long-running, or
that depend on external dependencies (typically Github), which have a
higher potential to fail on a normal run of Packer tests.
GetVarsByType is a function that gets a list of Traversals from a hcl
Block.

This approach works when what we are visiting is indeed one, however
when we can get an immediate list of Traversals, but want to filter them
based on their roots, we have to reimplement parts of that function.

Therefore, we split this function in two, GetVarsByType still keeps its
current behaviour, but the filtering step is exposed as another function
now: FilterTraversalsByType, so we can reuse it elsewhere.
The logic for evaluating local variables used to rely on their
definition order, with some edge cases. Typically `locals` blocks define
multiple local variables, which don't necessarily appear in the same
order at evaluation as within the template, leading to inconsistent
behaviour, as the order in which those are added to the list of local
variables is non-deterministic.

To avoid this problem, we change how local variables are evaluated, and
we're adopting a workflow similar to datasources, where the local
variables first build a list of direct dependencies. Then when
evaluation happens, we evaluate all the dependencies recursively for
each local variable, which takes care of this issue.

As with Datasources, we add a cap to the recursion: 10. I.e. if the
evaluation of a single variable provokes an infinite recursion, we stop
at that point and return an error to the user, telling them to fix their
template.
Previously duplicate detection for local variables happened during
`Initialise`, through a call to `checkForDuplicateLocalDefinition`.

This works in a majority of cases, but for commands like `console`, this
was not detected as the return diagnostics for `Initialise` are ignored.

That check can be done as early as during parsing however, as the names
of blocks are not dynamic in the slightest (no interpolation possible),
so we move that detection logic into `Parse`, so that the behaviour is
coherent between all commands.
When a command is run, it is the expectation that no test should make
Packer panic. If it did, something is wrong and Packer should be fixed
so it doesn't panic anymore in that situation.

The way we did the check before was adding a PanicCheck after the
command ran, so we could make sure of that during `Assert`.

However, since we introduced the possibility to have multiple runs,
having this addition as part of the run loop meant that the PanicCheck
would be run as many times as there were runs.

While this worked, this implied that we'd do the same check multiple
times on a single command output, which is not optimal.

Instead, this commit moves the check to within the `Run` function, this
way for each run of the command we do the check once, and then we can
assert the results of the command on what output it produced.
Copy link
Contributor

@nywilken nywilken left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rework looks good to me.

@lbajolet-hashicorp lbajolet-hashicorp added the backport/1.11.x Backport PR changes to `release/1.11.x` label Jun 17, 2024
@lbajolet-hashicorp lbajolet-hashicorp merged commit 13c5212 into main Jun 17, 2024
12 checks passed
@lbajolet-hashicorp lbajolet-hashicorp deleted the fix_locals_eval_order branch June 17, 2024 20:52
Copy link

I'm going to lock this pull request because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.
If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 18, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
backport/1.11.x Backport PR changes to `release/1.11.x` bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Seemingly random incorrect evaluation of the can() function
2 participants