-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Propagate base image's environment variable config to built image #716
Conversation
…opagate-container-config
…opagate-container-config
@@ -96,8 +100,15 @@ | |||
buildAndCacheApplicationLayerSteps) { | |||
imageBuilder.addLayer(NonBlockingSteps.get(buildAndCacheApplicationLayerStep)); | |||
} | |||
|
|||
// Use environment from base image if not configured | |||
if (buildConfiguration.getEnvironment() == null) { |
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.
Later we will have the ability to add (not overwrite) env variables, right?
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.
Possibly, I'm not sure what the plan is for that.
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.
Yea, I think environment variables should be overwritten only at the specific variable level, and not at the entire environment level.
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.
So it could be like initialize imageBuilder
with the base image, then apply overwrites from buildConfiguration
.
} | ||
ImmutableList.Builder<String> builder = ImmutableList.builder(); | ||
for (Map.Entry<String, String> environmentVariable : environment.entrySet()) { | ||
builder.add(environmentVariable.getKey() + "=" + environmentVariable.getValue()); |
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 see this PR does not change the current behavior, but I'm just wondering if any kind of escaping should also be considered. Do we have to convert this to a list? Maybe changing some API, we could pass the map and don't worry about escaping at all, although I admit it'd be very rare to have =
in a variable name.
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.
Yeah, I definitely think this can be more bullet proof. Actually I think this breaks if there's an =
in the value, not just the name.
Edit: Not this, the opposite conversion in JsonToImageTranslator
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.
Hmm I think we might want to guard this at the environment-setting level - so maybe we might need to have a separate Environment
class to be this abstraction (like mentioned in #15)
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.
We can handle this when we handle #15 then?
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.
Maybe we can just throw an IllegalArgumentException
for now?
} | ||
ImmutableList.Builder<String> builder = ImmutableList.builder(); | ||
for (Map.Entry<String, String> environmentVariable : environment.entrySet()) { | ||
builder.add(environmentVariable.getKey() + "=" + environmentVariable.getValue()); |
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.
Hmm I think we might want to guard this at the environment-setting level - so maybe we might need to have a separate Environment
class to be this abstraction (like mentioned in #15)
* <p>Example matches: NAME=VALUE, A12345=$$$$$ | ||
*/ | ||
@VisibleForTesting | ||
static final Pattern environmentPattern = Pattern.compile("([A-Za-z][A-Za-z0-9_]+)=(.*)"); |
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 believe environment variable names can actually contain any character (http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap08.html) - These strings have the form name=value; names shall not contain the character '='.
, so we should probably have this pattern be like (.*?)=(.*)
.
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.
Here it says:
env: (array of strings, OPTIONAL) with the same semantics as IEEE Std 1003.1-2008's environ.
And in the link you shared:
Environment variable names used by the utilities in the Shell and Utilities volume of IEEE Std 1003.1-2001 consist solely of uppercase letters, digits, and the '_' (underscore) from the characters defined in Portable Character Set and do not begin with a digit
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, for environment variables used in shell utilities (like bash
), the names shouldn't consist of anything besides the alphanumerics, but for process environment variables, the definition is more lenient to allow any character besides =
.
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.
reading is hard
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.
Shouldn't we make sure the name has at least one character though? I think what you gave will match something like "=ABC". Is that valid?
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.
That’s a good point - let’s change the star to a plus.
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.
That matches "====", I think ([^=].*?)=(.*)
should work
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.
Yea, I think having = in the value is okay
@@ -46,6 +47,8 @@ | |||
|
|||
@Mock private BuildConfiguration mockBuildConfiguration; | |||
@Mock private BuildLogger mockBuildLogger; | |||
@Mock private PullBaseImageStep mockPullBaseImageStep; | |||
@Mock private Image<Layer> mockBaseImage; |
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.
We could prob just have a real Image
since there's nothing in Image that needs to be mocked.
@@ -110,6 +111,32 @@ public void testPortMapToList() throws BadContainerConfigurationFormatException | |||
} | |||
} | |||
|
|||
@Test | |||
public void testJsonToImageTranslatorRegex() { | |||
assertGoodPattern("NAME=VALUE", "NAME", "VALUE"); |
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.
Might want to call this like assertGoodEnvironmentPattern
jib-gradle-plugin/CHANGELOG.md
Outdated
@@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file. | |||
|
|||
- Only builds non-empty layers ([#516](https://github.com/GoogleContainerTools/jib/pull/516/files)) | |||
- Fixed slow image reference parsing ([#680](https://github.com/GoogleContainerTools/jib/pull/680)) | |||
- Environment variable configuration is propagated from the base image ([#716](https://github.com/GoogleContainerTools/jib/pull/716)) |
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.
nit: Propagates environment variables from base image...
* | ||
* <p>Example matches: NAME=VALUE, A12345=$$$$$ | ||
*/ | ||
@VisibleForTesting static final Pattern environmentPattern = Pattern.compile("([^=].*?)=(.*)"); |
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 seems weird. ([^=].*?)
matches any string that doesn't start with =
. I think it's conceptually correct to say ([^=]+)
.
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.
So as a concrete example,
Pattern pattern = Pattern.compile("([^=].*?)");
Matcher matcher = pattern.matcher("ab==c==d");
matcher.matches();
System.out.println(matcher.group(1));
It prints out ab==c==d
.
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.
Yeah I guess within the context of the rest of the expression, a name containing an =
would only be detected if it started with an =
. But yours makes more sense and is easier to read.
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.
Suggestions only, take 'em or leave 'em as you see fit.
"Invalid environment variable definition: " + environmentVariable); | ||
} | ||
String name = matcher.group(1); | ||
String value = matcher.group(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.
When patterns are far away from their use, it can help to use a named group (e.g., matcher.group("name")
and matcher.group("value")
).
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.
+1 We should probably do this for some of the other regex patterns too.
if (buildConfiguration.getEnvironment() != null) { | ||
for (Map.Entry<String, String> entry : buildConfiguration.getEnvironment().entrySet()) { | ||
imageBuilder.setEnvironmentVariable(entry.getKey(), entry.getValue()); | ||
} |
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 wonder if this could be simplified by instead having just a imageBuilder.addEnvironment()
:
+ // Initialize to environment from base image
+ imageBuilder.addEnvironment(
+ NonBlockingSteps.get(pullBaseImageStep).getBaseImage().getEnvironment());
+ // override with configured environment
+ if (buildConfiguration.getEnvironment() != null) {
+ imageBuilder.addEnvironment(buildConfiguration.getEnvironment());
+ }
for (Map.Entry<String, String> environmentVariable : environment.entrySet()) { | ||
if (environmentVariable.getKey().contains("=")) { | ||
throw new IllegalArgumentException("Environment variable name cannot contain '='"); | ||
} |
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 exactly what Preconditions.checkArgument()
does?
} | ||
builder.add(environmentVariable.getKey() + "=" + environmentVariable.getValue()); | ||
} | ||
return builder.build(); |
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 like streams, you could simplify this a bit:
Preconditions.checkArgument(
Iterables.any(environment.keySet(), s -> s.contains("=")),
"Environment variable name cannot contain '='");
return environment
.entrySet()
.stream()
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(ImmutableList.toImmutableList());
ImmutableList.Builder<String> builder = ImmutableList.builder(); | ||
for (Map.Entry<String, String> environmentVariable : environment.entrySet()) { | ||
if (environmentVariable.getKey().contains("=")) { | ||
throw new IllegalArgumentException("Environment variable name cannot contain '='"); |
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.
It might be good to include the illegal name in the message.
* | ||
* <p>Example matches: NAME=VALUE, A12345=$$$$$ | ||
*/ | ||
@VisibleForTesting static final Pattern environmentPattern = Pattern.compile("([^=]+)=(.*)"); |
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.
nit: constants should be in all caps
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.
Gonna leave LGTM to after addressing @briandealwis 's comments.
} | ||
public Builder<T> addEnvironment(@Nullable Map<String, String> environment) { | ||
if (environment != null) { | ||
this.environmentBuilder.putAll(ImmutableMap.copyOf(environment)); |
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'm thinking this is no different from and just could be environmentBuilder.putAll(environment)
, am I right? You are just putting references to objects (String
s in this case) into the builder, not like you are doing a deep copy of String
instances. (And String
s are immutable, so there is no point of deep copying, actually.) Am I right?
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.
Sounds right to me.
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 it looks good generally.
return null; | ||
} | ||
Preconditions.checkArgument( | ||
environment.keySet().stream().noneMatch(s -> s.contains("=")), |
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.
key -> key.contains("=")
seems more readable.
* <p>Example matches: NAME=VALUE, A12345=$$$$$ | ||
*/ | ||
@VisibleForTesting | ||
static final Pattern environmentPattern = Pattern.compile("(?<name>[^=]+)=(?<value>.*)"); |
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.
ENVIRONMENT_PATTERN
too.
imageBuilder.addEnvironment( | ||
NonBlockingSteps.get(pullBaseImageStep).getBaseImage().getEnvironment()); | ||
if (buildConfiguration.getEnvironment() != null) { | ||
imageBuilder.addEnvironment(buildConfiguration.getEnvironment()); |
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.
Since the parameter of addEnvironment()
is @Nullable
, we could probably remove the null check.
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 - will let someone else give final approval
return null; | ||
} | ||
Preconditions.checkArgument( | ||
environment.keySet().stream().noneMatch(key -> key.contains("=")), |
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.
Or, even more clear may be s/key/variableName/
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 like key
more since it's descriptive about which part of the map itself it's dealing with. And we can still tell that the key corresponds to the name with the error message right below.
Part of #595. Also did a small bit of refactoring/error checking for translating between environment variable list/map