Add support for security options in CNB builder container config

Closes gh-37479
pull/37500/head
Scott Frederick 1 year ago
parent 4433fcd1f2
commit 7de770f6a1

@ -87,6 +87,8 @@ public class BuildRequest {
private final String applicationDirectory; private final String applicationDirectory;
private final List<String> securityOptions;
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");
@ -109,13 +111,14 @@ public class BuildRequest {
this.launchCache = null; this.launchCache = null;
this.createdDate = null; this.createdDate = null;
this.applicationDirectory = null; this.applicationDirectory = null;
this.securityOptions = 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, String network, List<ImageReference> tags, Cache buildWorkspace, Cache buildCache, List<Binding> bindings, String network, List<ImageReference> tags, Cache buildWorkspace, Cache buildCache,
Cache launchCache, Instant createdDate, String applicationDirectory) { Cache launchCache, Instant createdDate, String applicationDirectory, List<String> securityOptions) {
this.name = name; this.name = name;
this.applicationContent = applicationContent; this.applicationContent = applicationContent;
this.builder = builder; this.builder = builder;
@ -135,6 +138,7 @@ public class BuildRequest {
this.launchCache = launchCache; this.launchCache = launchCache;
this.createdDate = createdDate; this.createdDate = createdDate;
this.applicationDirectory = applicationDirectory; this.applicationDirectory = applicationDirectory;
this.securityOptions = securityOptions;
} }
/** /**
@ -147,7 +151,7 @@ public class BuildRequest {
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.network, this.tags, this.buildWorkspace, this.buildCache, this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache,
this.launchCache, this.createdDate, this.applicationDirectory); this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions);
} }
/** /**
@ -159,7 +163,7 @@ public class BuildRequest {
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.network, this.tags, this.buildWorkspace, this.buildCache, this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache,
this.launchCache, this.createdDate, this.applicationDirectory); this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions);
} }
/** /**
@ -172,7 +176,7 @@ public class BuildRequest {
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, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory); this.applicationDirectory, this.securityOptions);
} }
/** /**
@ -189,7 +193,7 @@ public class BuildRequest {
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.network, this.tags, this.buildWorkspace, this.buildCache, this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace, this.buildCache,
this.launchCache, this.createdDate, this.applicationDirectory); this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions);
} }
/** /**
@ -204,7 +208,7 @@ public class BuildRequest {
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.network, this.tags, this.buildWorkspace, this.publish, this.buildpacks, this.bindings, this.network, this.tags, this.buildWorkspace,
this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory); this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions);
} }
/** /**
@ -216,7 +220,7 @@ public class BuildRequest {
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, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory); this.applicationDirectory, this.securityOptions);
} }
/** /**
@ -228,7 +232,7 @@ public class BuildRequest {
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, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory); this.applicationDirectory, this.securityOptions);
} }
/** /**
@ -240,7 +244,7 @@ public class BuildRequest {
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, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory); this.applicationDirectory, this.securityOptions);
} }
/** /**
@ -252,7 +256,7 @@ public class BuildRequest {
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, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory); this.applicationDirectory, this.securityOptions);
} }
/** /**
@ -277,7 +281,7 @@ public class BuildRequest {
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, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory); this.applicationDirectory, this.securityOptions);
} }
/** /**
@ -302,7 +306,7 @@ public class BuildRequest {
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, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory); this.applicationDirectory, this.securityOptions);
} }
/** /**
@ -315,7 +319,7 @@ public class BuildRequest {
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, this.bindings, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory); this.applicationDirectory, this.securityOptions);
} }
/** /**
@ -338,7 +342,7 @@ public class BuildRequest {
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, this.bindings, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.network, tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory); this.applicationDirectory, this.securityOptions);
} }
/** /**
@ -351,7 +355,7 @@ public class BuildRequest {
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, this.bindings, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, this.tags, buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.network, this.tags, buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory); this.applicationDirectory, this.securityOptions);
} }
/** /**
@ -364,7 +368,7 @@ public class BuildRequest {
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, this.bindings, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, this.tags, this.buildWorkspace, buildCache, this.launchCache, this.createdDate, this.network, this.tags, this.buildWorkspace, buildCache, this.launchCache, this.createdDate,
this.applicationDirectory); this.applicationDirectory, this.securityOptions);
} }
/** /**
@ -377,7 +381,7 @@ public class BuildRequest {
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, this.bindings, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, this.tags, this.buildWorkspace, this.buildCache, launchCache, this.createdDate, this.network, this.tags, this.buildWorkspace, this.buildCache, launchCache, this.createdDate,
this.applicationDirectory); this.applicationDirectory, this.securityOptions);
} }
/** /**
@ -390,7 +394,7 @@ public class BuildRequest {
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, this.bindings, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache,
parseCreatedDate(createdDate), this.applicationDirectory); parseCreatedDate(createdDate), this.applicationDirectory, this.securityOptions);
} }
private Instant parseCreatedDate(String createdDate) { private Instant parseCreatedDate(String createdDate) {
@ -415,7 +419,20 @@ public class BuildRequest {
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, this.bindings, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
applicationDirectory); applicationDirectory, this.securityOptions);
}
/**
* Return a new {@link BuildRequest} with an updated security options.
* @param securityOptions the security options
* @return an updated build request
*/
public BuildRequest withSecurityOptions(List<String> securityOptions) {
Assert.notNull(securityOptions, "SecurityOption 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, this.bindings,
this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory, securityOptions);
} }
/** /**
@ -571,6 +588,14 @@ public class BuildRequest {
return this.applicationDirectory; return this.applicationDirectory;
} }
/**
* Return the security options that should be used by the lifecycle.
* @return the security options
*/
public List<String> getSecurityOptions() {
return this.securityOptions;
}
/** /**
* 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

@ -20,6 +20,7 @@ import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -58,6 +59,8 @@ class Lifecycle implements Closeable {
private static final String DOMAIN_SOCKET_PATH = "/var/run/docker.sock"; private static final String DOMAIN_SOCKET_PATH = "/var/run/docker.sock";
private static final List<String> DEFAULT_SECURITY_OPTIONS = List.of("label=disable");
private final BuildLog log; private final BuildLog log;
private final DockerApi docker; private final DockerApi docker;
@ -82,6 +85,8 @@ class Lifecycle implements Closeable {
private final String applicationDirectory; private final String applicationDirectory;
private final List<String> securityOptions;
private boolean executed; private boolean executed;
private boolean applicationVolumePopulated; private boolean applicationVolumePopulated;
@ -108,6 +113,7 @@ class Lifecycle implements Closeable {
this.buildCache = getBuildCache(request); this.buildCache = getBuildCache(request);
this.launchCache = getLaunchCache(request); this.launchCache = getLaunchCache(request);
this.applicationDirectory = getApplicationDirectory(request); this.applicationDirectory = getApplicationDirectory(request);
this.securityOptions = getSecurityOptions(request);
} }
private Cache getBuildCache(BuildRequest request) { private Cache getBuildCache(BuildRequest request) {
@ -128,6 +134,13 @@ class Lifecycle implements Closeable {
return (request.getApplicationDirectory() != null) ? request.getApplicationDirectory() : Directory.APPLICATION; return (request.getApplicationDirectory() != null) ? request.getApplicationDirectory() : Directory.APPLICATION;
} }
private List<String> getSecurityOptions(BuildRequest request) {
if (request.getSecurityOptions() != null) {
return request.getSecurityOptions();
}
return (Platform.isWindows()) ? Collections.emptyList() : DEFAULT_SECURITY_OPTIONS;
}
private ApiVersion getPlatformVersion(BuilderMetadata.Lifecycle lifecycle) { private ApiVersion getPlatformVersion(BuilderMetadata.Lifecycle lifecycle) {
if (lifecycle.getApis().getPlatform() != null) { if (lifecycle.getApis().getPlatform() != null) {
String[] supportedVersions = lifecycle.getApis().getPlatform(); String[] supportedVersions = lifecycle.getApis().getPlatform();
@ -240,8 +253,8 @@ class Lifecycle implements Closeable {
else { else {
phase.withBinding(Binding.from(DOMAIN_SOCKET_PATH, DOMAIN_SOCKET_PATH)); phase.withBinding(Binding.from(DOMAIN_SOCKET_PATH, DOMAIN_SOCKET_PATH));
} }
if (!Platform.isWindows()) { if (this.securityOptions != null) {
phase.withSecurityOption("label=disable"); this.securityOptions.forEach(phase::withSecurityOption);
} }
} }

@ -333,6 +333,13 @@ class BuildRequestTests {
assertThat(withAppDir.getApplicationDirectory()).isEqualTo("/application"); assertThat(withAppDir.getApplicationDirectory()).isEqualTo("/application");
} }
@Test
void withSecurityOptionsSetsSecurityOptions() throws Exception {
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar"));
BuildRequest withAppDir = request.withSecurityOptions(List.of("label=user:USER", "label=role:ROLE"));
assertThat(withAppDir.getSecurityOptions()).containsExactly("label=user:USER", "label=role:ROLE");
}
private void hasExpectedJarContent(TarArchive archive) { private void hasExpectedJarContent(TarArchive archive) {
try { try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

@ -23,6 +23,7 @@ import java.io.InputStreamReader;
import java.io.PrintStream; import java.io.PrintStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
@ -254,6 +255,17 @@ class LifecycleTests {
assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
} }
@Test
void executeWithSecurityOptionsExecutesPhases() 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().withSecurityOptions(List.of("label=user:USER", "label=role:ROLE"));
createLifecycle(request).execute();
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-security-opts.json"));
assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
}
@Test @Test
void executeWithDockerHostAndRemoteAddressExecutesPhases() throws Exception { void executeWithDockerHostAndRemoteAddressExecutesPhases() throws Exception {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());

@ -0,0 +1,40 @@
{
"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",
"docker.io/library/my-application:latest"
],
"Env": [
"CNB_PLATFORM_API=0.8"
],
"Labels": {
"author": "spring-boot"
},
"HostConfig": {
"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"
],
"SecurityOpt" : [
"label=user:USER",
"label=role:ROLE"
]
}
}

@ -223,6 +223,11 @@ The value must be a string in the ISO 8601 instant format, or `now` to use the c
Application contents will also be in this location in the generated image. Application contents will also be in this location in the generated image.
| `/workspace` | `/workspace`
| `securityOptions`
| `--securityOptions`
| https://docs.docker.com/engine/reference/run/#security-configuration[Security options] that will be applied to the builder container, provided as an array of string values
| `["label=disable"]`
|=== |===
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.

@ -302,6 +302,15 @@ public abstract class BootBuildImage extends DefaultTask {
@Option(option = "applicationDirectory", description = "The directory containing application content in the image") @Option(option = "applicationDirectory", description = "The directory containing application content in the image")
public abstract Property<String> getApplicationDirectory(); public abstract Property<String> getApplicationDirectory();
/**
* Returns the security options that will be applied to the builder container.
* @return the security options
*/
@Input
@Optional
@Option(option = "securityOptions", description = "Security options that will be applied to the builder container")
public abstract ListProperty<String> getSecurityOptions();
/** /**
* Returns the Docker configuration the builder will use. * Returns the Docker configuration the builder will use.
* @return docker configuration. * @return docker configuration.
@ -349,6 +358,7 @@ public abstract class BootBuildImage extends DefaultTask {
request = request.withNetwork(getNetwork().getOrNull()); request = request.withNetwork(getNetwork().getOrNull());
request = customizeCreatedDate(request); request = customizeCreatedDate(request);
request = customizeApplicationDirectory(request); request = customizeApplicationDirectory(request);
request = customizeSecurityOptions(request);
return request; return request;
} }
@ -450,4 +460,12 @@ public abstract class BootBuildImage extends DefaultTask {
return request; return request;
} }
private BuildRequest customizeSecurityOptions(BuildRequest request) {
List<String> securityOptions = getSecurityOptions().getOrNull();
if (securityOptions != null) {
return request.withSecurityOptions(securityOptions);
}
return request;
}
} }

@ -368,6 +368,19 @@ class BootBuildImageIntegrationTests {
removeImages(projectName); removeImages(projectName);
} }
@TestTemplate
void buildsImageWithEmptySecurityOptions() throws IOException {
writeMainClass();
writeLongNameResource();
BuildResult result = this.gradleBuild.build("bootBuildImage");
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("---> Test Info buildpack done");
removeImages(projectName);
}
@TestTemplate @TestTemplate
void failsWithInvalidCreatedDate() throws IOException { void failsWithInvalidCreatedDate() throws IOException {
writeMainClass(); writeMainClass();

@ -0,0 +1,15 @@
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
}
java {
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
}
bootBuildImage {
builder = "projects.registry.vmware.com/springboot/spring-boot-cnb-builder:0.0.2"
pullPolicy = "IF_NOT_PRESENT"
securityOptions = []
}

@ -230,6 +230,10 @@ The value must be a string in the ISO 8601 instant format, or `now` to use the c
Application contents will also be in this location in the generated image. Application contents will also be in this location in the generated image.
| `/workspace` | `/workspace`
| `securityOptions`
| https://docs.docker.com/engine/reference/run/#security-configuration[Security options] that will be applied to the builder container, provided as an array of string values
| `["label=disable"]`
|=== |===
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.

@ -480,6 +480,21 @@ class BuildImageTests extends AbstractArchiveIntegrationTests {
}); });
} }
@TestTemplate
void whenBuildImageIsInvokedWithEmptySecurityOptions(MavenBuild mavenBuild) {
String testBuildId = randomString();
mavenBuild.project("build-image-security-opts")
.goals("package")
.systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT")
.systemProperty("test-build-id", testBuildId)
.execute((project) -> {
assertThat(buildLog(project)).contains("Building image")
.contains("docker.io/library/build-image-security-opts:0.0.1.BUILD-SNAPSHOT")
.contains("Successfully built image");
removeImage("build-image-security-opts", "0.0.1.BUILD-SNAPSHOT");
});
}
@TestTemplate @TestTemplate
void failsWhenBuildImageIsInvokedOnMultiModuleProjectWithBuildImageGoal(MavenBuild mavenBuild) { void failsWhenBuildImageIsInvokedOnMultiModuleProjectWithBuildImageGoal(MavenBuild mavenBuild) {
mavenBuild.project("build-image-multi-module") mavenBuild.project("build-image-multi-module")

@ -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-security-opts</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-no-fork</goal>
</goals>
<configuration>
<image>
<builder>projects.registry.vmware.com/springboot/spring-boot-cnb-builder:0.0.2</builder>
<security-options/>
</image>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,28 @@
/*
* Copyright 2012-2023 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"
}
}
}

@ -79,6 +79,8 @@ public class Image {
String applicationDirectory; String applicationDirectory;
List<String> securityOptions;
/** /**
* The name of the created image. * The name of the created image.
* @return the image name * @return the image name
@ -260,6 +262,9 @@ public class Image {
if (StringUtils.hasText(this.applicationDirectory)) { if (StringUtils.hasText(this.applicationDirectory)) {
request = request.withApplicationDirectory(this.applicationDirectory); request = request.withApplicationDirectory(this.applicationDirectory);
} }
if (this.securityOptions != null) {
request = request.withSecurityOptions(this.securityOptions);
}
return request; return request;
} }

@ -18,6 +18,7 @@ package org.springframework.boot.maven;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.Artifact;
@ -234,6 +235,22 @@ class ImageTests {
assertThat(request.getApplicationDirectory()).isEqualTo("/application"); assertThat(request.getApplicationDirectory()).isEqualTo("/application");
} }
@Test
void getBuildRequestWhenHasSecurityOptionsUsesSecurityOptions() {
Image image = new Image();
image.securityOptions = List.of("label=user:USER", "label=role:ROLE");
BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent());
assertThat(request.getSecurityOptions()).containsExactly("label=user:USER", "label=role:ROLE");
}
@Test
void getBuildRequestWhenHasEmptySecurityOptionsUsesSecurityOptions() {
Image image = new Image();
image.securityOptions = Collections.emptyList();
BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent());
assertThat(request.getSecurityOptions()).isEmpty();
}
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());

Loading…
Cancel
Save