-
-
Notifications
You must be signed in to change notification settings - Fork 966
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
Locals #802
Locals #802
Conversation
Wow awesome, this is an excellent improvement if it works (might test it today..). Like you said, I don't really see how this could have helped with issue #752 and that is fine.. I think the overall best solution to clean up some of the shortcomings of terragrunt is to implement the entire feature set in #759, which would probably require a hefty chunk of code/functionality to change. |
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.
Nice addition!
README.md
Outdated
} | ||
``` | ||
|
||
You can use any valid terragrunt expression in the `locals` configuration. |
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.
Prepare yourself for the "how do I reuse locals from another file?" question in 3... 2... 1... :-D
Perhaps tackle that head on and add a section on reusing/merging yaml
files for now? And when we add get_input
and get_output
, we can replace 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.
Ok I can add that in.
FWIW, I was thinking about proposing a similar construct, but called globals
which can be merged with the parent in an include
call. I think the explicit separating of locals
from globals
is important, especially if and when include
becomes more complex to allow inclusion of multiple levels of terragrunt config.
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.
Do you have any immediate plans to implement globals
? We've just developed a need for something like that (having variables in our parent terragrunt.hcl
that we can override from child terragrunt.hcl
s). While including a yaml file if it exists would work, it doesn't seem like the cleanest solution and we'd have to walk it back once there's a better way.
If you haven't started and you think it would be something that would be accepted, I don't mind creating a PR for 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.
If you haven't started and you think it would be something that would be accepted, I don't mind creating a PR for it.
I don't think we have immediate plans for globals
. I would suggest first creating an issue (with Feature Request
in the title) describing:
- Your use case. Why do you need something like
globals
? - Ideal solution. Is
globals
what you want, or do you want something likeget_local
, which can take anotherterragrunt.hcl
config file as input, akey
and return the value assigned to thatlocal
in the target file? - Indicate whether you want to open a PR, either for the ideal solution or if that is challenging, a temporary workaround solution that works, but is better than what is available now.
config/config.go
Outdated
// the config. For consistency, `locals` in the call to `decodeHcl` is always assumed to be nil. | ||
func decodeAsTerragruntLocals(file *hcl.File, filename string, terragruntOptions *options.TerragruntOptions) (*terragruntLocals, error) { | ||
terragruntLocals := terragruntLocals{} | ||
err := decodeHcl(file, filename, &terragruntLocals, terragruntOptions, nil, nil) |
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.
What happens if a local references another local?
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.
Ah I didn't think about this possibility. I think currently it will panic or raise a syntax error, but it would be nice if it can be recursive. Let me do a quick spike to see how hard it is to implement this.
Co-Authored-By: Yevgeniy Brikman <[email protected]>
UPDATE: I made a huge refactor of the implementation to convert the Note that I am still optimizing the logs and error handling, so not quite ready to merge yet but I reached the point where it would be good to get some feedback on the new approach. |
Ok @brikis98 this is ready for re-review given the new implementation. |
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.
Nice!
} | ||
``` | ||
|
||
##### Including globally defined locals |
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.
Thx for adding 👍
config/config.go
Outdated
@@ -45,6 +47,15 @@ type terragruntConfigFile struct { | |||
PreventDestroy *bool `hcl:"prevent_destroy,attr"` | |||
Skip *bool `hcl:"skip,attr"` | |||
IamRole *string `hcl:"iam_role,attr"` | |||
|
|||
// This should be ignored |
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.
Why ignored?
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 needs to be skipped here because hcl
can't decode it correctly when it self references locals
. It is not necessary to decode it into this struct here because it is evaluated in a separate loop.
However, we don't want to completely omit the existence of it because we want a syntax error if we have blocks that terragrunt doesn't expect (e.g typos). Hence it is added here as an attribute but the struct won't attempt to parse anything in 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.
Perhaps expand the comment a bit to explain this?
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.
config/config.go
Outdated
@@ -299,24 +318,37 @@ func ParseConfigString(configString string, terragruntOptions *options.Terragrun | |||
// an include block) | |||
func decodeAsTerragruntInclude(file *hcl.File, filename string, terragruntOptions *options.TerragruntOptions) (*terragruntInclude, error) { | |||
terragruntInclude := terragruntInclude{} | |||
err := decodeHcl(file, filename, &terragruntInclude, terragruntOptions, nil) | |||
err := decodeHcl(file, filename, &terragruntInclude, terragruntOptions, nil, nil) |
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.
An include
can't have its own locals?
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.
Ah good point! Updated the evaluation order so it can be there.
config/locals.go
Outdated
} | ||
if localsBlock == nil { | ||
// No locals block referenced in the file | ||
terragruntOptions.Logger.Printf("Did not find any locals block: skipping evaluation.") |
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.
Do we need to log this?
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.
Implemented a crude version of log levels. This is actually quite useful to debug issues with locals and I expect we will need it once people start using it.
config/locals.go
Outdated
return nil, nil | ||
} | ||
|
||
terragruntOptions.Logger.Printf("Found locals block: evaluating the expressions.") |
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.
Same here... Terragrunt doesn't currently support log levels, so the log statements here could be quite noisy...
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.
See above re:log levels.
config/locals.go
Outdated
// further. | ||
evaluatedLocals := map[string]cty.Value{} | ||
evaluated := true | ||
for len(locals) > 0 && evaluated { |
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 add a simple counter and ensure it stays under, say, 1000? Could be a nice safety valve in case of a bug in the code that leads to an infinite loop.
Actually, on that note, what if the code has:
locals {
a = local.b
b = local.a
}
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 will fail because neither can be evaluated in the first loop, so evaluated
will be false
.
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 add a simple counter and ensure it stays under, say, 1000? Could be a nice safety valve in case of a bug in the code that leads to an infinite loop.
Implemented.
// - the updated map of evaluated locals after this attempt | ||
// - whether or not any locals were evaluated in this attempt | ||
// - any errors from the evaluation | ||
func attemptEvaluateLocals( |
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.
Yikes, this is quite complicated. I have some vague memory that the HCL parser has native support for partial evaluation specifically for this use case, but can't find it in the code base quickly...
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 searched but the only partial evaluation I could find was at the top file level, and not at the individual block level where we have expressions.
// Multiple references | ||
x = 1 | ||
y = 2 | ||
z = local.x + local.y |
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.
Consider testing a few corner cases:
- Reference to a
local
that isn't defined in alocals { ... }
block - Reference to a
local
that isn't defined elsewhere in the code - Cyclical references
- Several layers of references (a = local.b, b = local.c, c = local.d, etc)
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.
Done in a107c89
…blocks - Expand comment on why locals evaluation is ignored at the file level - Implement a crude version of Debugf that can be used for debug logging - Add infinite loop protection
if os.Getenv("TG_LOG") == "DEBUG" { | ||
logger.Printf(fmtString, fmtArgs...) | ||
} | ||
} |
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.
Ah, clever 👍
UPDATE a107c89 :
With that, if the build passes, I think we are ready to merge + release! |
LGTM! |
Ok merging and releasing. Thanks for the review! |
This implements the concept of
locals
in terragrunt config. This can be used to bind expressions to names for reusing within the config, so that you can DRY complex expressions within the config.TODO: