Add network option for image building
This commit adds configuration to the Maven and Gradle plugins to allow specifying the network mode to be provided to the image building goal and task. See gh-27486
This commit is contained in:
committed by
Scott Frederick
parent
e737388f5c
commit
8e6d03b221
@@ -36,6 +36,7 @@ import org.springframework.util.Assert;
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
* @author Andrey Shlykov
|
||||
* @author Jeroen Meijer
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class BuildRequest {
|
||||
@@ -68,6 +69,8 @@ public class BuildRequest {
|
||||
|
||||
private final List<Binding> bindings;
|
||||
|
||||
private final String network;
|
||||
|
||||
BuildRequest(ImageReference name, Function<Owner, TarArchive> applicationContent) {
|
||||
Assert.notNull(name, "Name must not be null");
|
||||
Assert.notNull(applicationContent, "ApplicationContent must not be null");
|
||||
@@ -83,12 +86,13 @@ public class BuildRequest {
|
||||
this.creator = Creator.withVersion("");
|
||||
this.buildpacks = Collections.emptyList();
|
||||
this.bindings = Collections.emptyList();
|
||||
this.network = null;
|
||||
}
|
||||
|
||||
BuildRequest(ImageReference name, Function<Owner, TarArchive> applicationContent, ImageReference builder,
|
||||
ImageReference runImage, Creator creator, Map<String, String> env, boolean cleanCache,
|
||||
boolean verboseLogging, PullPolicy pullPolicy, boolean publish, List<BuildpackReference> buildpacks,
|
||||
List<Binding> bindings) {
|
||||
List<Binding> bindings, String network) {
|
||||
this.name = name;
|
||||
this.applicationContent = applicationContent;
|
||||
this.builder = builder;
|
||||
@@ -101,6 +105,7 @@ public class BuildRequest {
|
||||
this.publish = publish;
|
||||
this.buildpacks = buildpacks;
|
||||
this.bindings = bindings;
|
||||
this.network = network;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,7 +117,7 @@ public class BuildRequest {
|
||||
Assert.notNull(builder, "Builder must not be null");
|
||||
return new BuildRequest(this.name, this.applicationContent, builder.inTaggedOrDigestForm(), this.runImage,
|
||||
this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish,
|
||||
this.buildpacks, this.bindings);
|
||||
this.buildpacks, this.bindings, this.network);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,7 +128,7 @@ public class BuildRequest {
|
||||
public BuildRequest withRunImage(ImageReference runImageName) {
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, runImageName.inTaggedOrDigestForm(),
|
||||
this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish,
|
||||
this.buildpacks, this.bindings);
|
||||
this.buildpacks, this.bindings, this.network);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,7 +139,8 @@ public class BuildRequest {
|
||||
public BuildRequest withCreator(Creator creator) {
|
||||
Assert.notNull(creator, "Creator must not be null");
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, creator, this.env,
|
||||
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings);
|
||||
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
|
||||
this.network);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,7 +156,7 @@ public class BuildRequest {
|
||||
env.put(name, value);
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator,
|
||||
Collections.unmodifiableMap(env), this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish,
|
||||
this.buildpacks, this.bindings);
|
||||
this.buildpacks, this.bindings, this.network);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,7 +170,7 @@ public class BuildRequest {
|
||||
updatedEnv.putAll(env);
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator,
|
||||
Collections.unmodifiableMap(updatedEnv), this.cleanCache, this.verboseLogging, this.pullPolicy,
|
||||
this.publish, this.buildpacks, this.bindings);
|
||||
this.publish, this.buildpacks, this.bindings, this.network);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,7 +180,8 @@ public class BuildRequest {
|
||||
*/
|
||||
public BuildRequest withCleanCache(boolean cleanCache) {
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
|
||||
cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings);
|
||||
cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
|
||||
this.network);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,7 +191,8 @@ public class BuildRequest {
|
||||
*/
|
||||
public BuildRequest withVerboseLogging(boolean verboseLogging) {
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
|
||||
this.cleanCache, verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings);
|
||||
this.cleanCache, verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
|
||||
this.network);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,7 +202,8 @@ public class BuildRequest {
|
||||
*/
|
||||
public BuildRequest withPullPolicy(PullPolicy pullPolicy) {
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
|
||||
this.cleanCache, this.verboseLogging, pullPolicy, this.publish, this.buildpacks, this.bindings);
|
||||
this.cleanCache, this.verboseLogging, pullPolicy, this.publish, this.buildpacks, this.bindings,
|
||||
this.network);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,7 +213,8 @@ public class BuildRequest {
|
||||
*/
|
||||
public BuildRequest withPublish(boolean publish) {
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
|
||||
this.cleanCache, this.verboseLogging, this.pullPolicy, publish, this.buildpacks, this.bindings);
|
||||
this.cleanCache, this.verboseLogging, this.pullPolicy, publish, this.buildpacks, this.bindings,
|
||||
this.network);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -227,7 +237,8 @@ public class BuildRequest {
|
||||
public BuildRequest withBuildpacks(List<BuildpackReference> buildpacks) {
|
||||
Assert.notNull(buildpacks, "Buildpacks must not be null");
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
|
||||
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, buildpacks, this.bindings);
|
||||
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, buildpacks, this.bindings,
|
||||
this.network);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,7 +261,14 @@ public class BuildRequest {
|
||||
public BuildRequest withBindings(List<Binding> bindings) {
|
||||
Assert.notNull(bindings, "Bindings must not be null");
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
|
||||
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, bindings);
|
||||
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, bindings,
|
||||
this.network);
|
||||
}
|
||||
|
||||
public BuildRequest withNetwork(String network) {
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
|
||||
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
|
||||
network);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -353,6 +371,10 @@ public class BuildRequest {
|
||||
return this.bindings;
|
||||
}
|
||||
|
||||
public String getNetwork() {
|
||||
return this.network;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@link BuildRequest} from a JAR file.
|
||||
* @param jarFile the source jar file
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
* @author Jeroen Meijer
|
||||
*/
|
||||
class Lifecycle implements Closeable {
|
||||
|
||||
@@ -147,6 +148,7 @@ class Lifecycle implements Closeable {
|
||||
this.request.getBindings().forEach(phase::withBinding);
|
||||
}
|
||||
phase.withEnv(PLATFORM_API_VERSION_KEY, this.platformVersion.toString());
|
||||
phase.withNetworkMode(this.request.getNetwork());
|
||||
return phase;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
* @author Jeroen Meijer
|
||||
*/
|
||||
class Phase {
|
||||
|
||||
@@ -48,6 +49,8 @@ class Phase {
|
||||
|
||||
private final Map<String, String> env = new LinkedHashMap<>();
|
||||
|
||||
private String networkMode;
|
||||
|
||||
/**
|
||||
* Create a new {@link Phase} instance.
|
||||
* @param name the name of the phase
|
||||
@@ -101,6 +104,10 @@ class Phase {
|
||||
this.env.put(name, value);
|
||||
}
|
||||
|
||||
void withNetworkMode(String networkMode) {
|
||||
this.networkMode = networkMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the phase.
|
||||
* @return the phase name
|
||||
@@ -127,6 +134,7 @@ class Phase {
|
||||
update.withLabel("author", "spring-boot");
|
||||
this.bindings.forEach(update::withBinding);
|
||||
this.env.forEach(update::withEnv);
|
||||
update.withNetworkMode(this.networkMode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
* @author Jeroen Meijer
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class ContainerConfig {
|
||||
@@ -47,7 +48,7 @@ public class ContainerConfig {
|
||||
private final String json;
|
||||
|
||||
ContainerConfig(String user, ImageReference image, String command, List<String> args, Map<String, String> labels,
|
||||
List<Binding> bindings, Map<String, String> env) throws IOException {
|
||||
List<Binding> bindings, Map<String, String> env, String networkMode) throws IOException {
|
||||
Assert.notNull(image, "Image must not be null");
|
||||
Assert.hasText(command, "Command must not be empty");
|
||||
ObjectMapper objectMapper = SharedObjectMapper.get();
|
||||
@@ -64,6 +65,9 @@ public class ContainerConfig {
|
||||
ObjectNode labelsNode = node.putObject("Labels");
|
||||
labels.forEach(labelsNode::put);
|
||||
ObjectNode hostConfigNode = node.putObject("HostConfig");
|
||||
if (networkMode != null) {
|
||||
hostConfigNode.put("NetworkMode", networkMode);
|
||||
}
|
||||
ArrayNode bindsNode = hostConfigNode.putArray("Binds");
|
||||
bindings.forEach((binding) -> bindsNode.add(binding.toString()));
|
||||
this.json = objectMapper.writeValueAsString(node);
|
||||
@@ -114,6 +118,8 @@ public class ContainerConfig {
|
||||
|
||||
private final Map<String, String> env = new LinkedHashMap<>();
|
||||
|
||||
private String networkMode;
|
||||
|
||||
Update(ImageReference image) {
|
||||
this.image = image;
|
||||
}
|
||||
@@ -122,7 +128,7 @@ public class ContainerConfig {
|
||||
update.accept(this);
|
||||
try {
|
||||
return new ContainerConfig(this.user, this.image, this.command, this.args, this.labels, this.bindings,
|
||||
this.env);
|
||||
this.env, this.networkMode);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
@@ -182,6 +188,10 @@ public class ContainerConfig {
|
||||
this.env.put(name, value);
|
||||
}
|
||||
|
||||
public void withNetworkMode(String networkMode) {
|
||||
this.networkMode = networkMode;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ import static org.assertj.core.api.Assertions.entry;
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
* @author Jeroen Meijer
|
||||
*/
|
||||
public class BuildRequestTests {
|
||||
|
||||
@@ -199,6 +200,12 @@ public class BuildRequestTests {
|
||||
.withMessage("Bindings must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNetworkUpdatesNetwork() throws IOException {
|
||||
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")).withNetwork("test");
|
||||
assertThat(request.getNetwork()).isEqualTo("test");
|
||||
}
|
||||
|
||||
private void hasExpectedJarContent(TarArchive archive) {
|
||||
try {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
@@ -62,6 +62,7 @@ import static org.mockito.Mockito.verify;
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
* @author Jeroen Meijer
|
||||
*/
|
||||
class LifecycleTests {
|
||||
|
||||
@@ -188,6 +189,17 @@ class LifecycleTests {
|
||||
verify(this.docker.volume()).delete(VolumeName.of("pack-app-aaaaaaaaaa"), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeWithNetworkExecutesPhases() throws Exception {
|
||||
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
|
||||
BuildRequest request = getTestRequest().withNetwork("test");
|
||||
createLifecycle(request).execute();
|
||||
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-network.json"));
|
||||
assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
|
||||
}
|
||||
|
||||
private DockerApi mockDockerApi() {
|
||||
DockerApi docker = mock(DockerApi.class);
|
||||
ImageApi imageApi = mock(ImageApi.class);
|
||||
|
||||
@@ -32,6 +32,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
* @author Jeroen Meijer
|
||||
*/
|
||||
class PhaseTests {
|
||||
|
||||
@@ -56,6 +57,7 @@ class PhaseTests {
|
||||
phase.apply(update);
|
||||
verify(update).withCommand("/cnb/lifecycle/test", NO_ARGS);
|
||||
verify(update).withLabel("author", "spring-boot");
|
||||
verify(update).withNetworkMode(null);
|
||||
verifyNoMoreInteractions(update);
|
||||
}
|
||||
|
||||
@@ -69,6 +71,7 @@ class PhaseTests {
|
||||
verify(update).withBinding(Binding.from("/var/run/docker.sock", "/var/run/docker.sock"));
|
||||
verify(update).withCommand("/cnb/lifecycle/test", NO_ARGS);
|
||||
verify(update).withLabel("author", "spring-boot");
|
||||
verify(update).withNetworkMode(null);
|
||||
verifyNoMoreInteractions(update);
|
||||
}
|
||||
|
||||
@@ -80,6 +83,7 @@ class PhaseTests {
|
||||
phase.apply(update);
|
||||
verify(update).withCommand("/cnb/lifecycle/test", "-log-level", "debug");
|
||||
verify(update).withLabel("author", "spring-boot");
|
||||
verify(update).withNetworkMode(null);
|
||||
verifyNoMoreInteractions(update);
|
||||
}
|
||||
|
||||
@@ -91,6 +95,7 @@ class PhaseTests {
|
||||
phase.apply(update);
|
||||
verify(update).withCommand("/cnb/lifecycle/test");
|
||||
verify(update).withLabel("author", "spring-boot");
|
||||
verify(update).withNetworkMode(null);
|
||||
verifyNoMoreInteractions(update);
|
||||
}
|
||||
|
||||
@@ -102,6 +107,7 @@ class PhaseTests {
|
||||
phase.apply(update);
|
||||
verify(update).withCommand("/cnb/lifecycle/test", "a", "b", "c");
|
||||
verify(update).withLabel("author", "spring-boot");
|
||||
verify(update).withNetworkMode(null);
|
||||
verifyNoMoreInteractions(update);
|
||||
}
|
||||
|
||||
@@ -115,6 +121,7 @@ class PhaseTests {
|
||||
verify(update).withCommand("/cnb/lifecycle/test");
|
||||
verify(update).withLabel("author", "spring-boot");
|
||||
verify(update).withBinding(Binding.from(volumeName, "/test"));
|
||||
verify(update).withNetworkMode(null);
|
||||
verifyNoMoreInteractions(update);
|
||||
}
|
||||
|
||||
@@ -129,6 +136,19 @@ class PhaseTests {
|
||||
verify(update).withLabel("author", "spring-boot");
|
||||
verify(update).withEnv("name1", "value1");
|
||||
verify(update).withEnv("name2", "value2");
|
||||
verify(update).withNetworkMode(null);
|
||||
verifyNoMoreInteractions(update);
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenWithNetworkModeUpdatesConfigurationWithNetworkMode() {
|
||||
Phase phase = new Phase("test", true);
|
||||
phase.withNetworkMode("test");
|
||||
Update update = mock(Update.class);
|
||||
phase.apply(update);
|
||||
verify(update).withCommand("/cnb/lifecycle/test");
|
||||
verify(update).withNetworkMode("test");
|
||||
verify(update).withLabel("author", "spring-boot");
|
||||
verifyNoMoreInteractions(update);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
* @author Jeroen Meijer
|
||||
*/
|
||||
class ContainerConfigTests extends AbstractJsonTests {
|
||||
|
||||
@@ -59,6 +60,7 @@ class ContainerConfigTests extends AbstractJsonTests {
|
||||
update.withBinding(Binding.from("bind-source", "bind-dest"));
|
||||
update.withEnv("name1", "value1");
|
||||
update.withEnv("name2", "value2");
|
||||
update.withNetworkMode("test");
|
||||
});
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
containerConfig.writeTo(outputStream);
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"User" : "root",
|
||||
"Image" : "pack.local/ephemeral-builder",
|
||||
"Cmd" : [ "/cnb/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run:latest", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "-process-type=web", "docker.io/library/my-application:latest" ],
|
||||
"Env" : [ "CNB_PLATFORM_API=0.4" ],
|
||||
"Labels" : {
|
||||
"author" : "spring-boot"
|
||||
},
|
||||
"HostConfig" : {
|
||||
"NetworkMode" : "test",
|
||||
"Binds" : [ "/var/run/docker.sock:/var/run/docker.sock", "pack-layers-aaaaaaaaaa:/layers", "pack-app-aaaaaaaaaa:/workspace", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache" ]
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
"HostConfig": {
|
||||
"Binds": [
|
||||
"bind-source:bind-dest"
|
||||
]
|
||||
],
|
||||
"NetworkMode": "test"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user