Merge pull request #27486 from Meijuh

* pr/27486:
  Polish "Add network option for image building"
  Add network option for image building

Closes gh-27486
pull/27659/head
Scott Frederick 3 years ago
commit 308535011b

@ -36,6 +36,7 @@ import org.springframework.util.Assert;
* @author Phillip Webb * @author Phillip Webb
* @author Scott Frederick * @author Scott Frederick
* @author Andrey Shlykov * @author Andrey Shlykov
* @author Jeroen Meijer
* @since 2.3.0 * @since 2.3.0
*/ */
public class BuildRequest { public class BuildRequest {
@ -68,6 +69,8 @@ public class BuildRequest {
private final List<Binding> bindings; private final List<Binding> bindings;
private final String network;
BuildRequest(ImageReference name, Function<Owner, TarArchive> applicationContent) { BuildRequest(ImageReference name, Function<Owner, TarArchive> applicationContent) {
Assert.notNull(name, "Name must not be null"); Assert.notNull(name, "Name must not be null");
Assert.notNull(applicationContent, "ApplicationContent must not be null"); Assert.notNull(applicationContent, "ApplicationContent must not be null");
@ -83,12 +86,13 @@ public class BuildRequest {
this.creator = Creator.withVersion(""); this.creator = Creator.withVersion("");
this.buildpacks = Collections.emptyList(); this.buildpacks = Collections.emptyList();
this.bindings = Collections.emptyList(); this.bindings = Collections.emptyList();
this.network = null;
} }
BuildRequest(ImageReference name, Function<Owner, TarArchive> applicationContent, ImageReference builder, BuildRequest(ImageReference name, Function<Owner, TarArchive> applicationContent, ImageReference builder,
ImageReference runImage, Creator creator, Map<String, String> env, boolean cleanCache, ImageReference runImage, Creator creator, Map<String, String> env, boolean cleanCache,
boolean verboseLogging, PullPolicy pullPolicy, boolean publish, List<BuildpackReference> buildpacks, boolean verboseLogging, PullPolicy pullPolicy, boolean publish, List<BuildpackReference> buildpacks,
List<Binding> bindings) { List<Binding> bindings, String network) {
this.name = name; this.name = name;
this.applicationContent = applicationContent; this.applicationContent = applicationContent;
this.builder = builder; this.builder = builder;
@ -101,6 +105,7 @@ public class BuildRequest {
this.publish = publish; this.publish = publish;
this.buildpacks = buildpacks; this.buildpacks = buildpacks;
this.bindings = bindings; this.bindings = bindings;
this.network = network;
} }
/** /**
@ -112,7 +117,7 @@ public class BuildRequest {
Assert.notNull(builder, "Builder must not be null"); Assert.notNull(builder, "Builder must not be null");
return new BuildRequest(this.name, this.applicationContent, builder.inTaggedOrDigestForm(), this.runImage, return new BuildRequest(this.name, this.applicationContent, builder.inTaggedOrDigestForm(), this.runImage,
this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, 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) { public BuildRequest withRunImage(ImageReference runImageName) {
return new BuildRequest(this.name, this.applicationContent, this.builder, runImageName.inTaggedOrDigestForm(), return new BuildRequest(this.name, this.applicationContent, this.builder, runImageName.inTaggedOrDigestForm(),
this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, 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) { public BuildRequest withCreator(Creator creator) {
Assert.notNull(creator, "Creator must not be null"); Assert.notNull(creator, "Creator must not be null");
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, creator, this.env, 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); env.put(name, value);
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator,
Collections.unmodifiableMap(env), this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, 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); updatedEnv.putAll(env);
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator,
Collections.unmodifiableMap(updatedEnv), this.cleanCache, this.verboseLogging, this.pullPolicy, 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) { public BuildRequest withCleanCache(boolean cleanCache) {
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, 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) { public BuildRequest withVerboseLogging(boolean verboseLogging) {
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, 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) { public BuildRequest withPullPolicy(PullPolicy pullPolicy) {
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, 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) { public BuildRequest withPublish(boolean publish) {
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, 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) { public BuildRequest withBuildpacks(List<BuildpackReference> buildpacks) {
Assert.notNull(buildpacks, "Buildpacks must not be null"); Assert.notNull(buildpacks, "Buildpacks must not be null");
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, 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<Binding> bindings) { public BuildRequest withBindings(List<Binding> bindings) {
Assert.notNull(bindings, "Bindings must not be null"); Assert.notNull(bindings, "Bindings must not be null");
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env, 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 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. * Factory method to create a new {@link BuildRequest} from a JAR file.
* @param jarFile the source jar file * @param jarFile the source jar file

@ -38,6 +38,7 @@ import org.springframework.util.Assert;
* *
* @author Phillip Webb * @author Phillip Webb
* @author Scott Frederick * @author Scott Frederick
* @author Jeroen Meijer
*/ */
class Lifecycle implements Closeable { class Lifecycle implements Closeable {
@ -147,6 +148,9 @@ class Lifecycle implements Closeable {
this.request.getBindings().forEach(phase::withBinding); this.request.getBindings().forEach(phase::withBinding);
} }
phase.withEnv(PLATFORM_API_VERSION_KEY, this.platformVersion.toString()); phase.withEnv(PLATFORM_API_VERSION_KEY, this.platformVersion.toString());
if (this.request.getNetwork() != null) {
phase.withNetworkMode(this.request.getNetwork());
}
return phase; return phase;
} }

@ -31,6 +31,7 @@ import org.springframework.util.StringUtils;
* *
* @author Phillip Webb * @author Phillip Webb
* @author Scott Frederick * @author Scott Frederick
* @author Jeroen Meijer
*/ */
class Phase { class Phase {
@ -48,6 +49,8 @@ class Phase {
private final Map<String, String> env = new LinkedHashMap<>(); private final Map<String, String> env = new LinkedHashMap<>();
private String networkMode;
/** /**
* Create a new {@link Phase} instance. * Create a new {@link Phase} instance.
* @param name the name of the phase * @param name the name of the phase
@ -101,6 +104,14 @@ class Phase {
this.env.put(name, value); 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 name of the phase.
* @return the phase name * @return the phase name
@ -127,6 +138,9 @@ class Phase {
update.withLabel("author", "spring-boot"); update.withLabel("author", "spring-boot");
this.bindings.forEach(update::withBinding); this.bindings.forEach(update::withBinding);
this.env.forEach(update::withEnv); this.env.forEach(update::withEnv);
if (this.networkMode != null) {
update.withNetworkMode(this.networkMode);
}
} }
} }

@ -40,6 +40,7 @@ import org.springframework.util.StringUtils;
* *
* @author Phillip Webb * @author Phillip Webb
* @author Scott Frederick * @author Scott Frederick
* @author Jeroen Meijer
* @since 2.3.0 * @since 2.3.0
*/ */
public class ContainerConfig { public class ContainerConfig {
@ -47,7 +48,7 @@ public class ContainerConfig {
private final String json; private final String json;
ContainerConfig(String user, ImageReference image, String command, List<String> args, Map<String, String> labels, 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.notNull(image, "Image must not be null");
Assert.hasText(command, "Command must not be empty"); Assert.hasText(command, "Command must not be empty");
ObjectMapper objectMapper = SharedObjectMapper.get(); ObjectMapper objectMapper = SharedObjectMapper.get();
@ -64,6 +65,9 @@ public class ContainerConfig {
ObjectNode labelsNode = node.putObject("Labels"); ObjectNode labelsNode = node.putObject("Labels");
labels.forEach(labelsNode::put); labels.forEach(labelsNode::put);
ObjectNode hostConfigNode = node.putObject("HostConfig"); ObjectNode hostConfigNode = node.putObject("HostConfig");
if (networkMode != null) {
hostConfigNode.put("NetworkMode", networkMode);
}
ArrayNode bindsNode = hostConfigNode.putArray("Binds"); ArrayNode bindsNode = hostConfigNode.putArray("Binds");
bindings.forEach((binding) -> bindsNode.add(binding.toString())); bindings.forEach((binding) -> bindsNode.add(binding.toString()));
this.json = objectMapper.writeValueAsString(node); this.json = objectMapper.writeValueAsString(node);
@ -114,6 +118,8 @@ public class ContainerConfig {
private final Map<String, String> env = new LinkedHashMap<>(); private final Map<String, String> env = new LinkedHashMap<>();
private String networkMode;
Update(ImageReference image) { Update(ImageReference image) {
this.image = image; this.image = image;
} }
@ -122,7 +128,7 @@ public class ContainerConfig {
update.accept(this); update.accept(this);
try { try {
return new ContainerConfig(this.user, this.image, this.command, this.args, this.labels, this.bindings, return new ContainerConfig(this.user, this.image, this.command, this.args, this.labels, this.bindings,
this.env); this.env, this.networkMode);
} }
catch (IOException ex) { catch (IOException ex) {
throw new IllegalStateException(ex); throw new IllegalStateException(ex);
@ -182,6 +188,15 @@ public class ContainerConfig {
this.env.put(name, value); 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;
}
} }
} }

@ -45,6 +45,7 @@ import static org.assertj.core.api.Assertions.entry;
* *
* @author Phillip Webb * @author Phillip Webb
* @author Scott Frederick * @author Scott Frederick
* @author Jeroen Meijer
*/ */
public class BuildRequestTests { public class BuildRequestTests {
@ -199,6 +200,12 @@ public class BuildRequestTests {
.withMessage("Bindings must not be null"); .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) { private void hasExpectedJarContent(TarArchive archive) {
try { try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

@ -62,6 +62,7 @@ import static org.mockito.Mockito.verify;
* *
* @author Phillip Webb * @author Phillip Webb
* @author Scott Frederick * @author Scott Frederick
* @author Jeroen Meijer
*/ */
class LifecycleTests { class LifecycleTests {
@ -188,6 +189,17 @@ class LifecycleTests {
verify(this.docker.volume()).delete(VolumeName.of("pack-app-aaaaaaaaaa"), true); 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() { private DockerApi mockDockerApi() {
DockerApi docker = mock(DockerApi.class); DockerApi docker = mock(DockerApi.class);
ImageApi imageApi = mock(ImageApi.class); ImageApi imageApi = mock(ImageApi.class);

@ -32,6 +32,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
* *
* @author Phillip Webb * @author Phillip Webb
* @author Scott Frederick * @author Scott Frederick
* @author Jeroen Meijer
*/ */
class PhaseTests { class PhaseTests {
@ -132,4 +133,16 @@ class PhaseTests {
verifyNoMoreInteractions(update); 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 Phillip Webb
* @author Scott Frederick * @author Scott Frederick
* @author Jeroen Meijer
*/ */
class ContainerConfigTests extends AbstractJsonTests { class ContainerConfigTests extends AbstractJsonTests {
@ -59,6 +60,7 @@ class ContainerConfigTests extends AbstractJsonTests {
update.withBinding(Binding.from("bind-source", "bind-dest")); update.withBinding(Binding.from("bind-source", "bind-dest"));
update.withEnv("name1", "value1"); update.withEnv("name1", "value1");
update.withEnv("name2", "value2"); update.withEnv("name2", "value2");
update.withNetworkMode("test");
}); });
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
containerConfig.writeTo(outputStream); 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": { "HostConfig": {
"Binds": [ "Binds": [
"bind-source:bind-dest" "bind-source:bind-dest"
] ],
"NetworkMode": "test"
} }
} }

@ -155,6 +155,12 @@ Where `<options>` can contain:
* `volume-opt=key=value` to specify key-value pairs consisting of an option name and its value * `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`
| `--cleanCache` | `--cleanCache`
| Whether to clean the cache before building. | Whether to clean the cache before building.
@ -169,6 +175,7 @@ Where `<options>` can contain:
| `--publishImage` | `--publishImage`
| Whether to publish the generated image to a Docker registry. | Whether to publish the generated image to a Docker registry.
| `false` | `false`
|=== |===
NOTE: The plugin detects the target Java compatibility of the project using the JavaPlugin's `targetCompatibility` property. NOTE: The plugin detects the target Java compatibility of the project using the JavaPlugin's `targetCompatibility` property.

@ -58,6 +58,7 @@ import org.springframework.util.StringUtils;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Scott Frederick * @author Scott Frederick
* @author Jeroen Meijer
* @since 2.3.0 * @since 2.3.0
*/ */
public class BootBuildImage extends DefaultTask { public class BootBuildImage extends DefaultTask {
@ -92,6 +93,8 @@ public class BootBuildImage extends DefaultTask {
private final ListProperty<String> bindings; private final ListProperty<String> bindings;
private String network;
private final DockerSpec docker = new DockerSpec(); private final DockerSpec docker = new DockerSpec();
public BootBuildImage() { public BootBuildImage() {
@ -376,6 +379,25 @@ public class BootBuildImage extends DefaultTask {
this.bindings.addAll(bindings); 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. * Returns the Docker configuration the builder will use.
* @return docker configuration. * @return docker configuration.
@ -438,6 +460,7 @@ public class BootBuildImage extends DefaultTask {
request = customizePublish(request); request = customizePublish(request);
request = customizeBuildpacks(request); request = customizeBuildpacks(request);
request = customizeBindings(request); request = customizeBindings(request);
request = request.withNetwork(this.network);
return request; return request;
} }

@ -71,6 +71,7 @@ class BootBuildImageIntegrationTests {
assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()).contains("docker.io/library/" + projectName);
assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("---> Test Info buildpack building");
assertThat(result.getOutput()).contains("env: BP_JVM_VERSION=8.*"); 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"); assertThat(result.getOutput()).contains("---> Test Info buildpack done");
removeImage(projectName); removeImage(projectName);
} }
@ -247,6 +248,20 @@ class BootBuildImageIntegrationTests {
removeImage(projectName); 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 @TestTemplate
void failsWithBuilderError() throws IOException { void failsWithBuilderError() throws IOException {
writeMainClass(); writeMainClass();

@ -42,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Scott Frederick * @author Scott Frederick
* @author Andrey Shlykov * @author Andrey Shlykov
* @author Jeroen Meijer
*/ */
class BootBuildImageTests { class BootBuildImageTests {
@ -278,4 +279,10 @@ class BootBuildImageTests {
.containsExactly(Binding.of("host-src:container-dest:ro"), Binding.of("volume-name:container-dest:rw")); .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");
}
} }

@ -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"
}

@ -163,6 +163,12 @@ Where `<options>` can contain:
* `volume-opt=key=value` to specify key-value pairs consisting of an option name and its value * `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` + | `cleanCache` +
(`spring-boot.build-image.cleanCache`) (`spring-boot.build-image.cleanCache`)
| Whether to clean the cache before building. | Whether to clean the cache before building.
@ -176,6 +182,7 @@ Where `<options>` can contain:
(`spring-boot.build-image.publish`) (`spring-boot.build-image.publish`)
| Whether to publish the generated image to a Docker registry. | Whether to publish the generated image to a Docker registry.
| `false` | `false`
|=== |===
NOTE: The plugin detects the target Java compatibility of the project using the compiler's plugin configuration or the `maven.compiler.target` property. NOTE: The plugin detects the target Java compatibility of the project using the compiler's plugin configuration or the `maven.compiler.target` property.

@ -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 @TestTemplate
void whenBuildImageIsInvokedOnMultiModuleProjectWithPackageGoal(MavenBuild mavenBuild) { void whenBuildImageIsInvokedOnMultiModuleProjectWithPackageGoal(MavenBuild mavenBuild) {
mavenBuild.project("build-image-multi-module").goals("package") mavenBuild.project("build-image-multi-module").goals("package")

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>build-image-network</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>@java.version@</maven.compiler.source>
<maven.compiler.target>@java.version@</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<goals>
<goal>build-image</goal>
</goals>
<configuration>
<image>
<builder>projects.registry.vmware.com/springboot/spring-boot-cnb-builder:0.0.1</builder>
<network>none</network>
</image>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -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"
}
}
}

@ -59,6 +59,7 @@ import org.springframework.util.StringUtils;
* *
* @author Phillip Webb * @author Phillip Webb
* @author Scott Frederick * @author Scott Frederick
* @author Jeroen Meijer
* @since 2.3.0 * @since 2.3.0
*/ */
@Mojo(name = "build-image", defaultPhase = LifecyclePhase.PACKAGE, requiresProject = true, threadSafe = true, @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) @Parameter(property = "spring-boot.build-image.publish", readonly = true)
Boolean publish; 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. * Docker configuration options.
* @since 2.4.0 * @since 2.4.0
@ -248,6 +256,9 @@ public class BuildImageMojo extends AbstractPackagerMojo {
if (image.publish == null && this.publish != null) { if (image.publish == null && this.publish != null) {
image.setPublish(this.publish); image.setPublish(this.publish);
} }
if (image.network == null && this.network != null) {
image.setNetwork(this.network);
}
if (image.publish != null && image.publish && publishRegistryNotConfigured()) { if (image.publish != null && image.publish && publishRegistryNotConfigured()) {
throw new MojoExecutionException("Publishing an image requires docker.publishRegistry to be configured"); throw new MojoExecutionException("Publishing an image requires docker.publishRegistry to be configured");
} }

@ -39,6 +39,7 @@ import org.springframework.util.StringUtils;
* *
* @author Phillip Webb * @author Phillip Webb
* @author Scott Frederick * @author Scott Frederick
* @author Jeroen Meijer
* @since 2.3.0 * @since 2.3.0
*/ */
public class Image { public class Image {
@ -63,6 +64,8 @@ public class Image {
List<String> bindings; List<String> bindings;
String network;
/** /**
* The name of the created image. * The name of the created image.
* @return the image name * @return the image name
@ -151,6 +154,18 @@ public class Image {
this.publish = publish; 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<Owner, TarArchive> applicationContent) { BuildRequest getBuildRequest(Artifact artifact, Function<Owner, TarArchive> applicationContent) {
return customize(BuildRequest.of(getOrDeduceName(artifact), applicationContent)); return customize(BuildRequest.of(getOrDeduceName(artifact), applicationContent));
} }
@ -190,6 +205,7 @@ public class Image {
if (!CollectionUtils.isEmpty(this.bindings)) { if (!CollectionUtils.isEmpty(this.bindings)) {
request = request.withBindings(this.bindings.stream().map(Binding::of).collect(Collectors.toList())); request = request.withBindings(this.bindings.stream().map(Binding::of).collect(Collectors.toList()));
} }
request = request.withNetwork(this.network);
return request; return request;
} }

@ -41,6 +41,7 @@ import static org.assertj.core.api.Assertions.entry;
* *
* @author Phillip Webb * @author Phillip Webb
* @author Scott Frederick * @author Scott Frederick
* @author Jeroen Meijer
*/ */
class ImageTests { class ImageTests {
@ -70,6 +71,7 @@ class ImageTests {
assertThat(request.getPullPolicy()).isEqualTo(PullPolicy.ALWAYS); assertThat(request.getPullPolicy()).isEqualTo(PullPolicy.ALWAYS);
assertThat(request.getBuildpacks()).isEmpty(); assertThat(request.getBuildpacks()).isEmpty();
assertThat(request.getBindings()).isEmpty(); assertThat(request.getBindings()).isEmpty();
assertThat(request.getNetwork()).isNull();
} }
@Test @Test
@ -146,6 +148,14 @@ class ImageTests {
Binding.of("volume-name:container-dest:rw")); 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() { private Artifact createArtifact() {
return new DefaultArtifact("com.example", "my-app", VersionRange.createFromVersion("0.0.1-SNAPSHOT"), "compile", return new DefaultArtifact("com.example", "my-app", VersionRange.createFromVersion("0.0.1-SNAPSHOT"), "compile",
"jar", null, new DefaultArtifactHandler()); "jar", null, new DefaultArtifactHandler());

@ -32,4 +32,6 @@ if [[ -f META-INF/MANIFEST.MF ]]; then
cat META-INF/MANIFEST.MF cat META-INF/MANIFEST.MF
fi fi
echo "Network status: $(curl --silent --head https://spring.io | grep -E '^HTTP' || echo "curl failed")"
echo "---> Test Info buildpack done" echo "---> Test Info buildpack done"

Loading…
Cancel
Save