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

Propagate base image's environment variable config to built image #716

Merged
merged 16 commits into from
Jul 26, 2018

Conversation

TadCordle
Copy link
Contributor

Part of #595. Also did a small bit of refactoring/error checking for translating between environment variable list/map

@TadCordle TadCordle added this to the v0.9.8 milestone Jul 25, 2018
@@ -96,8 +100,15 @@
buildAndCacheApplicationLayerSteps) {
imageBuilder.addLayer(NonBlockingSteps.get(buildAndCacheApplicationLayerStep));
}

// Use environment from base image if not configured
if (buildConfiguration.getEnvironment() == null) {
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor

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());
Copy link
Member

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.

Copy link
Contributor Author

@TadCordle TadCordle Jul 25, 2018

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

Copy link
Contributor

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)

Copy link
Contributor Author

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?

Copy link
Contributor

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());
Copy link
Contributor

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_]+)=(.*)");
Copy link
Contributor

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 (.*?)=(.*).

Copy link
Contributor Author

@TadCordle TadCordle Jul 25, 2018

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

Copy link
Contributor

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 =.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

reading is hard

Copy link
Contributor Author

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?

Copy link
Contributor

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.

Copy link
Contributor Author

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

Copy link
Contributor

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;
Copy link
Contributor

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");
Copy link
Contributor

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

@@ -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))
Copy link
Contributor

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("([^=].*?)=(.*)");
Copy link
Member

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 ([^=]+).

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

@briandealwis briandealwis left a 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);
Copy link
Member

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")).

Copy link
Contributor

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());
}
Copy link
Member

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 '='");
}
Copy link
Member

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();
Copy link
Member

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 '='");
Copy link
Contributor

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("([^=]+)=(.*)");
Copy link
Contributor

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

Copy link
Contributor

@coollog coollog left a 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));
Copy link
Member

@chanseokoh chanseokoh Jul 26, 2018

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 (Strings in this case) into the builder, not like you are doing a deep copy of String instances. (And Strings are immutable, so there is no point of deep copying, actually.) Am I right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds right to me.

Copy link
Member

@chanseokoh chanseokoh 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 it looks good generally.

return null;
}
Preconditions.checkArgument(
environment.keySet().stream().noneMatch(s -> s.contains("=")),
Copy link
Member

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>.*)");
Copy link
Member

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());
Copy link
Member

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.

Copy link
Contributor

@coollog coollog left a 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("=")),
Copy link
Contributor

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/

Copy link
Contributor Author

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.

@chanseokoh chanseokoh dismissed coollog’s stale review July 26, 2018 15:32

Approval delegated

@TadCordle TadCordle merged commit 82ce0a0 into master Jul 26, 2018
@TadCordle TadCordle deleted the propagate-container-config branch July 26, 2018 15:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants