diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildRequest.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildRequest.java index 93600e49e0..b7feaa5277 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildRequest.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildRequest.java @@ -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 bindings; + private final String network; + BuildRequest(ImageReference name, Function 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 applicationContent, ImageReference builder, ImageReference runImage, Creator creator, Map env, boolean cleanCache, boolean verboseLogging, PullPolicy pullPolicy, boolean publish, List buildpacks, - List bindings) { + List 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 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,20 @@ public class BuildRequest { public BuildRequest withBindings(List 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); + } + + /** + * Return a new {@link BuildRequest} with an updated network setting. + * @param network the network the build container will connect to + * @return an updated build request + * @since 2.6.0 + */ + 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 +377,15 @@ public class BuildRequest { return this.bindings; } + /** + * Return the network the build container will connect to. + * @return the network + * @since 2.6.0 + */ + public String getNetwork() { + return this.network; + } + /** * Factory method to create a new {@link BuildRequest} from a JAR file. * @param jarFile the source jar file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Lifecycle.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Lifecycle.java index c6adc77096..ae372bc71e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Lifecycle.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Lifecycle.java @@ -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,9 @@ class Lifecycle implements Closeable { this.request.getBindings().forEach(phase::withBinding); } phase.withEnv(PLATFORM_API_VERSION_KEY, this.platformVersion.toString()); + if (this.request.getNetwork() != null) { + phase.withNetworkMode(this.request.getNetwork()); + } return phase; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Phase.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Phase.java index 234592afd4..8c0264f836 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Phase.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Phase.java @@ -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 env = new LinkedHashMap<>(); + private String networkMode; + /** * Create a new {@link Phase} instance. * @param name the name of the phase @@ -101,6 +104,14 @@ class Phase { this.env.put(name, value); } + /** + * Update this phase with the network the build container will connect to. + * @param networkMode the network + */ + void withNetworkMode(String networkMode) { + this.networkMode = networkMode; + } + /** * Return the name of the phase. * @return the phase name @@ -127,6 +138,9 @@ class Phase { update.withLabel("author", "spring-boot"); this.bindings.forEach(update::withBinding); this.env.forEach(update::withEnv); + if (this.networkMode != null) { + update.withNetworkMode(this.networkMode); + } } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ContainerConfig.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ContainerConfig.java index da945f6848..a411b49f28 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ContainerConfig.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ContainerConfig.java @@ -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 args, Map labels, - List bindings, Map env) throws IOException { + List bindings, Map 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 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,15 @@ public class ContainerConfig { this.env.put(name, value); } + /** + * Update the container config with the network that the build container will + * connect to. + * @param networkMode the network + */ + public void withNetworkMode(String networkMode) { + this.networkMode = networkMode; + } + } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildRequestTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildRequestTests.java index 92115cc0a5..c75e5155cf 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildRequestTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildRequestTests.java @@ -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(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java index 8e5b26187c..03234771e2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java @@ -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); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/PhaseTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/PhaseTests.java index ffddb46448..6bed915d45 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/PhaseTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/PhaseTests.java @@ -32,6 +32,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; * * @author Phillip Webb * @author Scott Frederick + * @author Jeroen Meijer */ class PhaseTests { @@ -132,4 +133,16 @@ class PhaseTests { 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); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ContainerConfigTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ContainerConfigTests.java index cc94d2d7ae..a0b9292345 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ContainerConfigTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ContainerConfigTests.java @@ -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); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-network.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-network.json new file mode 100644 index 0000000000..ae6ab807d1 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/lifecycle-creator-network.json @@ -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" ] + } +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/container-config.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/container-config.json index 2c034b7767..51780616f1 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/container-config.json +++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/docker/type/container-config.json @@ -16,6 +16,7 @@ "HostConfig": { "Binds": [ "bind-source:bind-dest" - ] + ], + "NetworkMode": "test" } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc index 65f0f4cc6d..0d1677aac2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/packaging-oci-image.adoc @@ -155,6 +155,12 @@ Where `` can contain: * `volume-opt=key=value` to specify key-value pairs consisting of an option name and its value | +| `network` +| `--network` +| The https://docs.docker.com/network/#network-drivers[network driver] the builder container will be configured to use. +The value supplied will be passed unvalidated to Docker when creating the builder container. +| + | `cleanCache` | `--cleanCache` | Whether to clean the cache before building. @@ -169,6 +175,7 @@ Where `` can contain: | `--publishImage` | Whether to publish the generated image to a Docker registry. | `false` + |=== NOTE: The plugin detects the target Java compatibility of the project using the JavaPlugin's `targetCompatibility` property. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java index bdd8cdb2e2..831987fa72 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java @@ -58,6 +58,7 @@ import org.springframework.util.StringUtils; * * @author Andy Wilkinson * @author Scott Frederick + * @author Jeroen Meijer * @since 2.3.0 */ public class BootBuildImage extends DefaultTask { @@ -92,6 +93,8 @@ public class BootBuildImage extends DefaultTask { private final ListProperty bindings; + private String network; + private final DockerSpec docker = new DockerSpec(); public BootBuildImage() { @@ -376,6 +379,25 @@ public class BootBuildImage extends DefaultTask { this.bindings.addAll(bindings); } + /** + * Returns the network the build container will connect to. + * @return the network + */ + @Input + @Optional + public String getNetwork() { + return this.network; + } + + /** + * Sets the network the build container will connect to. + * @param network the network + */ + @Option(option = "network", description = "Connect detect and build containers to network") + public void setNetwork(String network) { + this.network = network; + } + /** * Returns the Docker configuration the builder will use. * @return docker configuration. @@ -438,6 +460,7 @@ public class BootBuildImage extends DefaultTask { request = customizePublish(request); request = customizeBuildpacks(request); request = customizeBindings(request); + request = request.withNetwork(this.network); return request; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java index a8517e11ef..8f5d4691af 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java @@ -71,6 +71,7 @@ class BootBuildImageIntegrationTests { assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("env: BP_JVM_VERSION=8.*"); + assertThat(result.getOutput()).contains("Network status: HTTP/2 200"); assertThat(result.getOutput()).contains("---> Test Info buildpack done"); removeImage(projectName); } @@ -247,6 +248,20 @@ class BootBuildImageIntegrationTests { removeImage(projectName); } + @TestTemplate + void buildsImageWithNetworkModeNone() throws IOException { + writeMainClass(); + writeLongNameResource(); + BuildResult result = this.gradleBuild.build("bootBuildImage", "--pullPolicy=IF_NOT_PRESENT"); + String projectName = this.gradleBuild.getProjectDir().getName(); + assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(result.getOutput()).contains("docker.io/library/" + projectName); + assertThat(result.getOutput()).contains("---> Test Info buildpack building"); + assertThat(result.getOutput()).contains("Network status: curl failed"); + assertThat(result.getOutput()).contains("---> Test Info buildpack done"); + removeImage(projectName); + } + @TestTemplate void failsWithBuilderError() throws IOException { writeMainClass(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageTests.java index 31d2d10dd8..082a2b5954 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageTests.java @@ -42,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; * @author Andy Wilkinson * @author Scott Frederick * @author Andrey Shlykov + * @author Jeroen Meijer */ class BootBuildImageTests { @@ -278,4 +279,10 @@ class BootBuildImageTests { .containsExactly(Binding.of("host-src:container-dest:ro"), Binding.of("volume-name:container-dest:rw")); } + @Test + void whenNetworkIsConfiguredThenRequestHasNetwork() { + this.buildImage.setNetwork("test"); + assertThat(this.buildImage.createRequest().getNetwork()).isEqualTo("test"); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithNetworkModeNone.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithNetworkModeNone.gradle new file mode 100644 index 0000000000..d5b5b0113e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests-buildsImageWithNetworkModeNone.gradle @@ -0,0 +1,16 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +if (project.hasProperty('applyWarPlugin')) { + apply plugin: 'war' +} + +sourceCompatibility = '1.8' +targetCompatibility = '1.8' + +bootBuildImage { + builder = "projects.registry.vmware.com/springboot/spring-boot-cnb-builder:0.0.1" + network = "none" +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc index 24de20aaa1..df8aa32b50 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/docs/asciidoc/packaging-oci-image.adoc @@ -163,6 +163,12 @@ Where `` can contain: * `volume-opt=key=value` to specify key-value pairs consisting of an option name and its value | +| `network` + +(`spring-boot.build-image.network`) +| The https://docs.docker.com/network/#network-drivers[network driver] the builder container will be configured to use. +The value supplied will be passed unvalidated to Docker when creating the builder container. +| + | `cleanCache` + (`spring-boot.build-image.cleanCache`) | Whether to clean the cache before building. @@ -176,6 +182,7 @@ Where `` can contain: (`spring-boot.build-image.publish`) | Whether to publish the generated image to a Docker registry. | `false` + |=== NOTE: The plugin detects the target Java compatibility of the project using the compiler's plugin configuration or the `maven.compiler.target` property. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/BuildImageTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/BuildImageTests.java index 6bc3d56770..0d75a99e44 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/BuildImageTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/BuildImageTests.java @@ -270,6 +270,17 @@ public class BuildImageTests extends AbstractArchiveIntegrationTests { }); } + @TestTemplate + void whenBuildImageIsInvokedWithNetworkModeNone(MavenBuild mavenBuild) { + mavenBuild.project("build-image-network").goals("package") + .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT").execute((project) -> { + assertThat(buildLog(project)).contains("Building image") + .contains("docker.io/library/build-image-network:0.0.1.BUILD-SNAPSHOT") + .contains("Network status: curl failed").contains("Successfully built image"); + removeImage("build-image-network", "0.0.1.BUILD-SNAPSHOT"); + }); + } + @TestTemplate void whenBuildImageIsInvokedOnMultiModuleProjectWithPackageGoal(MavenBuild mavenBuild) { mavenBuild.project("build-image-multi-module").goals("package") diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/build-image-network/pom.xml b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/build-image-network/pom.xml new file mode 100644 index 0000000000..1f0ca34f92 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/build-image-network/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + org.springframework.boot.maven.it + build-image-network + 0.0.1.BUILD-SNAPSHOT + + UTF-8 + @java.version@ + @java.version@ + + + + + @project.groupId@ + @project.artifactId@ + @project.version@ + + + + build-image + + + + projects.registry.vmware.com/springboot/spring-boot-cnb-builder:0.0.1 + none + + + + + + + + diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/build-image-network/src/main/java/org/test/SampleApplication.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/build-image-network/src/main/java/org/test/SampleApplication.java new file mode 100644 index 0000000000..27259ff01a --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/build-image-network/src/main/java/org/test/SampleApplication.java @@ -0,0 +1,28 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.test; + +public class SampleApplication { + + public static void main(String[] args) throws Exception { + System.out.println("Launched"); + synchronized(args) { + args.wait(); // Prevent exit" + } + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java index 33b9891b78..95f2ecee4f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java @@ -59,6 +59,7 @@ import org.springframework.util.StringUtils; * * @author Phillip Webb * @author Scott Frederick + * @author Jeroen Meijer * @since 2.3.0 */ @Mojo(name = "build-image", defaultPhase = LifecyclePhase.PACKAGE, requiresProject = true, threadSafe = true, @@ -153,6 +154,13 @@ public class BuildImageMojo extends AbstractPackagerMojo { @Parameter(property = "spring-boot.build-image.publish", readonly = true) Boolean publish; + /** + * Alias for {@link Image#network} to support configuration via command-line property. + * @since 2.6.0 + */ + @Parameter(property = "spring-boot.build-image.network", readonly = true) + String network; + /** * Docker configuration options. * @since 2.4.0 @@ -248,6 +256,9 @@ public class BuildImageMojo extends AbstractPackagerMojo { if (image.publish == null && this.publish != null) { image.setPublish(this.publish); } + if (image.network == null && this.network != null) { + image.setNetwork(this.network); + } if (image.publish != null && image.publish && publishRegistryNotConfigured()) { throw new MojoExecutionException("Publishing an image requires docker.publishRegistry to be configured"); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Image.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Image.java index 0ce0afd83b..e648f556bc 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Image.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/Image.java @@ -39,6 +39,7 @@ import org.springframework.util.StringUtils; * * @author Phillip Webb * @author Scott Frederick + * @author Jeroen Meijer * @since 2.3.0 */ public class Image { @@ -63,6 +64,8 @@ public class Image { List bindings; + String network; + /** * The name of the created image. * @return the image name @@ -151,6 +154,18 @@ public class Image { this.publish = publish; } + /** + * Returns the network the build container will connect to. + * @return the network + */ + public String getNetwork() { + return this.network; + } + + public void setNetwork(String network) { + this.network = network; + } + BuildRequest getBuildRequest(Artifact artifact, Function applicationContent) { return customize(BuildRequest.of(getOrDeduceName(artifact), applicationContent)); } @@ -190,6 +205,7 @@ public class Image { if (!CollectionUtils.isEmpty(this.bindings)) { request = request.withBindings(this.bindings.stream().map(Binding::of).collect(Collectors.toList())); } + request = request.withNetwork(this.network); return request; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ImageTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ImageTests.java index 15ee4db410..7c0d24921f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ImageTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/test/java/org/springframework/boot/maven/ImageTests.java @@ -41,6 +41,7 @@ import static org.assertj.core.api.Assertions.entry; * * @author Phillip Webb * @author Scott Frederick + * @author Jeroen Meijer */ class ImageTests { @@ -70,6 +71,7 @@ class ImageTests { assertThat(request.getPullPolicy()).isEqualTo(PullPolicy.ALWAYS); assertThat(request.getBuildpacks()).isEmpty(); assertThat(request.getBindings()).isEmpty(); + assertThat(request.getNetwork()).isNull(); } @Test @@ -146,6 +148,14 @@ class ImageTests { Binding.of("volume-name:container-dest:rw")); } + @Test + void getBuildRequestWhenNetworkUsesNetwork() { + Image image = new Image(); + image.network = "test"; + BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); + assertThat(request.getNetwork()).isEqualTo("test"); + } + private Artifact createArtifact() { return new DefaultArtifact("com.example", "my-app", VersionRange.createFromVersion("0.0.1-SNAPSHOT"), "compile", "jar", null, new DefaultArtifactHandler()); diff --git a/src/spring-boot-builder/buildpacks/test-info/bin/build b/src/spring-boot-builder/buildpacks/test-info/bin/build index a9317aa0c6..8c1dce7774 100755 --- a/src/spring-boot-builder/buildpacks/test-info/bin/build +++ b/src/spring-boot-builder/buildpacks/test-info/bin/build @@ -32,4 +32,6 @@ if [[ -f META-INF/MANIFEST.MF ]]; then cat META-INF/MANIFEST.MF fi +echo "Network status: $(curl --silent --head https://spring.io | grep -E '^HTTP' || echo "curl failed")" + echo "---> Test Info buildpack done"