-
Notifications
You must be signed in to change notification settings - Fork 289
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
Preserve plugin config env var names with consecutive underscores #2116
Preserve plugin config env var names with consecutive underscores #2116
Conversation
When an env var name, that was derived from a plugin config, had consecutive underscores, the consecutive underscores were replaced by a single one. Now, both versions of the variable before and after this transformation are exported.
Previously we ignored errors from this method, but now we just log them. Either way, execution continues, exposing us to the possibility of a nil pointer dereference. Since go is not expressive enough to eliminate the possibility of a nil pointer dereference, we have to be extra careful to ensure we don't propagate nil pointer in this manner.
22ff9e4
to
f29a37c
Compare
It is ultimately returned as a map, so the sort order is lost
d474bca
to
1051cb3
Compare
agent/plugin/plugin.go
Outdated
nonIDCharacterRE = regexp.MustCompile(`[^a-zA-Z0-9]`) | ||
consecutiveHyphenRE = regexp.MustCompile(`-+`) | ||
dashOrSpace = regexp.MustCompile(`-|\s`) | ||
whitespaceRE = regexp.MustCompile(`\s+`) | ||
consecutiveUnderscoreRE = regexp.MustCompile(`_+`) |
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.
good renames 👍
agent/plugin/plugin.go
Outdated
noConsecutiveUnderScoreKey := consecutiveUnderscoreRE.ReplaceAllString(k, "_") | ||
if k != noConsecutiveUnderScoreKey { | ||
envSlice = append(envSlice, fmt.Sprintf("%s=%s", noConsecutiveUnderScoreKey, v)) | ||
err = errors.Join(err, &DeprecatedNameError{old: noConsecutiveUnderScoreKey, new: k}) |
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.
:O TIL errors.Join
made it into the stdlib
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 1.20. But it wasn't powerful enough for me here :(
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 testing, I needed a way to make sure that the expected set of errors is exactly the set of the actual errors. But I could only see how to errors.Is
on an error created with errors.Join
in a way that allows you to conclude that the expected set of errors is a subset of the actual errors.
`[ { "%s": %s } ]`, | ||
"github.com/buildkite-plugins/docker-compose-buildkite-plugin", | ||
configJSON, | ||
) |
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 just cosmetic.
cfgJSON1, | ||
"github.com/buildkite-plugins/docker-compose-buildkite-plugin", | ||
cfgJSON2, | ||
) |
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 just cosmetic.
2d3527c
to
089db2a
Compare
The subsequent sort would sort the original array
Co-authored-by: Ben Moskovitz <ben@mosk.nz>
60ff367
to
ed9ae19
Compare
The deprecation message isn't something that is actionable by people who create builds (builders). It's only relevant if the plugin is depending on the deprecated variable name, and the builder would not normally be aware of this. Furthermore, it's often the case that the plugin will work fine even though the environment variable name is "deprecated", so this does not need to be a warning.
func (e *DeprecatedNameErrors) Error() string { | ||
builder := strings.Builder{} | ||
for i, err := range e.Errors() { | ||
_, _ = builder.WriteString(err.Error()) |
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.
Builder.WriteString (and WriteRune) are documented as returning nil error always, so the underscores seem extra unnecessary here.
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 don't think it's too distracting to leave it this way. It makes the life of static analysis tools easier.
// Errors returns the contained set of errors in sorted order | ||
func (e *DeprecatedNameErrors) Errors() []DeprecatedNameError { | ||
if e == nil { | ||
return []DeprecatedNameError{} |
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 not return 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.
Yep, and I made it handle when e.errs
is nil
too.
…Environment in bootstrap
…ains a nil map in Errors()
4d36759
to
18ad336
Compare
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.
one tiny whitespace thing, but other than that this LGTM 🎉
Co-authored-by: Ben Moskovitz <ben@mosk.nz>
Other languages have much better ways to do 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.
I haven't looked closely at the implementation, but I like what it does 👍🏼
And it looks like there's good test coverage.
"You may be able avoid this by removing consecutive underscore, hypen, or whitespace " + | ||
"characters in your plugin configuration.", | ||
) | ||
b.shell.Logger.Printf("%s", strings.Join([]string{ |
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.
Just to understand, I'm guessing the problem with using a single string literal is how long it would be?
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.
Yep. Wrapped lines are irritating for many reasons
When an env var name, that was derived from a plugin config, had consecutive underscores, the consecutive underscores were replaced by a single one. (For later reference, this will be called the underscore collapsing transformation). Now, both versions of the variable before and after this transformation are exported.
We also log a warning about this and log both the deprecated variable name and its replacement.
We need to keep the deprecated version around, as these environment variables are how plugins read their config without depending on a tool like
jq
to parse the config.So removing them would mean that plugins could be inadvertently misconfigured, and they would have to be rewritten to start using the replacement environment variables instead.
Implementing this was not that straightforward, as the function that creates the environment slice from the plugin config is recursive and did not take a logger or return errors. It is recursive as that is the most straightforward way to turn a tree like object such as
Into the list of environment variables, similar to:
I could have adapted it to do something when it hits a leaf node and the path to root had consecutive underscores, but I think that would complicate the recursion too much.
Instead, I just removed the underscore collapsing transformation and added a pass over the result that "fans-out" environment variable names with consecutive underscores into two environment variables with and without the underscore collapsing transformation applied.
So in the example above, there will now be an additional environment variable:
Also, for passing the errors back to bootstrap, I made two custom error types. It's arguably a little overengineered, but I tried to find a way to use error wrapping and/or
errors.Join
to do the job, but they weren't powerful enough, or I was holding them wrong. There could be some generics libraries we could import to eliminate a lot of the code written for the error types. I would be glad to do this.Fixes: #2100
Screenshots
Before
After