-
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
Changes from 6 commits
305f993
54560a9
f922ee1
4ee9257
09c02aa
1d78caf
3ed7011
4366e19
3304003
d859764
6b3bdeb
26d83c8
f165a74
a9b7397
16bba97
0e7c811
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ | |
|
||
import com.google.cloud.tools.jib.configuration.Port; | ||
import com.google.common.collect.ImmutableList; | ||
import com.google.common.collect.ImmutableMap; | ||
import java.time.Instant; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
@@ -30,7 +31,7 @@ public class Image<T extends Layer> { | |
public static class Builder<T extends Layer> { | ||
|
||
private final ImageLayers.Builder<T> imageLayersBuilder = ImageLayers.builder(); | ||
private ImmutableList.Builder<String> environmentBuilder = ImmutableList.builder(); | ||
private ImmutableMap.Builder<String, String> environmentBuilder = ImmutableMap.builder(); | ||
|
||
@Nullable private Instant created; | ||
@Nullable private ImmutableList<String> entrypoint; | ||
|
@@ -56,11 +57,9 @@ public Builder<T> setCreated(Instant created) { | |
*/ | ||
public Builder<T> setEnvironment(@Nullable Map<String, String> environment) { | ||
if (environment == null) { | ||
this.environmentBuilder = ImmutableList.builder(); | ||
this.environmentBuilder = ImmutableMap.builder(); | ||
} else { | ||
for (Map.Entry<String, String> environmentVariable : environment.entrySet()) { | ||
setEnvironmentVariable(environmentVariable.getKey(), environmentVariable.getValue()); | ||
} | ||
this.environmentBuilder.putAll(ImmutableMap.copyOf(environment)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm thinking this is no different from and just could be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds right to me. |
||
} | ||
return this; | ||
} | ||
|
@@ -73,18 +72,7 @@ public Builder<T> setEnvironment(@Nullable Map<String, String> environment) { | |
* @return this | ||
*/ | ||
public Builder<T> setEnvironmentVariable(String name, String value) { | ||
environmentBuilder.add(name + "=" + value); | ||
return this; | ||
} | ||
|
||
/** | ||
* Adds an environment variable definition in the format {@code NAME=VALUE}. | ||
* | ||
* @param environmentVariableDefinition the definition to add | ||
* @return this | ||
*/ | ||
public Builder<T> addEnvironmentVariableDefinition(String environmentVariableDefinition) { | ||
environmentBuilder.add(environmentVariableDefinition); | ||
environmentBuilder.put(name, value); | ||
return this; | ||
} | ||
|
||
|
@@ -167,7 +155,7 @@ public static <T extends Layer> Builder<T> builder() { | |
private final ImageLayers<T> layers; | ||
|
||
/** Environment variable definitions for running the image, in the format {@code NAME=VALUE}. */ | ||
@Nullable private final ImmutableList<String> environment; | ||
@Nullable private final ImmutableMap<String, String> environment; | ||
|
||
/** Initial command to run when running the image. */ | ||
@Nullable private final ImmutableList<String> entrypoint; | ||
|
@@ -181,7 +169,7 @@ public static <T extends Layer> Builder<T> builder() { | |
private Image( | ||
@Nullable Instant created, | ||
ImageLayers<T> layers, | ||
@Nullable ImmutableList<String> environment, | ||
@Nullable ImmutableMap<String, String> environment, | ||
@Nullable ImmutableList<String> entrypoint, | ||
@Nullable ImmutableList<String> javaArguments, | ||
@Nullable ImmutableList<Port> exposedPorts) { | ||
|
@@ -199,7 +187,7 @@ public Instant getCreated() { | |
} | ||
|
||
@Nullable | ||
public ImmutableList<String> getEnvironment() { | ||
public ImmutableMap<String, String> getEnvironment() { | ||
return environment; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,7 @@ | |
import com.google.cloud.tools.jib.image.Image; | ||
import com.google.cloud.tools.jib.json.JsonTemplateMapper; | ||
import com.google.common.annotations.VisibleForTesting; | ||
import com.google.common.collect.ImmutableList; | ||
import com.google.common.collect.ImmutableSortedMap; | ||
import java.lang.reflect.InvocationTargetException; | ||
import java.util.Collections; | ||
|
@@ -67,6 +68,24 @@ public class ImageToJsonTranslator { | |
return result.build(); | ||
} | ||
|
||
/** | ||
* Converts the map of environment variables to a list with items in the format "NAME=VALUE". | ||
* | ||
* @return the list | ||
*/ | ||
@VisibleForTesting | ||
@Nullable | ||
static ImmutableList<String> environmentMapToList(@Nullable Map<String, String> environment) { | ||
if (environment == null) { | ||
return null; | ||
} | ||
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 commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Edit: Not this, the opposite conversion in There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we can just throw an |
||
} | ||
return builder.build(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you like streams, you could simplify this a bit:
|
||
} | ||
|
||
private final Image<CachedLayer> image; | ||
|
||
/** | ||
|
@@ -96,7 +115,7 @@ public Blob getContainerConfigurationBlob() { | |
template.setCreated(image.getCreated() == null ? null : image.getCreated().toString()); | ||
|
||
// Adds the environment variables. | ||
template.setContainerEnvironment(image.getEnvironment()); | ||
template.setContainerEnvironment(environmentMapToList(image.getEnvironment())); | ||
|
||
// Sets the entrypoint. | ||
template.setContainerEntrypoint(image.getEntrypoint()); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,6 +49,16 @@ public class JsonToImageTranslator { | |
*/ | ||
private static final Pattern portPattern = Pattern.compile("(\\d+)(?:/(tcp|udp))?"); | ||
|
||
/** | ||
* Pattern used for parsing environment variables in the format {@code NAME=VALUE}. {@code NAME} | ||
* should start with a letter and contain only letters (uppercase and lower) and numbers. {@code | ||
* VALUE} can contain anything. | ||
* | ||
* <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 commentThe 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) - There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here it says:
And in the link you shared:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, for environment variables used in shell utilities (like There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe 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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. That matches "====", I think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea, I think having = in the value is okay |
||
|
||
/** | ||
* Translates {@link V21ManifestTemplate} to {@link Image}. | ||
* | ||
|
@@ -139,7 +149,14 @@ public static Image<Layer> toImage( | |
|
||
if (containerConfigurationTemplate.getContainerEnvironment() != null) { | ||
for (String environmentVariable : containerConfigurationTemplate.getContainerEnvironment()) { | ||
imageBuilder.addEnvironmentVariableDefinition(environmentVariable); | ||
Matcher matcher = environmentPattern.matcher(environmentVariable); | ||
if (!matcher.matches()) { | ||
throw new BadContainerConfigurationFormatException( | ||
"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 commentThe 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., There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
imageBuilder.setEnvironmentVariable(name, value); | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ | |
import com.google.cloud.tools.jib.configuration.BuildConfiguration; | ||
import com.google.cloud.tools.jib.image.DescriptorDigest; | ||
import com.google.cloud.tools.jib.image.Image; | ||
import com.google.cloud.tools.jib.image.Layer; | ||
import com.google.common.collect.ImmutableList; | ||
import com.google.common.collect.ImmutableMap; | ||
import com.google.common.util.concurrent.Futures; | ||
|
@@ -46,6 +47,8 @@ public class BuildImageStepTest { | |
|
||
@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 commentThe reason will be displayed to describe this comment to others. Learn more. We could prob just have a real |
||
@Mock private PullAndCacheBaseImageLayersStep mockPullAndCacheBaseImageLayersStep; | ||
@Mock private PullAndCacheBaseImageLayerStep mockPullAndCacheBaseImageLayerStep; | ||
@Mock private BuildAndCacheApplicationLayerStep mockBuildAndCacheApplicationLayerStep; | ||
|
@@ -70,15 +73,19 @@ public void setUp() throws DigestException { | |
Mockito.when(mockBuildConfiguration.getExposedPorts()).thenReturn(ImmutableList.of()); | ||
Mockito.when(mockBuildConfiguration.getEntrypoint()).thenReturn(ImmutableList.of()); | ||
|
||
Mockito.when(mockPullAndCacheBaseImageLayerStep.getFuture()) | ||
.thenReturn(Futures.immediateFuture(testCachedLayer)); | ||
Mockito.when(mockPullAndCacheBaseImageLayersStep.getFuture()) | ||
.thenReturn( | ||
Futures.immediateFuture( | ||
ImmutableList.of( | ||
mockPullAndCacheBaseImageLayerStep, | ||
mockPullAndCacheBaseImageLayerStep, | ||
mockPullAndCacheBaseImageLayerStep))); | ||
Mockito.when(mockPullAndCacheBaseImageLayerStep.getFuture()) | ||
.thenReturn(Futures.immediateFuture(testCachedLayer)); | ||
Mockito.when(mockPullBaseImageStep.getFuture()) | ||
.thenReturn( | ||
Futures.immediateFuture( | ||
new PullBaseImageStep.BaseImageWithAuthorization(mockBaseImage, null))); | ||
Mockito.when(mockBuildAndCacheApplicationLayerStep.getFuture()) | ||
.thenReturn(Futures.immediateFuture(testCachedLayer)); | ||
} | ||
|
@@ -89,6 +96,7 @@ public void test_validateAsyncDependencies() throws ExecutionException, Interrup | |
new BuildImageStep( | ||
MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()), | ||
mockBuildConfiguration, | ||
mockPullBaseImageStep, | ||
mockPullAndCacheBaseImageLayersStep, | ||
ImmutableList.of( | ||
mockBuildAndCacheApplicationLayerStep, | ||
|
@@ -98,4 +106,24 @@ public void test_validateAsyncDependencies() throws ExecutionException, Interrup | |
Assert.assertEquals( | ||
testDescriptorDigest, image.getLayers().asList().get(0).getBlobDescriptor().getDigest()); | ||
} | ||
|
||
@Test | ||
public void test_propagateBaseImageConfiguration() | ||
throws ExecutionException, InterruptedException { | ||
Mockito.when(mockBuildConfiguration.getEnvironment()).thenReturn(null); | ||
Mockito.when(mockBaseImage.getEnvironment()).thenReturn(ImmutableMap.of("NAME", "VALUE")); | ||
|
||
BuildImageStep buildImageStep = | ||
new BuildImageStep( | ||
MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()), | ||
mockBuildConfiguration, | ||
mockPullBaseImageStep, | ||
mockPullAndCacheBaseImageLayersStep, | ||
ImmutableList.of( | ||
mockBuildAndCacheApplicationLayerStep, | ||
mockBuildAndCacheApplicationLayerStep, | ||
mockBuildAndCacheApplicationLayerStep)); | ||
Image<CachedLayer> image = buildImageStep.getFuture().get().getFuture().get(); | ||
Assert.assertEquals(ImmutableMap.of("NAME", "VALUE"), image.getEnvironment()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,6 +37,7 @@ | |
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.regex.Matcher; | ||
import org.junit.Assert; | ||
import org.junit.Test; | ||
|
||
|
@@ -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 commentThe reason will be displayed to describe this comment to others. Learn more. Might want to call this like |
||
assertGoodPattern("A1203921=www=ww", "A1203921", "www=ww"); | ||
assertGoodPattern("abcABC123=", "abcABC123", ""); | ||
assertGoodPattern("m_a_8943=100", "m_a_8943", "100"); | ||
assertGoodPattern("A_B_C_D=*****", "A_B_C_D", "*****"); | ||
|
||
assertBadPattern("123=ABC"); | ||
assertBadPattern("================="); | ||
assertBadPattern("A_B_C"); | ||
assertBadPattern("$$$$$=fjdkslajfkdsl"); | ||
} | ||
|
||
private void assertGoodPattern(String input, String expectedName, String expectedValue) { | ||
Matcher matcher = JsonToImageTranslator.environmentPattern.matcher(input); | ||
Assert.assertTrue(matcher.matches()); | ||
Assert.assertEquals(expectedName, matcher.group(1)); | ||
Assert.assertEquals(expectedValue, matcher.group(2)); | ||
} | ||
|
||
private void assertBadPattern(String input) { | ||
Matcher matcher = JsonToImageTranslator.environmentPattern.matcher(input); | ||
Assert.assertFalse(matcher.matches()); | ||
} | ||
|
||
private <T extends BuildableManifestTemplate> void testToImage_buildable( | ||
String jsonFilename, Class<T> manifestTemplateClass) | ||
throws IOException, LayerPropertyNotFoundException, LayerCountMismatchException, | ||
|
@@ -144,7 +171,7 @@ private <T extends BuildableManifestTemplate> void testToImage_buildable( | |
layers.get(0).getDiffId()); | ||
Assert.assertEquals(Instant.ofEpochSecond(20), image.getCreated()); | ||
Assert.assertEquals(Arrays.asList("some", "entrypoint", "command"), image.getEntrypoint()); | ||
Assert.assertEquals(Arrays.asList("VAR1=VAL1", "VAR2=VAL2"), image.getEnvironment()); | ||
Assert.assertEquals(ImmutableMap.of("VAR1", "VAL1", "VAR2", "VAL2"), image.getEnvironment()); | ||
Assert.assertEquals( | ||
ImmutableList.of( | ||
new Port(1000, Protocol.TCP), | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 commentThe reason will be displayed to describe this comment to others. Learn more. nit: |
||
|
||
## 0.9.7 | ||
|
||
|
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 frombuildConfiguration
.