Add support for untrusted CNB builders

A `trustBuilder` configuration option has been added to the Maven and
Gradle CNB integration image building goal and task. A known set of
builders published by Paketo, Heroku, and Google are trusted by default,
all other builders are untrusted by default.

Closes gh-41352
This commit is contained in:
Scott Frederick 2024-07-11 11:43:38 -05:00
parent c848a5e3ed
commit 224b06982e
62 changed files with 1654 additions and 207 deletions

View File

@ -45,9 +45,20 @@ import org.springframework.util.Assert;
*/ */
public class BuildRequest { public class BuildRequest {
static final String DEFAULT_BUILDER_IMAGE_NAME = "paketobuildpacks/builder-jammy-tiny:latest"; static final String DEFAULT_BUILDER_IMAGE_NAME = "paketobuildpacks/builder-jammy-tiny";
private static final ImageReference DEFAULT_BUILDER = ImageReference.of(DEFAULT_BUILDER_IMAGE_NAME); static final String DEFAULT_BUILDER_IMAGE_REF = DEFAULT_BUILDER_IMAGE_NAME + ":latest";
static final List<ImageReference> KNOWN_TRUSTED_BUILDERS = List.of(
ImageReference.of("paketobuildpacks/builder-jammy-tiny"),
ImageReference.of("paketobuildpacks/builder-jammy-base"),
ImageReference.of("paketobuildpacks/builder-jammy-full"),
ImageReference.of("paketobuildpacks/builder-jammy-buildpackless-tiny"),
ImageReference.of("paketobuildpacks/builder-jammy-buildpackless-base"),
ImageReference.of("paketobuildpacks/builder-jammy-buildpackless-full"),
ImageReference.of("gcr.io/buildpacks/builder"), ImageReference.of("heroku/builder"));
private static final ImageReference DEFAULT_BUILDER = ImageReference.of(DEFAULT_BUILDER_IMAGE_REF);
private final ImageReference name; private final ImageReference name;
@ -55,6 +66,8 @@ public class BuildRequest {
private final ImageReference builder; private final ImageReference builder;
private final Boolean trustBuilder;
private final ImageReference runImage; private final ImageReference runImage;
private final Creator creator; private final Creator creator;
@ -95,6 +108,7 @@ public class BuildRequest {
this.name = name.inTaggedForm(); this.name = name.inTaggedForm();
this.applicationContent = applicationContent; this.applicationContent = applicationContent;
this.builder = DEFAULT_BUILDER; this.builder = DEFAULT_BUILDER;
this.trustBuilder = null;
this.runImage = null; this.runImage = null;
this.env = Collections.emptyMap(); this.env = Collections.emptyMap();
this.cleanCache = false; this.cleanCache = false;
@ -118,7 +132,8 @@ public class BuildRequest {
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, List<String> securityOptions) { Cache launchCache, Instant createdDate, String applicationDirectory, List<String> securityOptions,
Boolean trustBuilder) {
this.name = name; this.name = name;
this.applicationContent = applicationContent; this.applicationContent = applicationContent;
this.builder = builder; this.builder = builder;
@ -139,6 +154,7 @@ public class BuildRequest {
this.createdDate = createdDate; this.createdDate = createdDate;
this.applicationDirectory = applicationDirectory; this.applicationDirectory = applicationDirectory;
this.securityOptions = securityOptions; this.securityOptions = securityOptions;
this.trustBuilder = trustBuilder;
} }
/** /**
@ -151,7 +167,20 @@ 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.securityOptions); this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.trustBuilder);
}
/**
* Return a new {@link BuildRequest} with an updated trust builder setting.
* @param trustBuilder {@code true} if the builder should be treated as trusted,
* {@code false} otherwise
* @return an updated build request
*/
public BuildRequest withTrustBuilder(boolean trustBuilder) {
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, this.securityOptions, trustBuilder);
} }
/** /**
@ -163,7 +192,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.securityOptions); this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.trustBuilder);
} }
/** /**
@ -176,7 +205,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.securityOptions); this.applicationDirectory, this.securityOptions, this.trustBuilder);
} }
/** /**
@ -193,7 +222,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.securityOptions); this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions, this.trustBuilder);
} }
/** /**
@ -208,7 +237,8 @@ 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.securityOptions); this.buildCache, this.launchCache, this.createdDate, this.applicationDirectory, this.securityOptions,
this.trustBuilder);
} }
/** /**
@ -220,7 +250,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.securityOptions); this.applicationDirectory, this.securityOptions, this.trustBuilder);
} }
/** /**
@ -232,7 +262,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.securityOptions); this.applicationDirectory, this.securityOptions, this.trustBuilder);
} }
/** /**
@ -244,7 +274,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.securityOptions); this.applicationDirectory, this.securityOptions, this.trustBuilder);
} }
/** /**
@ -256,7 +286,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.securityOptions); this.applicationDirectory, this.securityOptions, this.trustBuilder);
} }
/** /**
@ -281,7 +311,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.securityOptions); this.applicationDirectory, this.securityOptions, this.trustBuilder);
} }
/** /**
@ -306,7 +336,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.securityOptions); this.applicationDirectory, this.securityOptions, this.trustBuilder);
} }
/** /**
@ -319,7 +349,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.securityOptions); this.applicationDirectory, this.securityOptions, this.trustBuilder);
} }
/** /**
@ -342,7 +372,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.securityOptions); this.applicationDirectory, this.securityOptions, this.trustBuilder);
} }
/** /**
@ -356,7 +386,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.securityOptions); this.applicationDirectory, this.securityOptions, this.trustBuilder);
} }
/** /**
@ -369,7 +399,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.securityOptions); this.applicationDirectory, this.securityOptions, this.trustBuilder);
} }
/** /**
@ -382,7 +412,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.securityOptions); this.applicationDirectory, this.securityOptions, this.trustBuilder);
} }
/** /**
@ -395,7 +425,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, this.securityOptions); parseCreatedDate(createdDate), this.applicationDirectory, this.securityOptions, this.trustBuilder);
} }
private Instant parseCreatedDate(String createdDate) { private Instant parseCreatedDate(String createdDate) {
@ -420,7 +450,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.createdDate, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
applicationDirectory, this.securityOptions); applicationDirectory, this.securityOptions, this.trustBuilder);
} }
/** /**
@ -434,7 +464,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.createdDate, this.network, this.tags, this.buildWorkspace, this.buildCache, this.launchCache, this.createdDate,
this.applicationDirectory, securityOptions); this.applicationDirectory, securityOptions, this.trustBuilder);
} }
/** /**
@ -464,6 +494,19 @@ public class BuildRequest {
return this.builder; return this.builder;
} }
/**
* Return whether the builder should be treated as trusted.
* @return the trust builder flag
* @since 3.4.0
*/
public boolean isTrustBuilder() {
return (this.trustBuilder != null) ? this.trustBuilder : isBuilderKnownAndTrusted();
}
private boolean isBuilderKnownAndTrusted() {
return KNOWN_TRUSTED_BUILDERS.stream().anyMatch((builder) -> builder.getName().equals(this.builder.getName()));
}
/** /**
* Return the run image that should be used, if provided. * Return the run image that should be used, if provided.
* @return the run image * @return the run image

View File

@ -161,43 +161,104 @@ class Lifecycle implements Closeable {
if (this.request.isCleanCache()) { if (this.request.isCleanCache()) {
deleteCache(this.buildCache); deleteCache(this.buildCache);
} }
if (this.request.isTrustBuilder()) {
run(createPhase()); run(createPhase());
}
else {
run(analyzePhase());
run(detectPhase());
if (!this.request.isCleanCache()) {
run(restorePhase());
}
else {
this.log.skippingPhase("restorer", "because 'cleanCache' is enabled");
}
run(buildPhase());
run(exportPhase());
}
this.log.executedLifecycle(this.request); this.log.executedLifecycle(this.request);
} }
private Phase createPhase() { private Phase createPhase() {
Phase phase = new Phase("creator", isVerboseLogging()); Phase phase = new Phase("creator", isVerboseLogging());
phase.withDaemonAccess(); phase.withApp(this.applicationDirectory,
Binding.from(getCacheBindingSource(this.application), this.applicationDirectory));
phase.withPlatform(Directory.PLATFORM);
phase.withRunImage(this.request.getRunImage());
phase.withLayers(Directory.LAYERS, Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS));
phase.withBuildCache(Directory.CACHE, Binding.from(getCacheBindingSource(this.buildCache), Directory.CACHE));
phase.withLaunchCache(Directory.LAUNCH_CACHE,
Binding.from(getCacheBindingSource(this.launchCache), Directory.LAUNCH_CACHE));
configureDaemonAccess(phase); configureDaemonAccess(phase);
phase.withLogLevelArg();
phase.withArgs("-app", this.applicationDirectory);
phase.withArgs("-platform", Directory.PLATFORM);
phase.withArgs("-run-image", this.request.getRunImage());
phase.withArgs("-layers", Directory.LAYERS);
phase.withArgs("-cache-dir", Directory.CACHE);
phase.withArgs("-launch-cache", Directory.LAUNCH_CACHE);
phase.withArgs("-daemon");
if (this.request.isCleanCache()) { if (this.request.isCleanCache()) {
phase.withArgs("-skip-restore"); phase.withSkipRestore();
} }
if (requiresProcessTypeDefault()) { if (requiresProcessTypeDefault()) {
phase.withArgs("-process-type=web"); phase.withProcessType("web");
} }
phase.withArgs(this.request.getName()); phase.withImageName(this.request.getName());
phase.withBinding(Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS)); configureOptions(phase);
phase.withBinding(Binding.from(getCacheBindingSource(this.application), this.applicationDirectory)); configureCreatedDate(phase);
phase.withBinding(Binding.from(getCacheBindingSource(this.buildCache), Directory.CACHE)); return phase;
phase.withBinding(Binding.from(getCacheBindingSource(this.launchCache), Directory.LAUNCH_CACHE));
if (this.request.getBindings() != null) {
this.request.getBindings().forEach(phase::withBinding);
} }
phase.withEnv(PLATFORM_API_VERSION_KEY, this.platformVersion.toString());
if (this.request.getNetwork() != null) { private Phase analyzePhase() {
phase.withNetworkMode(this.request.getNetwork()); Phase phase = new Phase("analyzer", isVerboseLogging());
configureDaemonAccess(phase);
phase.withLaunchCache(Directory.LAUNCH_CACHE,
Binding.from(getCacheBindingSource(this.launchCache), Directory.LAUNCH_CACHE));
phase.withLayers(Directory.LAYERS, Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS));
phase.withRunImage(this.request.getRunImage());
phase.withImageName(this.request.getName());
configureOptions(phase);
return phase;
} }
if (this.request.getCreatedDate() != null) {
phase.withEnv(SOURCE_DATE_EPOCH_KEY, Long.toString(this.request.getCreatedDate().getEpochSecond())); private Phase detectPhase() {
Phase phase = new Phase("detector", isVerboseLogging());
phase.withApp(this.applicationDirectory,
Binding.from(getCacheBindingSource(this.application), this.applicationDirectory));
phase.withLayers(Directory.LAYERS, Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS));
phase.withPlatform(Directory.PLATFORM);
configureOptions(phase);
return phase;
} }
private Phase restorePhase() {
Phase phase = new Phase("restorer", isVerboseLogging());
configureDaemonAccess(phase);
phase.withBuildCache(Directory.CACHE, Binding.from(getCacheBindingSource(this.buildCache), Directory.CACHE));
phase.withLayers(Directory.LAYERS, Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS));
configureOptions(phase);
return phase;
}
private Phase buildPhase() {
Phase phase = new Phase("builder", isVerboseLogging());
phase.withApp(this.applicationDirectory,
Binding.from(getCacheBindingSource(this.application), this.applicationDirectory));
phase.withLayers(Directory.LAYERS, Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS));
phase.withPlatform(Directory.PLATFORM);
configureOptions(phase);
return phase;
}
private Phase exportPhase() {
Phase phase = new Phase("exporter", isVerboseLogging());
configureDaemonAccess(phase);
phase.withApp(this.applicationDirectory,
Binding.from(getCacheBindingSource(this.application), this.applicationDirectory));
phase.withBuildCache(Directory.CACHE, Binding.from(getCacheBindingSource(this.buildCache), Directory.CACHE));
phase.withLaunchCache(Directory.LAUNCH_CACHE,
Binding.from(getCacheBindingSource(this.launchCache), Directory.LAUNCH_CACHE));
phase.withLayers(Directory.LAYERS, Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS));
if (requiresProcessTypeDefault()) {
phase.withProcessType("web");
}
phase.withImageName(this.request.getName());
configureOptions(phase);
configureCreatedDate(phase);
return phase; return phase;
} }
@ -238,6 +299,7 @@ class Lifecycle implements Closeable {
} }
private void configureDaemonAccess(Phase phase) { private void configureDaemonAccess(Phase phase) {
phase.withDaemonAccess();
if (this.dockerHost != null) { if (this.dockerHost != null) {
if (this.dockerHost.isRemote()) { if (this.dockerHost.isRemote()) {
phase.withEnv("DOCKER_HOST", this.dockerHost.getAddress()); phase.withEnv("DOCKER_HOST", this.dockerHost.getAddress());
@ -258,6 +320,22 @@ class Lifecycle implements Closeable {
} }
} }
private void configureCreatedDate(Phase phase) {
if (this.request.getCreatedDate() != null) {
phase.withEnv(SOURCE_DATE_EPOCH_KEY, Long.toString(this.request.getCreatedDate().getEpochSecond()));
}
}
private void configureOptions(Phase phase) {
if (this.request.getBindings() != null) {
this.request.getBindings().forEach(phase::withBinding);
}
if (this.request.getNetwork() != null) {
phase.withNetworkMode(this.request.getNetwork());
}
phase.withEnv(PLATFORM_API_VERSION_KEY, this.platformVersion.toString());
}
private boolean isVerboseLogging() { private boolean isVerboseLogging() {
return this.request.isVerboseLogging() && this.lifecycleVersion.isEqualOrGreaterThan(LOGGING_MINIMUM_VERSION); return this.request.isVerboseLogging() && this.lifecycleVersion.isEqualOrGreaterThan(LOGGING_MINIMUM_VERSION);
} }
@ -269,7 +347,7 @@ class Lifecycle implements Closeable {
private void run(Phase phase) throws IOException { private void run(Phase phase) throws IOException {
Consumer<LogUpdateEvent> logConsumer = this.log.runningPhase(this.request, phase.getName()); Consumer<LogUpdateEvent> logConsumer = this.log.runningPhase(this.request, phase.getName());
ContainerConfig containerConfig = ContainerConfig.of(this.builder.getName(), phase::apply); ContainerConfig containerConfig = ContainerConfig.of(this.builder.getName(), phase::apply);
ContainerReference reference = createContainer(containerConfig); ContainerReference reference = createContainer(containerConfig, phase.requiresApp());
try { try {
this.docker.container().start(reference); this.docker.container().start(reference);
this.docker.container().logs(reference, logConsumer::accept); this.docker.container().logs(reference, logConsumer::accept);
@ -283,8 +361,8 @@ class Lifecycle implements Closeable {
} }
} }
private ContainerReference createContainer(ContainerConfig config) throws IOException { private ContainerReference createContainer(ContainerConfig config, boolean requiresAppUpload) throws IOException {
if (this.applicationVolumePopulated) { if (!requiresAppUpload || this.applicationVolumePopulated) {
return this.docker.container().create(config); return this.docker.container().create(config);
} }
try { try {
@ -339,8 +417,7 @@ class Lifecycle implements Closeable {
* <p> * <p>
* Maps to the {@code <layers...>} concept in the * Maps to the {@code <layers...>} concept in the
* <a href="https://github.com/buildpacks/spec/blob/master/buildpack.md">buildpack * <a href="https://github.com/buildpacks/spec/blob/master/buildpack.md">buildpack
* specification</a> and the {@code -layers} argument from the reference lifecycle * specification</a> and the {@code -layers} argument to lifecycle phases.
* implementation.
*/ */
static final String LAYERS = "/layers"; static final String LAYERS = "/layers";
@ -367,8 +444,7 @@ class Lifecycle implements Closeable {
* <p> * <p>
* Maps to the {@code <platform>/env} and {@code <platform>/#} concepts in the * Maps to the {@code <platform>/env} and {@code <platform>/#} concepts in the
* <a href="https://github.com/buildpacks/spec/blob/master/buildpack.md">buildpack * <a href="https://github.com/buildpacks/spec/blob/master/buildpack.md">buildpack
* specification</a> and the {@code -platform} argument from the reference * specification</a> and the {@code -platform} argument to lifecycle phases.
* lifecycle implementation.
*/ */
static final String PLATFORM = "/platform"; static final String PLATFORM = "/platform";
@ -377,8 +453,7 @@ class Lifecycle implements Closeable {
* image {@link BuildRequest#getName() name} being built, and is persistent across * image {@link BuildRequest#getName() name} being built, and is persistent across
* invocations even if the application content has changed. * invocations even if the application content has changed.
* <p> * <p>
* Maps to the {@code -path} argument from the reference lifecycle implementation * Maps to the {@code -path} argument to lifecycle phases.
* cache and restore phases
*/ */
static final String CACHE = "/cache"; static final String CACHE = "/cache";
@ -387,8 +462,7 @@ class Lifecycle implements Closeable {
* based on the image {@link BuildRequest#getName() name} being built, and is * based on the image {@link BuildRequest#getName() name} being built, and is
* persistent across invocations even if the application content has changed. * persistent across invocations even if the application content has changed.
* <p> * <p>
* Maps to the {@code -launch-cache} argument from the reference lifecycle * Maps to the {@code -launch-cache} argument to lifecycle phases.
* implementation export phase
*/ */
static final String LAUNCH_CACHE = "/launch-cache"; static final String LAUNCH_CACHE = "/launch-cache";

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2022 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -24,6 +24,7 @@ import java.util.Map;
import org.springframework.boot.buildpack.platform.docker.type.Binding; import org.springframework.boot.buildpack.platform.docker.type.Binding;
import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig; import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@ -37,8 +38,6 @@ class Phase {
private final String name; private final String name;
private final boolean verboseLogging;
private boolean daemonAccess = false; private boolean daemonAccess = false;
private final List<String> args = new ArrayList<>(); private final List<String> args = new ArrayList<>();
@ -51,6 +50,8 @@ class Phase {
private String networkMode; private String networkMode;
private boolean requiresApp = false;
/** /**
* 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
@ -58,22 +59,65 @@ class Phase {
*/ */
Phase(String name, boolean verboseLogging) { Phase(String name, boolean verboseLogging) {
this.name = name; this.name = name;
this.verboseLogging = verboseLogging; withLogLevelArg(verboseLogging);
}
void withApp(String path, Binding binding) {
withArgs("-app", path);
withBinding(binding);
this.requiresApp = true;
}
void withBuildCache(String path, Binding binding) {
withArgs("-cache-dir", path);
withBinding(binding);
} }
/** /**
* Update this phase with Docker daemon access. * Update this phase with Docker daemon access.
*/ */
void withDaemonAccess() { void withDaemonAccess() {
this.withArgs("-daemon");
this.daemonAccess = true; this.daemonAccess = true;
} }
void withImageName(ImageReference imageName) {
withArgs(imageName);
}
void withLaunchCache(String path, Binding binding) {
withArgs("-launch-cache", path);
withBinding(binding);
}
void withLayers(String path, Binding binding) {
withArgs("-layers", path);
withBinding(binding);
}
void withPlatform(String path) {
withArgs("-platform", path);
}
void withProcessType(String type) {
withArgs("-process-type", type);
}
void withRunImage(ImageReference runImage) {
withArgs("-run-image", runImage);
}
void withSkipRestore() {
withArgs("-skip-restore");
}
/** /**
* Update this phase with a debug log level arguments if verbose logging has been * Update this phase with a debug log level arguments if verbose logging has been
* requested. * requested.
* @param verboseLogging if verbose logging is requested
*/ */
void withLogLevelArg() { private void withLogLevelArg(boolean verboseLogging) {
if (this.verboseLogging) { if (verboseLogging) {
this.args.add("-log-level"); this.args.add("-log-level");
this.args.add("debug"); this.args.add("debug");
} }
@ -128,6 +172,10 @@ class Phase {
return this.name; return this.name;
} }
boolean requiresApp() {
return this.requiresApp;
}
@Override @Override
public String toString() { public String toString() {
return this.name; return this.name;

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2023 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -27,14 +27,18 @@ import java.time.ZoneId;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Stream;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.boot.buildpack.platform.docker.type.Binding; import org.springframework.boot.buildpack.platform.docker.type.Binding;
import org.springframework.boot.buildpack.platform.docker.type.ImageName;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.io.Owner; import org.springframework.boot.buildpack.platform.io.Owner;
import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.boot.buildpack.platform.io.TarArchive;
@ -64,7 +68,7 @@ class BuildRequestTests {
writeTestJarFile(jarFile); writeTestJarFile(jarFile);
BuildRequest request = BuildRequest.forJarFile(jarFile); BuildRequest request = BuildRequest.forJarFile(jarFile);
assertThat(request.getName()).hasToString("docker.io/library/my-app:0.0.1"); assertThat(request.getName()).hasToString("docker.io/library/my-app:0.0.1");
assertThat(request.getBuilder()).hasToString("docker.io/" + BuildRequest.DEFAULT_BUILDER_IMAGE_NAME); assertThat(request.getBuilder()).hasToString("docker.io/" + BuildRequest.DEFAULT_BUILDER_IMAGE_REF);
assertThat(request.getApplicationContent(Owner.ROOT)).satisfies(this::hasExpectedJarContent); assertThat(request.getApplicationContent(Owner.ROOT)).satisfies(this::hasExpectedJarContent);
assertThat(request.getEnv()).isEmpty(); assertThat(request.getEnv()).isEmpty();
} }
@ -75,7 +79,7 @@ class BuildRequestTests {
writeTestJarFile(jarFile); writeTestJarFile(jarFile);
BuildRequest request = BuildRequest.forJarFile(ImageReference.of("test-app"), jarFile); BuildRequest request = BuildRequest.forJarFile(ImageReference.of("test-app"), jarFile);
assertThat(request.getName()).hasToString("docker.io/library/test-app:latest"); assertThat(request.getName()).hasToString("docker.io/library/test-app:latest");
assertThat(request.getBuilder()).hasToString("docker.io/" + BuildRequest.DEFAULT_BUILDER_IMAGE_NAME); assertThat(request.getBuilder()).hasToString("docker.io/" + BuildRequest.DEFAULT_BUILDER_IMAGE_REF);
assertThat(request.getApplicationContent(Owner.ROOT)).satisfies(this::hasExpectedJarContent); assertThat(request.getApplicationContent(Owner.ROOT)).satisfies(this::hasExpectedJarContent);
assertThat(request.getEnv()).isEmpty(); assertThat(request.getEnv()).isEmpty();
} }
@ -104,6 +108,7 @@ class BuildRequestTests {
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")) BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar"))
.withBuilder(ImageReference.of("spring/builder")); .withBuilder(ImageReference.of("spring/builder"));
assertThat(request.getBuilder()).hasToString("docker.io/spring/builder:latest"); assertThat(request.getBuilder()).hasToString("docker.io/spring/builder:latest");
assertThat(request.isTrustBuilder()).isFalse();
} }
@Test @Test
@ -113,6 +118,53 @@ class BuildRequestTests {
.of("spring/builder@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d")); .of("spring/builder@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"));
assertThat(request.getBuilder()).hasToString( assertThat(request.getBuilder()).hasToString(
"docker.io/spring/builder@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"); "docker.io/spring/builder@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d");
assertThat(request.isTrustBuilder()).isFalse();
}
@Test
void withoutBuilderTrustsDefaultBuilder() throws IOException {
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar"));
assertThat(request.isTrustBuilder()).isTrue();
}
@Test
void withoutBuilderTrustsDefaultBuilderWithDifferentTag() throws IOException {
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar"))
.withBuilder(ImageReference.of(ImageName.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME), "other"));
assertThat(request.isTrustBuilder()).isTrue();
}
@Test
void withoutBuilderTrustsDefaultBuilderWithDigest() throws IOException {
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar"))
.withBuilder(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)
.withDigest("sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"));
assertThat(request.isTrustBuilder()).isTrue();
}
@ParameterizedTest
@MethodSource("trustedBuilders")
void withKnownTrustedBuilderTrustsBuilder(ImageReference builder) throws IOException {
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")).withBuilder(builder);
assertThat(request.isTrustBuilder()).isTrue();
}
static Stream<ImageReference> trustedBuilders() {
return BuildRequest.KNOWN_TRUSTED_BUILDERS.stream();
}
@Test
void withoutTrustBuilderAndDefaultBuilderUpdatesTrustsBuilder() throws IOException {
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")).withTrustBuilder(false);
assertThat(request.isTrustBuilder()).isFalse();
}
@Test
void withTrustBuilderAndBuilderUpdatesTrustBuilder() throws IOException {
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar"))
.withBuilder(ImageReference.of("spring/builder"))
.withTrustBuilder(true);
assertThat(request.isTrustBuilder()).isTrue();
} }
@Test @Test

View File

@ -86,7 +86,7 @@ class BuilderTests {
DockerApi docker = mockDockerApi(); DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image.json"); Image builderImage = loadImage("image.json");
Image runImage = loadImage("run-image.json"); Image runImage = loadImage("run-image.json");
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull())) given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull()))
.willAnswer(withPulledImage(builderImage)); .willAnswer(withPulledImage(builderImage));
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()))
.willAnswer(withPulledImage(runImage)); .willAnswer(withPulledImage(runImage));
@ -97,7 +97,7 @@ class BuilderTests {
assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
ArgumentCaptor<ImageArchive> archive = ArgumentCaptor.forClass(ImageArchive.class); ArgumentCaptor<ImageArchive> archive = ArgumentCaptor.forClass(ImageArchive.class);
then(docker.image()).should() then(docker.image()).should()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull()); .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull());
then(docker.image()).should() then(docker.image()).should()
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()); .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull());
then(docker.image()).should().load(archive.capture(), any()); then(docker.image()).should().load(archive.capture(), any());
@ -115,7 +115,7 @@ class BuilderTests {
.withBuilderRegistryTokenAuthentication("builder token") .withBuilderRegistryTokenAuthentication("builder token")
.withPublishRegistryTokenAuthentication("publish token"); .withPublishRegistryTokenAuthentication("publish token");
given(docker.image() given(docker.image()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(),
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))) eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())))
.willAnswer(withPulledImage(builderImage)); .willAnswer(withPulledImage(builderImage));
given(docker.image() given(docker.image()
@ -129,7 +129,7 @@ class BuilderTests {
assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'"); assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
ArgumentCaptor<ImageArchive> archive = ArgumentCaptor.forClass(ImageArchive.class); ArgumentCaptor<ImageArchive> archive = ArgumentCaptor.forClass(ImageArchive.class);
then(docker.image()).should() then(docker.image()).should()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(),
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())); eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()));
then(docker.image()).should() then(docker.image()).should()
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(),
@ -168,7 +168,7 @@ class BuilderTests {
DockerApi docker = mockDockerApi(); DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image-with-run-image-digest.json"); Image builderImage = loadImage("image-with-run-image-digest.json");
Image runImage = loadImage("run-image.json"); Image runImage = loadImage("run-image.json");
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull())) given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull()))
.willAnswer(withPulledImage(builderImage)); .willAnswer(withPulledImage(builderImage));
given(docker.image() given(docker.image()
.pull(eq(ImageReference .pull(eq(ImageReference
@ -211,7 +211,7 @@ class BuilderTests {
DockerApi docker = mockDockerApi(); DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image.json"); Image builderImage = loadImage("image.json");
Image runImage = loadImage("run-image.json"); Image runImage = loadImage("run-image.json");
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull())) given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull()))
.willAnswer(withPulledImage(builderImage)); .willAnswer(withPulledImage(builderImage));
given(docker.image().pull(eq(ImageReference.of("example.com/custom/run:latest")), any(), isNull())) given(docker.image().pull(eq(ImageReference.of("example.com/custom/run:latest")), any(), isNull()))
.willAnswer(withPulledImage(runImage)); .willAnswer(withPulledImage(runImage));
@ -231,11 +231,11 @@ class BuilderTests {
DockerApi docker = mockDockerApi(); DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image.json"); Image builderImage = loadImage("image.json");
Image runImage = loadImage("run-image.json"); Image runImage = loadImage("run-image.json");
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull())) given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull()))
.willAnswer(withPulledImage(builderImage)); .willAnswer(withPulledImage(builderImage));
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()))
.willAnswer(withPulledImage(runImage)); .willAnswer(withPulledImage(runImage));
given(docker.image().inspect(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)))) given(docker.image().inspect(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF))))
.willReturn(builderImage); .willReturn(builderImage);
given(docker.image().inspect(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")))) given(docker.image().inspect(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb"))))
.willReturn(runImage); .willReturn(runImage);
@ -257,11 +257,11 @@ class BuilderTests {
DockerApi docker = mockDockerApi(); DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image.json"); Image builderImage = loadImage("image.json");
Image runImage = loadImage("run-image.json"); Image runImage = loadImage("run-image.json");
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull())) given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull()))
.willAnswer(withPulledImage(builderImage)); .willAnswer(withPulledImage(builderImage));
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()))
.willAnswer(withPulledImage(runImage)); .willAnswer(withPulledImage(runImage));
given(docker.image().inspect(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)))) given(docker.image().inspect(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF))))
.willReturn(builderImage); .willReturn(builderImage);
given(docker.image().inspect(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")))) given(docker.image().inspect(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb"))))
.willReturn(runImage); .willReturn(runImage);
@ -283,11 +283,11 @@ class BuilderTests {
DockerApi docker = mockDockerApi(); DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image.json"); Image builderImage = loadImage("image.json");
Image runImage = loadImage("run-image.json"); Image runImage = loadImage("run-image.json");
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull())) given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull()))
.willAnswer(withPulledImage(builderImage)); .willAnswer(withPulledImage(builderImage));
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()))
.willAnswer(withPulledImage(runImage)); .willAnswer(withPulledImage(runImage));
given(docker.image().inspect(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)))) given(docker.image().inspect(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF))))
.willThrow( .willThrow(
new DockerEngineException("docker://localhost/", new URI("example"), 404, "NOT FOUND", null, null)) new DockerEngineException("docker://localhost/", new URI("example"), 404, "NOT FOUND", null, null))
.willReturn(builderImage); .willReturn(builderImage);
@ -313,7 +313,7 @@ class BuilderTests {
DockerApi docker = mockDockerApi(); DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image.json"); Image builderImage = loadImage("image.json");
Image runImage = loadImage("run-image.json"); Image runImage = loadImage("run-image.json");
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull())) given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull()))
.willAnswer(withPulledImage(builderImage)); .willAnswer(withPulledImage(builderImage));
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()))
.willAnswer(withPulledImage(runImage)); .willAnswer(withPulledImage(runImage));
@ -339,7 +339,7 @@ class BuilderTests {
.withBuilderRegistryTokenAuthentication("builder token") .withBuilderRegistryTokenAuthentication("builder token")
.withPublishRegistryTokenAuthentication("publish token"); .withPublishRegistryTokenAuthentication("publish token");
given(docker.image() given(docker.image()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(),
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))) eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())))
.willAnswer(withPulledImage(builderImage)); .willAnswer(withPulledImage(builderImage));
given(docker.image() given(docker.image()
@ -354,7 +354,7 @@ class BuilderTests {
assertThat(out.toString()).contains("Successfully created image tag 'docker.io/library/my-application:1.2.3'"); assertThat(out.toString()).contains("Successfully created image tag 'docker.io/library/my-application:1.2.3'");
then(docker.image()).should() then(docker.image()).should()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(),
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())); eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()));
then(docker.image()).should() then(docker.image()).should()
.pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), .pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(),
@ -378,7 +378,7 @@ class BuilderTests {
DockerApi docker = mockDockerApi(); DockerApi docker = mockDockerApi();
Image builderImage = loadImage("image.json"); Image builderImage = loadImage("image.json");
Image runImage = loadImage("run-image-with-bad-stack.json"); Image runImage = loadImage("run-image-with-bad-stack.json");
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull())) given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull()))
.willAnswer(withPulledImage(builderImage)); .willAnswer(withPulledImage(builderImage));
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()))
.willAnswer(withPulledImage(runImage)); .willAnswer(withPulledImage(runImage));
@ -395,7 +395,7 @@ class BuilderTests {
DockerApi docker = mockDockerApiLifecycleError(); DockerApi docker = mockDockerApiLifecycleError();
Image builderImage = loadImage("image.json"); Image builderImage = loadImage("image.json");
Image runImage = loadImage("run-image.json"); Image runImage = loadImage("run-image.json");
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull())) given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull()))
.willAnswer(withPulledImage(builderImage)); .willAnswer(withPulledImage(builderImage));
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()))
.willAnswer(withPulledImage(runImage)); .willAnswer(withPulledImage(runImage));
@ -413,7 +413,7 @@ class BuilderTests {
DockerConfiguration dockerConfiguration = new DockerConfiguration() DockerConfiguration dockerConfiguration = new DockerConfiguration()
.withBuilderRegistryTokenAuthentication("builder token"); .withBuilderRegistryTokenAuthentication("builder token");
given(docker.image() given(docker.image()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(),
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))) eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())))
.willAnswer(withPulledImage(builderImage)); .willAnswer(withPulledImage(builderImage));
Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration); Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration);
@ -431,7 +431,7 @@ class BuilderTests {
DockerConfiguration dockerConfiguration = new DockerConfiguration() DockerConfiguration dockerConfiguration = new DockerConfiguration()
.withBuilderRegistryTokenAuthentication("builder token"); .withBuilderRegistryTokenAuthentication("builder token");
given(docker.image() given(docker.image()
.pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), .pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(),
eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))) eq(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader())))
.willAnswer(withPulledImage(builderImage)); .willAnswer(withPulledImage(builderImage));
Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration); Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration);
@ -447,7 +447,7 @@ class BuilderTests {
DockerApi docker = mockDockerApiLifecycleError(); DockerApi docker = mockDockerApiLifecycleError();
Image builderImage = loadImage("image.json"); Image builderImage = loadImage("image.json");
Image runImage = loadImage("run-image.json"); Image runImage = loadImage("run-image.json");
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull())) given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF)), any(), isNull()))
.willAnswer(withPulledImage(builderImage)); .willAnswer(withPulledImage(builderImage));
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull())) given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()))
.willAnswer(withPulledImage(runImage)); .willAnswer(withPulledImage(runImage));
@ -490,7 +490,7 @@ class BuilderTests {
private BuildRequest getTestRequest() { private BuildRequest getTestRequest() {
TarArchive content = mock(TarArchive.class); TarArchive content = mock(TarArchive.class);
ImageReference name = ImageReference.of("my-application"); ImageReference name = ImageReference.of("my-application");
return BuildRequest.of(name, (owner) -> content); return BuildRequest.of(name, (owner) -> content).withTrustBuilder(true);
} }
private Image loadImage(String name) throws IOException { private Image loadImage(String name) throws IOException {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2023 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -34,6 +34,7 @@ import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONAssert;
@ -53,6 +54,7 @@ import org.springframework.boot.buildpack.platform.docker.type.VolumeName;
import org.springframework.boot.buildpack.platform.io.IOConsumer; import org.springframework.boot.buildpack.platform.io.IOConsumer;
import org.springframework.boot.buildpack.platform.io.TarArchive; import org.springframework.boot.buildpack.platform.io.TarArchive;
import org.springframework.boot.buildpack.platform.json.SharedObjectMapper; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
import org.springframework.boot.testsupport.junit.BooleanValueSource;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -87,13 +89,23 @@ class LifecycleTests {
this.docker = mockDockerApi(); this.docker = mockDockerApi();
} }
@Test @ParameterizedTest
void executeExecutesPhases() throws Exception { @BooleanValueSource
void executeExecutesPhases(boolean trustBuilder) throws Exception {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
createLifecycle().execute(); createLifecycle(trustBuilder).execute();
if (trustBuilder) {
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator.json")); assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator.json"));
}
else {
assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer.json"));
assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json"));
assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer.json"));
assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json"));
assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter.json"));
}
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'");
} }
@ -102,7 +114,7 @@ class LifecycleTests {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
BuildRequest request = getTestRequest().withBindings(Binding.of("/host/src/path:/container/dest/path:ro"), BuildRequest request = getTestRequest(true).withBindings(Binding.of("/host/src/path:/container/dest/path:ro"),
Binding.of("volume-name:/container/volume/path:rw")); Binding.of("volume-name:/container/volume/path:rw"));
createLifecycle(request).execute(); createLifecycle(request).execute();
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-bindings.json")); assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-bindings.json"));
@ -114,48 +126,62 @@ class LifecycleTests {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
createLifecycle("builder-metadata-platform-api-0.3.json").execute(); createLifecycle(true, "builder-metadata-platform-api-0.3.json").execute();
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-platform-api-0.3.json")); assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-platform-api-0.3.json"));
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 @ParameterizedTest
void executeOnlyUploadsContentOnce() throws Exception { @BooleanValueSource
void executeOnlyUploadsContentOnce(boolean trustBuilder) throws Exception {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
createLifecycle().execute(); createLifecycle(trustBuilder).execute();
assertThat(this.content).hasSize(1); assertThat(this.content).hasSize(1);
} }
@Test @ParameterizedTest
void executeWhenAlreadyRunThrowsException() throws Exception { @BooleanValueSource
void executeWhenAlreadyRunThrowsException(boolean trustBuilder) throws Exception {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
Lifecycle lifecycle = createLifecycle(); Lifecycle lifecycle = createLifecycle(trustBuilder);
lifecycle.execute(); lifecycle.execute();
assertThatIllegalStateException().isThrownBy(lifecycle::execute) assertThatIllegalStateException().isThrownBy(lifecycle::execute)
.withMessage("Lifecycle has already been executed"); .withMessage("Lifecycle has already been executed");
} }
@Test @ParameterizedTest
void executeWhenBuilderReturnsErrorThrowsException() throws Exception { @BooleanValueSource
void executeWhenBuilderReturnsErrorThrowsException(boolean trustBuilder) throws Exception {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(9, null)); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(9, null));
assertThatExceptionOfType(BuilderException.class).isThrownBy(() -> createLifecycle().execute()) assertThatExceptionOfType(BuilderException.class).isThrownBy(() -> createLifecycle(trustBuilder).execute())
.withMessage("Builder lifecycle 'creator' failed with status code 9"); .withMessage(
"Builder lifecycle '" + ((trustBuilder) ? "creator" : "analyzer") + "' failed with status code 9");
} }
@Test @ParameterizedTest
void executeWhenCleanCacheClearsCache() throws Exception { @BooleanValueSource
void executeWhenCleanCacheClearsCache(boolean trustBuilder) throws Exception {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
BuildRequest request = getTestRequest().withCleanCache(true); BuildRequest request = getTestRequest(trustBuilder).withCleanCache(true);
createLifecycle(request).execute(); createLifecycle(request).execute();
if (trustBuilder) {
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-clean-cache.json")); assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-clean-cache.json"));
}
else {
assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer.json"));
assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json"));
assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json"));
assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter.json"));
assertThat(this.out.toString()).contains("Skipping restorer because 'cleanCache' is enabled");
}
VolumeName name = VolumeName.of("pack-cache-b35197ac41ea.build"); VolumeName name = VolumeName.of("pack-cache-b35197ac41ea.build");
then(this.docker.volume()).should().delete(name, true); then(this.docker.volume()).should().delete(name, true);
} }
@ -166,7 +192,7 @@ class LifecycleTests {
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(() -> createLifecycle("builder-metadata-unsupported-api.json").execute()) .isThrownBy(() -> createLifecycle(true, "builder-metadata-unsupported-api.json").execute())
.withMessageContaining("Detected platform API versions '0.2' are not included in supported versions"); .withMessageContaining("Detected platform API versions '0.2' are not included in supported versions");
} }
@ -176,22 +202,32 @@ class LifecycleTests {
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(() -> createLifecycle("builder-metadata-unsupported-apis.json").execute()) .isThrownBy(() -> createLifecycle(true, "builder-metadata-unsupported-apis.json").execute())
.withMessageContaining("Detected platform API versions '0.1,0.2' are not included in supported versions"); .withMessageContaining("Detected platform API versions '0.1,0.2' are not included in supported versions");
} }
@Test @ParameterizedTest
void executeWhenMultiplePlatformApisSupportedExecutesPhase() throws Exception { @BooleanValueSource
void executeWhenMultiplePlatformApisSupportedExecutesPhase(boolean trustBuilder) throws Exception {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
createLifecycle("builder-metadata-supported-apis.json").execute(); createLifecycle(trustBuilder, "builder-metadata-supported-apis.json").execute();
if (trustBuilder) {
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator.json")); assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator.json"));
} }
else {
assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer.json"));
assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json"));
assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer.json"));
assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json"));
assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter.json"));
}
}
@Test @Test
void closeClearsVolumes() throws Exception { void closeClearsVolumes() throws Exception {
createLifecycle().close(); createLifecycle(true).close();
then(this.docker.volume()).should().delete(VolumeName.of("pack-layers-aaaaaaaaaa"), true); then(this.docker.volume()).should().delete(VolumeName.of("pack-layers-aaaaaaaaaa"), true);
then(this.docker.volume()).should().delete(VolumeName.of("pack-app-aaaaaaaaaa"), true); then(this.docker.volume()).should().delete(VolumeName.of("pack-app-aaaaaaaaaa"), true);
} }
@ -201,92 +237,163 @@ class LifecycleTests {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
BuildRequest request = getTestRequest().withNetwork("test"); BuildRequest request = getTestRequest(true).withNetwork("test");
createLifecycle(request).execute(); createLifecycle(request).execute();
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-network.json")); assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-network.json"));
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 @ParameterizedTest
void executeWithCacheVolumeNamesExecutesPhases() throws Exception { @BooleanValueSource
void executeWithCacheVolumeNamesExecutesPhases(boolean trustBuilder) throws Exception {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
BuildRequest request = getTestRequest().withBuildWorkspace(Cache.volume("work-volume")) BuildRequest request = getTestRequest(trustBuilder).withBuildWorkspace(Cache.volume("work-volume"))
.withBuildCache(Cache.volume("build-volume")) .withBuildCache(Cache.volume("build-volume"))
.withLaunchCache(Cache.volume("launch-volume")); .withLaunchCache(Cache.volume("launch-volume"));
createLifecycle(request).execute(); createLifecycle(request).execute();
if (trustBuilder) {
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-cache-volumes.json")); assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-cache-volumes.json"));
}
else {
assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer-cache-volumes.json"));
assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector-cache-volumes.json"));
assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer-cache-volumes.json"));
assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder-cache-volumes.json"));
assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-cache-volumes.json"));
}
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 @ParameterizedTest
void executeWithCacheBindMountsExecutesPhases() throws Exception { @BooleanValueSource
void executeWithCacheBindMountsExecutesPhases(boolean trustBuilder) throws Exception {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
BuildRequest request = getTestRequest().withBuildWorkspace(Cache.bind("/tmp/work")) BuildRequest request = getTestRequest(trustBuilder).withBuildWorkspace(Cache.bind("/tmp/work"))
.withBuildCache(Cache.bind("/tmp/build-cache")) .withBuildCache(Cache.bind("/tmp/build-cache"))
.withLaunchCache(Cache.bind("/tmp/launch-cache")); .withLaunchCache(Cache.bind("/tmp/launch-cache"));
createLifecycle(request).execute(); createLifecycle(request).execute();
if (trustBuilder) {
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-cache-bind-mounts.json")); assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-cache-bind-mounts.json"));
}
else {
assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer-cache-bind-mounts.json"));
assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector-cache-bind-mounts.json"));
assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer-cache-bind-mounts.json"));
assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder-cache-bind-mounts.json"));
assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-cache-bind-mounts.json"));
}
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 @ParameterizedTest
void executeWithCreatedDateExecutesPhases() throws Exception { @BooleanValueSource
void executeWithCreatedDateExecutesPhases(boolean trustBuilder) throws Exception {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
BuildRequest request = getTestRequest().withCreatedDate("2020-07-01T12:34:56Z"); BuildRequest request = getTestRequest(trustBuilder).withCreatedDate("2020-07-01T12:34:56Z");
createLifecycle(request).execute(); createLifecycle(request).execute();
if (trustBuilder) {
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-created-date.json")); assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-created-date.json"));
}
else {
assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer.json"));
assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json"));
assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer.json"));
assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json"));
assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-created-date.json"));
}
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 @ParameterizedTest
void executeWithApplicationDirectoryExecutesPhases() throws Exception { @BooleanValueSource
void executeWithApplicationDirectoryExecutesPhases(boolean trustBuilder) throws Exception {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
BuildRequest request = getTestRequest().withApplicationDirectory("/application"); BuildRequest request = getTestRequest(trustBuilder).withApplicationDirectory("/application");
createLifecycle(request).execute(); createLifecycle(request).execute();
if (trustBuilder) {
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-app-dir.json")); assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-app-dir.json"));
}
else {
assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer.json"));
assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector-app-dir.json"));
assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer.json"));
assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder-app-dir.json"));
assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-app-dir.json"));
}
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 @ParameterizedTest
void executeWithSecurityOptionsExecutesPhases() throws Exception { @BooleanValueSource
void executeWithSecurityOptionsExecutesPhases(boolean trustBuilder) throws Exception {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
BuildRequest request = getTestRequest().withSecurityOptions(List.of("label=user:USER", "label=role:ROLE")); BuildRequest request = getTestRequest(trustBuilder)
.withSecurityOptions(List.of("label=user:USER", "label=role:ROLE"));
createLifecycle(request).execute(); createLifecycle(request).execute();
if (trustBuilder) {
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-security-opts.json", true)); assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-security-opts.json", true));
}
else {
assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer-security-opts.json"));
assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json"));
assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer-security-opts.json"));
assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json"));
assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-security-opts.json"));
}
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 @ParameterizedTest
void executeWithDockerHostAndRemoteAddressExecutesPhases() throws Exception { @BooleanValueSource
void executeWithDockerHostAndRemoteAddressExecutesPhases(boolean trustBuilder) throws Exception {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
BuildRequest request = getTestRequest(); BuildRequest request = getTestRequest(trustBuilder);
createLifecycle(request, ResolvedDockerHost.from(DockerHostConfiguration.forAddress("tcp://192.168.1.2:2376"))) createLifecycle(request, ResolvedDockerHost.from(DockerHostConfiguration.forAddress("tcp://192.168.1.2:2376")))
.execute(); .execute();
if (trustBuilder) {
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-inherit-remote.json")); assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-inherit-remote.json"));
}
else {
assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer-inherit-remote.json"));
assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json"));
assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer-inherit-remote.json"));
assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json"));
assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-inherit-remote.json"));
}
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 @ParameterizedTest
void executeWithDockerHostAndLocalAddressExecutesPhases() throws Exception { @BooleanValueSource
void executeWithDockerHostAndLocalAddressExecutesPhases(boolean trustBuilder) throws Exception {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId()); given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null)); given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
BuildRequest request = getTestRequest(); BuildRequest request = getTestRequest(trustBuilder);
createLifecycle(request, ResolvedDockerHost.from(DockerHostConfiguration.forAddress("/var/alt.sock"))) createLifecycle(request, ResolvedDockerHost.from(DockerHostConfiguration.forAddress("/var/alt.sock")))
.execute(); .execute();
if (trustBuilder) {
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-inherit-local.json")); assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-inherit-local.json"));
}
else {
assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer-inherit-local.json"));
assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json"));
assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer-inherit-local.json"));
assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json"));
assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-inherit-local.json"));
}
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'");
} }
@ -301,14 +408,16 @@ class LifecycleTests {
return docker; return docker;
} }
private BuildRequest getTestRequest() { private BuildRequest getTestRequest(boolean trustBuilder) {
TarArchive content = mock(TarArchive.class); TarArchive content = mock(TarArchive.class);
ImageReference name = ImageReference.of("my-application"); ImageReference name = ImageReference.of("my-application");
return BuildRequest.of(name, (owner) -> content).withRunImage(ImageReference.of("cloudfoundry/run")); return BuildRequest.of(name, (owner) -> content)
.withRunImage(ImageReference.of("cloudfoundry/run"))
.withTrustBuilder(trustBuilder);
} }
private Lifecycle createLifecycle() throws IOException { private Lifecycle createLifecycle(boolean trustBuilder) throws IOException {
return createLifecycle(getTestRequest()); return createLifecycle(getTestRequest(trustBuilder));
} }
private Lifecycle createLifecycle(BuildRequest request) throws IOException { private Lifecycle createLifecycle(BuildRequest request) throws IOException {
@ -316,9 +425,9 @@ class LifecycleTests {
return createLifecycle(request, builder); return createLifecycle(request, builder);
} }
private Lifecycle createLifecycle(String builderMetadata) throws IOException { private Lifecycle createLifecycle(boolean trustBuilder, String builderMetadata) throws IOException {
EphemeralBuilder builder = mockEphemeralBuilder(builderMetadata); EphemeralBuilder builder = mockEphemeralBuilder(builderMetadata);
return createLifecycle(getTestRequest(), builder); return createLifecycle(getTestRequest(trustBuilder), builder);
} }
private Lifecycle createLifecycle(BuildRequest request, ResolvedDockerHost dockerHost) throws IOException { private Lifecycle createLifecycle(BuildRequest request, ResolvedDockerHost dockerHost) throws IOException {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2022 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -66,7 +66,7 @@ class PhaseTests {
Update update = mock(Update.class); Update update = mock(Update.class);
phase.apply(update); phase.apply(update);
then(update).should().withUser("root"); then(update).should().withUser("root");
then(update).should().withCommand("/cnb/lifecycle/test", NO_ARGS); then(update).should().withCommand("/cnb/lifecycle/test", "-daemon");
then(update).should().withLabel("author", "spring-boot"); then(update).should().withLabel("author", "spring-boot");
then(update).shouldHaveNoMoreInteractions(); then(update).shouldHaveNoMoreInteractions();
} }
@ -74,7 +74,6 @@ class PhaseTests {
@Test @Test
void applyWhenWithLogLevelArgAndVerboseLoggingUpdatesConfigurationWithLogLevel() { void applyWhenWithLogLevelArgAndVerboseLoggingUpdatesConfigurationWithLogLevel() {
Phase phase = new Phase("test", true); Phase phase = new Phase("test", true);
phase.withLogLevelArg();
Update update = mock(Update.class); Update update = mock(Update.class);
phase.apply(update); phase.apply(update);
then(update).should().withCommand("/cnb/lifecycle/test", "-log-level", "debug"); then(update).should().withCommand("/cnb/lifecycle/test", "-log-level", "debug");
@ -85,7 +84,6 @@ class PhaseTests {
@Test @Test
void applyWhenWithLogLevelArgAndNonVerboseLoggingDoesNotUpdateLogLevel() { void applyWhenWithLogLevelArgAndNonVerboseLoggingDoesNotUpdateLogLevel() {
Phase phase = new Phase("test", false); Phase phase = new Phase("test", false);
phase.withLogLevelArg();
Update update = mock(Update.class); Update update = mock(Update.class);
phase.apply(update); phase.apply(update);
then(update).should().withCommand("/cnb/lifecycle/test"); then(update).should().withCommand("/cnb/lifecycle/test");
@ -133,7 +131,7 @@ class PhaseTests {
@Test @Test
void applyWhenWithNetworkModeUpdatesConfigurationWithNetworkMode() { void applyWhenWithNetworkModeUpdatesConfigurationWithNetworkMode() {
Phase phase = new Phase("test", true); Phase phase = new Phase("test", false);
phase.withNetworkMode("test"); phase.withNetworkMode("test");
Update update = mock(Update.class); Update update = mock(Update.class);
phase.apply(update); phase.apply(update);
@ -145,7 +143,7 @@ class PhaseTests {
@Test @Test
void applyWhenWithSecurityOptionsUpdatesConfigurationWithSecurityOptions() { void applyWhenWithSecurityOptionsUpdatesConfigurationWithSecurityOptions() {
Phase phase = new Phase("test", true); Phase phase = new Phase("test", false);
phase.withSecurityOption("option1=value1"); phase.withSecurityOption("option1=value1");
phase.withSecurityOption("option2=value2"); phase.withSecurityOption("option2=value2");
Update update = mock(Update.class); Update update = mock(Update.class);

View File

@ -0,0 +1,31 @@
{
"User": "root",
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/analyzer",
"-daemon",
"-launch-cache",
"/launch-cache",
"-layers",
"/layers",
"-run-image",
"docker.io/cloudfoundry/run:latest",
"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",
"/tmp/launch-cache:/launch-cache",
"/tmp/work-layers:/layers"
],
"SecurityOpt" : [
"label=disable"
]
}
}

View File

@ -0,0 +1,31 @@
{
"User": "root",
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/analyzer",
"-daemon",
"-launch-cache",
"/launch-cache",
"-layers",
"/layers",
"-run-image",
"docker.io/cloudfoundry/run:latest",
"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",
"launch-volume:/launch-cache",
"work-volume-layers:/layers"
],
"SecurityOpt" : [
"label=disable"
]
}
}

View File

@ -0,0 +1,31 @@
{
"User": "root",
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/analyzer",
"-daemon",
"-launch-cache",
"/launch-cache",
"-layers",
"/layers",
"-run-image",
"docker.io/cloudfoundry/run:latest",
"docker.io/library/my-application:latest"
],
"Env": [
"CNB_PLATFORM_API=0.8"
],
"Labels": {
"author": "spring-boot"
},
"HostConfig": {
"Binds": [
"/var/alt.sock:/var/run/docker.sock",
"pack-cache-b35197ac41ea.launch:/launch-cache",
"pack-layers-aaaaaaaaaa:/layers"
],
"SecurityOpt" : [
"label=disable"
]
}
}

View File

@ -0,0 +1,31 @@
{
"User": "root",
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/analyzer",
"-daemon",
"-launch-cache",
"/launch-cache",
"-layers",
"/layers",
"-run-image",
"docker.io/cloudfoundry/run:latest",
"docker.io/library/my-application:latest"
],
"Env": [
"DOCKER_HOST=tcp://192.168.1.2:2376",
"CNB_PLATFORM_API=0.8"
],
"Labels": {
"author": "spring-boot"
},
"HostConfig": {
"Binds": [
"pack-cache-b35197ac41ea.launch:/launch-cache",
"pack-layers-aaaaaaaaaa:/layers"
],
"SecurityOpt" : [
"label=disable"
]
}
}

View File

@ -0,0 +1,32 @@
{
"User": "root",
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/analyzer",
"-daemon",
"-launch-cache",
"/launch-cache",
"-layers",
"/layers",
"-run-image",
"docker.io/cloudfoundry/run:latest",
"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-cache-b35197ac41ea.launch:/launch-cache",
"pack-layers-aaaaaaaaaa:/layers"
],
"SecurityOpt" : [
"label=user:USER",
"label=role:ROLE"
]
}
}

View File

@ -0,0 +1,31 @@
{
"User": "root",
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/analyzer",
"-daemon",
"-launch-cache",
"/launch-cache",
"-layers",
"/layers",
"-run-image",
"docker.io/cloudfoundry/run:latest",
"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-cache-b35197ac41ea.launch:/launch-cache",
"pack-layers-aaaaaaaaaa:/layers"
],
"SecurityOpt" : [
"label=disable"
]
}
}

View File

@ -0,0 +1,24 @@
{
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/builder",
"-app",
"/application",
"-layers",
"/layers",
"-platform",
"/platform"
],
"Env": [
"CNB_PLATFORM_API=0.8"
],
"Labels": {
"author": "spring-boot"
},
"HostConfig": {
"Binds": [
"pack-app-aaaaaaaaaa:/application",
"pack-layers-aaaaaaaaaa:/layers"
]
}
}

View File

@ -0,0 +1,24 @@
{
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/builder",
"-app",
"/workspace",
"-layers",
"/layers",
"-platform",
"/platform"
],
"Env": [
"CNB_PLATFORM_API=0.8"
],
"Labels": {
"author": "spring-boot"
},
"HostConfig": {
"Binds": [
"/tmp/work-app:/workspace",
"/tmp/work-layers:/layers"
]
}
}

View File

@ -0,0 +1,24 @@
{
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/builder",
"-app",
"/workspace",
"-layers",
"/layers",
"-platform",
"/platform"
],
"Env": [
"CNB_PLATFORM_API=0.8"
],
"Labels": {
"author": "spring-boot"
},
"HostConfig": {
"Binds": [
"work-volume-app:/workspace",
"work-volume-layers:/layers"
]
}
}

View File

@ -0,0 +1,24 @@
{
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/builder",
"-app",
"/workspace",
"-layers",
"/layers",
"-platform",
"/platform"
],
"Env": [
"CNB_PLATFORM_API=0.8"
],
"Labels": {
"author": "spring-boot"
},
"HostConfig": {
"Binds": [
"pack-app-aaaaaaaaaa:/workspace",
"pack-layers-aaaaaaaaaa:/layers"
]
}
}

View File

@ -26,11 +26,11 @@
}, },
"HostConfig": { "HostConfig": {
"Binds": [ "Binds": [
"/var/run/docker.sock:/var/run/docker.sock",
"pack-layers-aaaaaaaaaa:/layers",
"pack-app-aaaaaaaaaa:/application", "pack-app-aaaaaaaaaa:/application",
"pack-layers-aaaaaaaaaa:/layers",
"pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.build:/cache",
"pack-cache-b35197ac41ea.launch:/launch-cache" "pack-cache-b35197ac41ea.launch:/launch-cache",
"/var/run/docker.sock:/var/run/docker.sock"
], ],
"SecurityOpt" : [ "SecurityOpt" : [
"label=disable" "label=disable"

View File

@ -26,11 +26,11 @@
}, },
"HostConfig": { "HostConfig": {
"Binds": [ "Binds": [
"/var/run/docker.sock:/var/run/docker.sock",
"pack-layers-aaaaaaaaaa:/layers",
"pack-app-aaaaaaaaaa:/workspace", "pack-app-aaaaaaaaaa:/workspace",
"pack-layers-aaaaaaaaaa:/layers",
"pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.build:/cache",
"pack-cache-b35197ac41ea.launch:/launch-cache", "pack-cache-b35197ac41ea.launch:/launch-cache",
"/var/run/docker.sock:/var/run/docker.sock",
"/host/src/path:/container/dest/path:ro", "/host/src/path:/container/dest/path:ro",
"volume-name:/container/volume/path:rw" "volume-name:/container/volume/path:rw"
], ],

View File

@ -26,11 +26,11 @@
}, },
"HostConfig": { "HostConfig": {
"Binds": [ "Binds": [
"/var/run/docker.sock:/var/run/docker.sock",
"/tmp/work-layers:/layers",
"/tmp/work-app:/workspace", "/tmp/work-app:/workspace",
"/tmp/work-layers:/layers",
"/tmp/build-cache:/cache", "/tmp/build-cache:/cache",
"/tmp/launch-cache:/launch-cache" "/tmp/launch-cache:/launch-cache",
"/var/run/docker.sock:/var/run/docker.sock"
], ],
"SecurityOpt" : [ "SecurityOpt" : [
"label=disable" "label=disable"

View File

@ -26,11 +26,11 @@
}, },
"HostConfig": { "HostConfig": {
"Binds": [ "Binds": [
"/var/run/docker.sock:/var/run/docker.sock",
"work-volume-layers:/layers",
"work-volume-app:/workspace", "work-volume-app:/workspace",
"work-volume-layers:/layers",
"build-volume:/cache", "build-volume:/cache",
"launch-volume:/launch-cache" "launch-volume:/launch-cache",
"/var/run/docker.sock:/var/run/docker.sock"
], ],
"SecurityOpt" : [ "SecurityOpt" : [
"label=disable" "label=disable"

View File

@ -27,11 +27,11 @@
}, },
"HostConfig": { "HostConfig": {
"Binds": [ "Binds": [
"/var/run/docker.sock:/var/run/docker.sock",
"pack-layers-aaaaaaaaaa:/layers",
"pack-app-aaaaaaaaaa:/workspace", "pack-app-aaaaaaaaaa:/workspace",
"pack-layers-aaaaaaaaaa:/layers",
"pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.build:/cache",
"pack-cache-b35197ac41ea.launch:/launch-cache" "pack-cache-b35197ac41ea.launch:/launch-cache",
"/var/run/docker.sock:/var/run/docker.sock"
], ],
"SecurityOpt" : [ "SecurityOpt" : [
"label=disable" "label=disable"

View File

@ -27,11 +27,11 @@
}, },
"HostConfig": { "HostConfig": {
"Binds": [ "Binds": [
"/var/run/docker.sock:/var/run/docker.sock",
"pack-layers-aaaaaaaaaa:/layers",
"pack-app-aaaaaaaaaa:/workspace", "pack-app-aaaaaaaaaa:/workspace",
"pack-layers-aaaaaaaaaa:/layers",
"pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.build:/cache",
"pack-cache-b35197ac41ea.launch:/launch-cache" "pack-cache-b35197ac41ea.launch:/launch-cache",
"/var/run/docker.sock:/var/run/docker.sock"
], ],
"SecurityOpt" : [ "SecurityOpt" : [
"label=disable" "label=disable"

View File

@ -26,11 +26,11 @@
}, },
"HostConfig": { "HostConfig": {
"Binds": [ "Binds": [
"/var/alt.sock:/var/run/docker.sock",
"pack-layers-aaaaaaaaaa:/layers",
"pack-app-aaaaaaaaaa:/workspace", "pack-app-aaaaaaaaaa:/workspace",
"pack-layers-aaaaaaaaaa:/layers",
"pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.build:/cache",
"pack-cache-b35197ac41ea.launch:/launch-cache" "pack-cache-b35197ac41ea.launch:/launch-cache",
"/var/alt.sock:/var/run/docker.sock"
], ],
"SecurityOpt" : [ "SecurityOpt" : [
"label=disable" "label=disable"

View File

@ -27,8 +27,8 @@
}, },
"HostConfig": { "HostConfig": {
"Binds": [ "Binds": [
"pack-layers-aaaaaaaaaa:/layers",
"pack-app-aaaaaaaaaa:/workspace", "pack-app-aaaaaaaaaa:/workspace",
"pack-layers-aaaaaaaaaa:/layers",
"pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.build:/cache",
"pack-cache-b35197ac41ea.launch:/launch-cache" "pack-cache-b35197ac41ea.launch:/launch-cache"
], ],

View File

@ -27,11 +27,11 @@
"HostConfig": { "HostConfig": {
"NetworkMode": "test", "NetworkMode": "test",
"Binds": [ "Binds": [
"/var/run/docker.sock:/var/run/docker.sock",
"pack-layers-aaaaaaaaaa:/layers",
"pack-app-aaaaaaaaaa:/workspace", "pack-app-aaaaaaaaaa:/workspace",
"pack-layers-aaaaaaaaaa:/layers",
"pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.build:/cache",
"pack-cache-b35197ac41ea.launch:/launch-cache" "pack-cache-b35197ac41ea.launch:/launch-cache",
"/var/run/docker.sock:/var/run/docker.sock"
], ],
"SecurityOpt" : [ "SecurityOpt" : [
"label=disable" "label=disable"

View File

@ -7,7 +7,13 @@
"author" : "spring-boot" "author" : "spring-boot"
}, },
"HostConfig" : { "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" ], "Binds" : [
"pack-app-aaaaaaaaaa:/workspace",
"pack-layers-aaaaaaaaaa:/layers",
"pack-cache-b35197ac41ea.build:/cache",
"pack-cache-b35197ac41ea.launch:/launch-cache",
"/var/run/docker.sock:/var/run/docker.sock"
],
"SecurityOpt" : [ "SecurityOpt" : [
"label=disable" "label=disable"
] ]

View File

@ -26,11 +26,11 @@
}, },
"HostConfig": { "HostConfig": {
"Binds": [ "Binds": [
"/var/run/docker.sock:/var/run/docker.sock",
"pack-layers-aaaaaaaaaa:/layers",
"pack-app-aaaaaaaaaa:/workspace", "pack-app-aaaaaaaaaa:/workspace",
"pack-layers-aaaaaaaaaa:/layers",
"pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.build:/cache",
"pack-cache-b35197ac41ea.launch:/launch-cache" "pack-cache-b35197ac41ea.launch:/launch-cache",
"/var/run/docker.sock:/var/run/docker.sock"
], ],
"SecurityOpt" : [ "SecurityOpt" : [
"label=user:USER", "label=user:USER",

View File

@ -26,11 +26,11 @@
}, },
"HostConfig": { "HostConfig": {
"Binds": [ "Binds": [
"/var/run/docker.sock:/var/run/docker.sock",
"pack-layers-aaaaaaaaaa:/layers",
"pack-app-aaaaaaaaaa:/workspace", "pack-app-aaaaaaaaaa:/workspace",
"pack-layers-aaaaaaaaaa:/layers",
"pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.build:/cache",
"pack-cache-b35197ac41ea.launch:/launch-cache" "pack-cache-b35197ac41ea.launch:/launch-cache",
"/var/run/docker.sock:/var/run/docker.sock"
], ],
"SecurityOpt" : [ "SecurityOpt" : [
"label=disable" "label=disable"

View File

@ -0,0 +1,24 @@
{
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/detector",
"-app",
"/application",
"-layers",
"/layers",
"-platform",
"/platform"
],
"Env": [
"CNB_PLATFORM_API=0.8"
],
"Labels": {
"author": "spring-boot"
},
"HostConfig": {
"Binds": [
"pack-app-aaaaaaaaaa:/application",
"pack-layers-aaaaaaaaaa:/layers"
]
}
}

View File

@ -0,0 +1,24 @@
{
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/detector",
"-app",
"/workspace",
"-layers",
"/layers",
"-platform",
"/platform"
],
"Env": [
"CNB_PLATFORM_API=0.8"
],
"Labels": {
"author": "spring-boot"
},
"HostConfig": {
"Binds": [
"/tmp/work-app:/workspace",
"/tmp/work-layers:/layers"
]
}
}

View File

@ -0,0 +1,24 @@
{
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/detector",
"-app",
"/workspace",
"-layers",
"/layers",
"-platform",
"/platform"
],
"Env": [
"CNB_PLATFORM_API=0.8"
],
"Labels": {
"author": "spring-boot"
},
"HostConfig": {
"Binds": [
"work-volume-app:/workspace",
"work-volume-layers:/layers"
]
}
}

View File

@ -0,0 +1,24 @@
{
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/detector",
"-app",
"/workspace",
"-layers",
"/layers",
"-platform",
"/platform"
],
"Env": [
"CNB_PLATFORM_API=0.8"
],
"Labels": {
"author": "spring-boot"
},
"HostConfig": {
"Binds": [
"pack-app-aaaaaaaaaa:/workspace",
"pack-layers-aaaaaaaaaa:/layers"
]
}
}

View File

@ -0,0 +1,35 @@
{
"User": "root",
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/exporter",
"-daemon",
"-app",
"/application",
"-cache-dir",
"/cache",
"-launch-cache",
"/launch-cache",
"-layers",
"/layers",
"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-app-aaaaaaaaaa:/application",
"pack-cache-b35197ac41ea.build:/cache",
"pack-cache-b35197ac41ea.launch:/launch-cache",
"pack-layers-aaaaaaaaaa:/layers"
],
"SecurityOpt" : [
"label=disable"
]
}
}

View File

@ -0,0 +1,35 @@
{
"User": "root",
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/exporter",
"-daemon",
"-app",
"/workspace",
"-cache-dir",
"/cache",
"-launch-cache",
"/launch-cache",
"-layers",
"/layers",
"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",
"/tmp/work-app:/workspace",
"/tmp/build-cache:/cache",
"/tmp/launch-cache:/launch-cache",
"/tmp/work-layers:/layers"
],
"SecurityOpt" : [
"label=disable"
]
}
}

View File

@ -0,0 +1,35 @@
{
"User": "root",
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/exporter",
"-daemon",
"-app",
"/workspace",
"-cache-dir",
"/cache",
"-launch-cache",
"/launch-cache",
"-layers",
"/layers",
"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",
"work-volume-app:/workspace",
"build-volume:/cache",
"launch-volume:/launch-cache",
"work-volume-layers:/layers"
],
"SecurityOpt" : [
"label=disable"
]
}
}

View File

@ -0,0 +1,36 @@
{
"User": "root",
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/exporter",
"-daemon",
"-app",
"/workspace",
"-cache-dir",
"/cache",
"-launch-cache",
"/launch-cache",
"-layers",
"/layers",
"docker.io/library/my-application:latest"
],
"Env": [
"CNB_PLATFORM_API=0.8",
"SOURCE_DATE_EPOCH=1593606896"
],
"Labels": {
"author": "spring-boot"
},
"HostConfig": {
"Binds": [
"/var/run/docker.sock:/var/run/docker.sock",
"pack-app-aaaaaaaaaa:/workspace",
"pack-cache-b35197ac41ea.build:/cache",
"pack-cache-b35197ac41ea.launch:/launch-cache",
"pack-layers-aaaaaaaaaa:/layers"
],
"SecurityOpt" : [
"label=disable"
]
}
}

View File

@ -0,0 +1,35 @@
{
"User": "root",
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/exporter",
"-daemon",
"-app",
"/workspace",
"-cache-dir",
"/cache",
"-launch-cache",
"/launch-cache",
"-layers",
"/layers",
"docker.io/library/my-application:latest"
],
"Env": [
"CNB_PLATFORM_API=0.8"
],
"Labels": {
"author": "spring-boot"
},
"HostConfig": {
"Binds": [
"/var/alt.sock:/var/run/docker.sock",
"pack-app-aaaaaaaaaa:/workspace",
"pack-cache-b35197ac41ea.build:/cache",
"pack-cache-b35197ac41ea.launch:/launch-cache",
"pack-layers-aaaaaaaaaa:/layers"
],
"SecurityOpt" : [
"label=disable"
]
}
}

View File

@ -0,0 +1,35 @@
{
"User": "root",
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/exporter",
"-daemon",
"-app",
"/workspace",
"-cache-dir",
"/cache",
"-launch-cache",
"/launch-cache",
"-layers",
"/layers",
"docker.io/library/my-application:latest"
],
"Env": [
"DOCKER_HOST=tcp://192.168.1.2:2376",
"CNB_PLATFORM_API=0.8"
],
"Labels": {
"author": "spring-boot"
},
"HostConfig": {
"Binds": [
"pack-app-aaaaaaaaaa:/workspace",
"pack-cache-b35197ac41ea.build:/cache",
"pack-cache-b35197ac41ea.launch:/launch-cache",
"pack-layers-aaaaaaaaaa:/layers"
],
"SecurityOpt" : [
"label=disable"
]
}
}

View File

@ -0,0 +1,36 @@
{
"User": "root",
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/exporter",
"-daemon",
"-app",
"/workspace",
"-cache-dir",
"/cache",
"-launch-cache",
"/launch-cache",
"-layers",
"/layers",
"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-app-aaaaaaaaaa:/workspace",
"pack-cache-b35197ac41ea.build:/cache",
"pack-cache-b35197ac41ea.launch:/launch-cache",
"pack-layers-aaaaaaaaaa:/layers"
],
"SecurityOpt" : [
"label=user:USER",
"label=role:ROLE"
]
}
}

View File

@ -0,0 +1,35 @@
{
"User": "root",
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/exporter",
"-daemon",
"-app",
"/workspace",
"-cache-dir",
"/cache",
"-launch-cache",
"/launch-cache",
"-layers",
"/layers",
"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-app-aaaaaaaaaa:/workspace",
"pack-cache-b35197ac41ea.build:/cache",
"pack-cache-b35197ac41ea.launch:/launch-cache",
"pack-layers-aaaaaaaaaa:/layers"
],
"SecurityOpt" : [
"label=disable"
]
}
}

View File

@ -0,0 +1,28 @@
{
"User": "root",
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/restorer",
"-daemon",
"-cache-dir",
"/cache",
"-layers",
"/layers"
],
"Env": [
"CNB_PLATFORM_API=0.8"
],
"Labels": {
"author": "spring-boot"
},
"HostConfig": {
"Binds": [
"/var/run/docker.sock:/var/run/docker.sock",
"/tmp/build-cache:/cache",
"/tmp/work-layers:/layers"
],
"SecurityOpt" : [
"label=disable"
]
}
}

View File

@ -0,0 +1,28 @@
{
"User": "root",
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/restorer",
"-daemon",
"-cache-dir",
"/cache",
"-layers",
"/layers"
],
"Env": [
"CNB_PLATFORM_API=0.8"
],
"Labels": {
"author": "spring-boot"
},
"HostConfig": {
"Binds": [
"/var/run/docker.sock:/var/run/docker.sock",
"build-volume:/cache",
"work-volume-layers:/layers"
],
"SecurityOpt" : [
"label=disable"
]
}
}

View File

@ -0,0 +1,28 @@
{
"User": "root",
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/restorer",
"-daemon",
"-cache-dir",
"/cache",
"-layers",
"/layers"
],
"Env": [
"CNB_PLATFORM_API=0.8"
],
"Labels": {
"author": "spring-boot"
},
"HostConfig": {
"Binds": [
"/var/alt.sock:/var/run/docker.sock",
"pack-cache-b35197ac41ea.build:/cache",
"pack-layers-aaaaaaaaaa:/layers"
],
"SecurityOpt" : [
"label=disable"
]
}
}

View File

@ -0,0 +1,28 @@
{
"User": "root",
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/restorer",
"-daemon",
"-cache-dir",
"/cache",
"-layers",
"/layers"
],
"Env": [
"DOCKER_HOST=tcp://192.168.1.2:2376",
"CNB_PLATFORM_API=0.8"
],
"Labels": {
"author": "spring-boot"
},
"HostConfig": {
"Binds": [
"pack-cache-b35197ac41ea.build:/cache",
"pack-layers-aaaaaaaaaa:/layers"
],
"SecurityOpt" : [
"label=disable"
]
}
}

View File

@ -0,0 +1,29 @@
{
"User": "root",
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/restorer",
"-daemon",
"-cache-dir",
"/cache",
"-layers",
"/layers"
],
"Env": [
"CNB_PLATFORM_API=0.8"
],
"Labels": {
"author": "spring-boot"
},
"HostConfig": {
"Binds": [
"/var/run/docker.sock:/var/run/docker.sock",
"pack-cache-b35197ac41ea.build:/cache",
"pack-layers-aaaaaaaaaa:/layers"
],
"SecurityOpt" : [
"label=user:USER",
"label=role:ROLE"
]
}
}

View File

@ -0,0 +1,28 @@
{
"User": "root",
"Image": "pack.local/ephemeral-builder",
"Cmd": [
"/cnb/lifecycle/restorer",
"-daemon",
"-cache-dir",
"/cache",
"-layers",
"/layers"
],
"Env": [
"CNB_PLATFORM_API=0.8"
],
"Labels": {
"author": "spring-boot"
},
"HostConfig": {
"Binds": [
"/var/run/docker.sock:/var/run/docker.sock",
"pack-cache-b35197ac41ea.build:/cache",
"pack-layers-aaaaaaaaaa:/layers"
],
"SecurityOpt" : [
"label=disable"
]
}
}

View File

@ -78,6 +78,23 @@ class BootBuildImageIntegrationTests {
String projectName = this.gradleBuild.getProjectDir().getName(); String projectName = this.gradleBuild.getProjectDir().getName();
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("docker.io/library/" + projectName); assertThat(result.getOutput()).contains("docker.io/library/" + projectName);
assertThat(result.getOutput()).contains("Running detector");
assertThat(result.getOutput()).contains("Running builder");
assertThat(result.getOutput()).contains("---> Test Info buildpack building");
assertThat(result.getOutput()).contains("Network status: HTTP/2 200");
assertThat(result.getOutput()).contains("---> Test Info buildpack done");
removeImages(projectName);
}
@TestTemplate
void buildsImageWithTrustBuilder() 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("Running creator");
assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("---> Test Info buildpack building");
assertThat(result.getOutput()).contains("Network status: HTTP/2 200"); 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");
@ -146,10 +163,11 @@ class BootBuildImageIntegrationTests {
writeLongNameResource(); writeLongNameResource();
BuildResult result = this.gradleBuild.build("bootBuildImage", "--pullPolicy=IF_NOT_PRESENT", BuildResult result = this.gradleBuild.build("bootBuildImage", "--pullPolicy=IF_NOT_PRESENT",
"--imageName=example/test-image-cmd", "--builder=ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1", "--imageName=example/test-image-cmd", "--builder=ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1",
"--runImage=paketobuildpacks/run-jammy-tiny", "--createdDate=2020-07-01T12:34:56Z", "--trustBuilder", "--runImage=paketobuildpacks/run-jammy-tiny", "--createdDate=2020-07-01T12:34:56Z",
"--applicationDirectory=/application"); "--applicationDirectory=/application");
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("example/test-image-cmd"); assertThat(result.getOutput()).contains("example/test-image-cmd");
assertThat(result.getOutput()).contains("Running creator");
assertThat(result.getOutput()).contains("---> Test Info buildpack building"); assertThat(result.getOutput()).contains("---> Test Info buildpack building");
assertThat(result.getOutput()).contains("---> Test Info buildpack done"); assertThat(result.getOutput()).contains("---> Test Info buildpack done");
Image image = new DockerApi().image().inspect(ImageReference.of("example/test-image-cmd")); Image image = new DockerApi().image().inspect(ImageReference.of("example/test-image-cmd"));

View File

@ -0,0 +1,14 @@
plugins {
id 'java'
id 'org.springframework.boot' version '{version}'
}
if (project.hasProperty('applyWarPlugin')) {
apply plugin: 'war'
}
bootBuildImage {
builder = "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1"
trustBuilder = true
pullPolicy = "IF_NOT_PRESENT"
}

View File

@ -118,9 +118,14 @@ The following table summarizes the available properties and their default values
| `builder` | `builder`
| `--builder` | `--builder`
| Name of the Builder image to use. | Name of the builder image to use.
| `paketobuildpacks/builder-jammy-tiny:latest` | `paketobuildpacks/builder-jammy-tiny:latest`
| `trustBuilder`
| `--trustBuilder`
| Whether to treat the builder as https://buildpacks.io/docs/for-platform-operators/how-to/integrate-ci/pack/concepts/trusted_builders/#what-is-a-trusted-builder[trusted].
| `true` if the builder is one of `paketobuildpacks/builder-jammy-tiny`, `paketobuildpacks/builder-jammy-base`, `paketobuildpacks/builder-jammy-full`, `paketobuildpacks/builder-jammy-buildpackless-tiny`, `paketobuildpacks/builder-jammy-buildpackless-base`, `paketobuildpacks/builder-jammy-buildpackless-full`, `gcr.io/buildpacks/builder`, `heroku/builder`; false otherwise.
| `runImage` | `runImage`
| `--runImage` | `--runImage`
| Name of the run image to use. | Name of the run image to use.

View File

@ -91,6 +91,7 @@ public abstract class BootBuildImage extends DefaultTask {
} }
return ImageReference.of(imageName, projectVersion.get()).toString(); return ImageReference.of(imageName, projectVersion.get()).toString();
})); }));
getTrustBuilder().convention((Boolean) null);
getCleanCache().convention(false); getCleanCache().convention(false);
getVerboseLogging().convention(false); getVerboseLogging().convention(false);
getPublish().convention(false); getPublish().convention(false);
@ -131,6 +132,16 @@ public abstract class BootBuildImage extends DefaultTask {
@Option(option = "builder", description = "The name of the builder image to use") @Option(option = "builder", description = "The name of the builder image to use")
public abstract Property<String> getBuilder(); public abstract Property<String> getBuilder();
/**
* Whether to treat the builder as trusted.
* @return whether to trust the builder
* @since 3.4.0
*/
@Input
@Optional
@Option(option = "trustBuilder", description = "Consider the builder trusted")
public abstract Property<Boolean> getTrustBuilder();
/** /**
* Returns the run image that will be included in the built image. When {@code null}, * Returns the run image that will be included in the built image. When {@code null},
* the run image bundled with the builder will be used. * the run image bundled with the builder will be used.
@ -348,13 +359,16 @@ public abstract class BootBuildImage extends DefaultTask {
private BuildRequest customize(BuildRequest request) { private BuildRequest customize(BuildRequest request) {
request = customizeBuilder(request); request = customizeBuilder(request);
if (getTrustBuilder().isPresent()) {
request = request.withTrustBuilder(getTrustBuilder().get());
}
request = customizeRunImage(request); request = customizeRunImage(request);
request = customizeEnvironment(request); request = customizeEnvironment(request);
request = customizeCreator(request); request = customizeCreator(request);
request = request.withCleanCache(getCleanCache().get()); request = request.withCleanCache(getCleanCache().get());
request = request.withVerboseLogging(getVerboseLogging().get()); request = request.withVerboseLogging(getVerboseLogging().get());
request = customizePullPolicy(request); request = customizePullPolicy(request);
request = customizePublish(request); request = request.withPublish(getPublish().get());
request = customizeBuildpacks(request); request = customizeBuildpacks(request);
request = customizeBindings(request); request = customizeBindings(request);
request = customizeTags(request); request = customizeTags(request);
@ -406,11 +420,6 @@ public abstract class BootBuildImage extends DefaultTask {
return request; return request;
} }
private BuildRequest customizePublish(BuildRequest request) {
request = request.withPublish(getPublish().get());
return request;
}
private BuildRequest customizeBuildpacks(BuildRequest request) { private BuildRequest customizeBuildpacks(BuildRequest request) {
List<String> buildpacks = getBuildpacks().getOrNull(); List<String> buildpacks = getBuildpacks().getOrNull();
if (!CollectionUtils.isEmpty(buildpacks)) { if (!CollectionUtils.isEmpty(buildpacks)) {

View File

@ -172,14 +172,24 @@ class BootBuildImageTests {
@Test @Test
void whenNoBuilderIsConfiguredThenRequestHasDefaultBuilder() { void whenNoBuilderIsConfiguredThenRequestHasDefaultBuilder() {
assertThat(this.buildImage.createRequest().getBuilder().getName()) BuildRequest request = this.buildImage.createRequest();
.isEqualTo("paketobuildpacks/builder-jammy-tiny"); assertThat(request.getBuilder().getName()).isEqualTo("paketobuildpacks/builder-jammy-tiny");
assertThat(request.isTrustBuilder()).isTrue();
} }
@Test @Test
void whenBuilderIsConfiguredThenRequestUsesSpecifiedBuilder() { void whenBuilderIsConfiguredThenRequestUsesSpecifiedBuilder() {
this.buildImage.getBuilder().set("example.com/test/builder:1.2"); this.buildImage.getBuilder().set("example.com/test/builder:1.2");
assertThat(this.buildImage.createRequest().getBuilder().getName()).isEqualTo("test/builder"); BuildRequest request = this.buildImage.createRequest();
assertThat(request.getBuilder().getName()).isEqualTo("test/builder");
assertThat(request.isTrustBuilder()).isFalse();
}
@Test
void whenTrustBuilderIsEnabledThenRequestHasTrustBuilderEnabled() {
this.buildImage.getBuilder().set("example.com/test/builder:1.2");
this.buildImage.getTrustBuilder().set(true);
assertThat(this.buildImage.createRequest().isTrustBuilder()).isTrue();
} }
@Test @Test

View File

@ -68,6 +68,8 @@ class BuildImageTests extends AbstractArchiveIntegrationTests {
assertThat(original).doesNotExist(); assertThat(original).doesNotExist();
assertThat(buildLog(project)).contains("Building image") assertThat(buildLog(project)).contains("Building image")
.contains("docker.io/library/build-image:0.0.1.BUILD-SNAPSHOT") .contains("docker.io/library/build-image:0.0.1.BUILD-SNAPSHOT")
.contains("Running detector")
.contains("Running builder")
.contains("---> Test Info buildpack building") .contains("---> Test Info buildpack building")
.contains("---> Test Info buildpack done") .contains("---> Test Info buildpack done")
.contains("Successfully built image"); .contains("Successfully built image");
@ -88,6 +90,8 @@ class BuildImageTests extends AbstractArchiveIntegrationTests {
assertThat(original).doesNotExist(); assertThat(original).doesNotExist();
assertThat(buildLog(project)).contains("Building image") assertThat(buildLog(project)).contains("Building image")
.contains("docker.io/library/build-image-cmd-line:0.0.1.BUILD-SNAPSHOT") .contains("docker.io/library/build-image-cmd-line:0.0.1.BUILD-SNAPSHOT")
.contains("Running detector")
.contains("Running builder")
.contains("---> Test Info buildpack building") .contains("---> Test Info buildpack building")
.contains("---> Test Info buildpack done") .contains("---> Test Info buildpack done")
.contains("Successfully built image"); .contains("Successfully built image");
@ -248,12 +252,14 @@ class BuildImageTests extends AbstractArchiveIntegrationTests {
.systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT") .systemProperty("spring-boot.build-image.pullPolicy", "IF_NOT_PRESENT")
.systemProperty("spring-boot.build-image.imageName", "example.com/test/cmd-property-name:v1") .systemProperty("spring-boot.build-image.imageName", "example.com/test/cmd-property-name:v1")
.systemProperty("spring-boot.build-image.builder", "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1") .systemProperty("spring-boot.build-image.builder", "ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1")
.systemProperty("spring-boot.build-image.trustBuilder", "true")
.systemProperty("spring-boot.build-image.runImage", "paketobuildpacks/run-jammy-tiny") .systemProperty("spring-boot.build-image.runImage", "paketobuildpacks/run-jammy-tiny")
.systemProperty("spring-boot.build-image.createdDate", "2020-07-01T12:34:56Z") .systemProperty("spring-boot.build-image.createdDate", "2020-07-01T12:34:56Z")
.systemProperty("spring-boot.build-image.applicationDirectory", "/application") .systemProperty("spring-boot.build-image.applicationDirectory", "/application")
.execute((project) -> { .execute((project) -> {
assertThat(buildLog(project)).contains("Building image") assertThat(buildLog(project)).contains("Building image")
.contains("example.com/test/cmd-property-name:v1") .contains("example.com/test/cmd-property-name:v1")
.contains("Running creator")
.contains("---> Test Info buildpack building") .contains("---> Test Info buildpack building")
.contains("---> Test Info buildpack done") .contains("---> Test Info buildpack done")
.contains("Successfully built image"); .contains("Successfully built image");
@ -279,6 +285,22 @@ class BuildImageTests extends AbstractArchiveIntegrationTests {
}); });
} }
@TestTemplate
void whenBuildImageIsInvokedWithTrustBuilder(MavenBuild mavenBuild) {
mavenBuild.project("dockerTest", "build-image-trust-builder")
.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-v2-trust-builder:0.0.1.BUILD-SNAPSHOT")
.contains("Running creator")
.contains("---> Test Info buildpack building")
.contains("---> Test Info buildpack done")
.contains("Successfully built image");
removeImage("docker.io/library/build-image-v2-trust-builder", "0.0.1.BUILD-SNAPSHOT");
});
}
@TestTemplate @TestTemplate
void whenBuildImageIsInvokedWithEmptyEnvEntry(MavenBuild mavenBuild) { void whenBuildImageIsInvokedWithEmptyEnvEntry(MavenBuild mavenBuild) {
mavenBuild.project("dockerTest", "build-image-empty-env-entry") mavenBuild.project("dockerTest", "build-image-empty-env-entry")

View File

@ -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-v2-trust-builder</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>ghcr.io/spring-io/spring-boot-cnb-test-builder:0.0.1</builder>
<trustBuilder>true</trustBuilder>
</image>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

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

View File

@ -134,9 +134,14 @@ The following table summarizes the available parameters and their default values
| `builder` + | `builder` +
(`spring-boot.build-image.builder`) (`spring-boot.build-image.builder`)
| Name of the Builder image to use. | Name of the builder image to use.
| `paketobuildpacks/builder-jammy-tiny:latest` | `paketobuildpacks/builder-jammy-tiny:latest`
| `trustBuilder` +
(`spring-boot.build-image.trustBuilder`)
| Whether to treat the builder as https://buildpacks.io/docs/for-platform-operators/how-to/integrate-ci/pack/concepts/trusted_builders/#what-is-a-trusted-builder[trusted].
| `true` if the builder is one of `paketobuildpacks/builder-jammy-tiny`, `paketobuildpacks/builder-jammy-base`, `paketobuildpacks/builder-jammy-full`, `paketobuildpacks/builder-jammy-buildpackless-tiny`, `paketobuildpacks/builder-jammy-buildpackless-base`, `paketobuildpacks/builder-jammy-buildpackless-full`, `gcr.io/buildpacks/builder`, `heroku/builder`; false otherwise.
| `runImage` + | `runImage` +
(`spring-boot.build-image.runImage`) (`spring-boot.build-image.runImage`)
| Name of the run image to use. | Name of the run image to use.

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2023 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -118,6 +118,13 @@ public abstract class BuildImageMojo extends AbstractPackagerMojo {
@Parameter(property = "spring-boot.build-image.builder", readonly = true) @Parameter(property = "spring-boot.build-image.builder", readonly = true)
String imageBuilder; String imageBuilder;
/**
* Alias for {@link Image#trustBuilder} to support configuration through command-line
* property.
*/
@Parameter(property = "spring-boot.build-image.trustBuilder", readonly = true)
Boolean trustBuilder;
/** /**
* Alias for {@link Image#runImage} to support configuration through command-line * Alias for {@link Image#runImage} to support configuration through command-line
* property. * property.
@ -267,6 +274,9 @@ public abstract class BuildImageMojo extends AbstractPackagerMojo {
if (image.builder == null && this.imageBuilder != null) { if (image.builder == null && this.imageBuilder != null) {
image.setBuilder(this.imageBuilder); image.setBuilder(this.imageBuilder);
} }
if (image.trustBuilder == null && this.trustBuilder != null) {
image.setTrustBuilder(this.trustBuilder);
}
if (image.runImage == null && this.runImage != null) { if (image.runImage == null && this.runImage != null) {
image.setRunImage(this.runImage); image.setRunImage(this.runImage);
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2023 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -49,6 +49,8 @@ public class Image {
String builder; String builder;
Boolean trustBuilder;
String runImage; String runImage;
Map<String, String> env; Map<String, String> env;
@ -105,6 +107,18 @@ public class Image {
this.builder = builder; this.builder = builder;
} }
/**
* If the builder should be treated as trusted.
* @return {@code true} if the builder should be treated as trusted
*/
public Boolean getTrustBuilder() {
return this.trustBuilder;
}
void setTrustBuilder(Boolean trustBuilder) {
this.trustBuilder = trustBuilder;
}
/** /**
* The name of the run image to use to create the image. * The name of the run image to use to create the image.
* @return the builder image name * @return the builder image name
@ -221,6 +235,9 @@ public class Image {
if (StringUtils.hasText(this.builder)) { if (StringUtils.hasText(this.builder)) {
request = request.withBuilder(ImageReference.of(this.builder)); request = request.withBuilder(ImageReference.of(this.builder));
} }
if (this.trustBuilder != null) {
request = request.withTrustBuilder(this.trustBuilder);
}
if (StringUtils.hasText(this.runImage)) { if (StringUtils.hasText(this.runImage)) {
request = request.withRunImage(ImageReference.of(this.runImage)); request = request.withRunImage(ImageReference.of(this.runImage));
} }

View File

@ -70,11 +70,13 @@ class ImageTests {
BuildRequest request = new Image().getBuildRequest(createArtifact(), mockApplicationContent()); BuildRequest request = new Image().getBuildRequest(createArtifact(), mockApplicationContent());
assertThat(request.getName()).hasToString("docker.io/library/my-app:0.0.1-SNAPSHOT"); assertThat(request.getName()).hasToString("docker.io/library/my-app:0.0.1-SNAPSHOT");
assertThat(request.getBuilder().toString()).contains("paketobuildpacks/builder-jammy-tiny"); assertThat(request.getBuilder().toString()).contains("paketobuildpacks/builder-jammy-tiny");
assertThat(request.isTrustBuilder()).isTrue();
assertThat(request.getRunImage()).isNull(); assertThat(request.getRunImage()).isNull();
assertThat(request.getEnv()).isEmpty(); assertThat(request.getEnv()).isEmpty();
assertThat(request.isCleanCache()).isFalse(); assertThat(request.isCleanCache()).isFalse();
assertThat(request.isVerboseLogging()).isFalse(); assertThat(request.isVerboseLogging()).isFalse();
assertThat(request.getPullPolicy()).isEqualTo(PullPolicy.ALWAYS); assertThat(request.getPullPolicy()).isEqualTo(PullPolicy.ALWAYS);
assertThat(request.isPublish()).isFalse();
assertThat(request.getBuildpacks()).isEmpty(); assertThat(request.getBuildpacks()).isEmpty();
assertThat(request.getBindings()).isEmpty(); assertThat(request.getBindings()).isEmpty();
assertThat(request.getNetwork()).isNull(); assertThat(request.getNetwork()).isNull();
@ -86,6 +88,26 @@ class ImageTests {
image.builder = "springboot/builder:2.2.x"; image.builder = "springboot/builder:2.2.x";
BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent()); BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent());
assertThat(request.getBuilder()).hasToString("docker.io/springboot/builder:2.2.x"); assertThat(request.getBuilder()).hasToString("docker.io/springboot/builder:2.2.x");
assertThat(request.isTrustBuilder()).isFalse();
}
@Test
void getBuildRequestWhenHasBuilderAndTrustBuilderUsesBuilderAndTrustBuilder() {
Image image = new Image();
image.builder = "springboot/builder:2.2.x";
image.trustBuilder = true;
BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent());
assertThat(request.getBuilder()).hasToString("docker.io/springboot/builder:2.2.x");
assertThat(request.isTrustBuilder()).isTrue();
}
@Test
void getBuildRequestWhenHasDefaultBuilderAndTrustBuilderUsesTrustBuilder() {
Image image = new Image();
image.trustBuilder = false;
BuildRequest request = image.getBuildRequest(createArtifact(), mockApplicationContent());
assertThat(request.getBuilder().toString()).contains("paketobuildpacks/builder-jammy-tiny");
assertThat(request.isTrustBuilder()).isFalse();
} }
@Test @Test

View File

@ -0,0 +1,44 @@
/*
* Copyright 2012-2024 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.springframework.boot.testsupport.junit;
import java.lang.reflect.Method;
import java.util.stream.Stream;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import org.junit.platform.commons.util.Preconditions;
/**
* An {@link ArgumentsProvider} that provides {@code true} and {@code false} values.
*
* @author Scott Frederick
*/
class BooleanArgumentsProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
Method testMethod = context.getRequiredTestMethod();
Preconditions.condition(testMethod.getParameterCount() > 0, () -> String.format(
"@BooleanValueSource cannot provide arguments to method [%s]: the method does not declare any formal parameters.",
testMethod.toGenericString()));
return Stream.of(Arguments.arguments(false), Arguments.arguments(true));
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2012-2024 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.springframework.boot.testsupport.junit;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.params.provider.ArgumentsSource;
/**
* {@code @BooleanValueSource} is an {@link ArgumentsSource} which provides {@code true}
* and {@code false} values to the annotated {@code @ParameterizedTest} method.
*
* @author Scott Frederick
*/
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ArgumentsSource(BooleanArgumentsProvider.class)
public @interface BooleanValueSource {
}

View File

@ -48,7 +48,6 @@ import org.springframework.boot.image.junit.GradleBuildInjectionExtension;
import org.springframework.boot.testsupport.gradle.testkit.GradleBuild; import org.springframework.boot.testsupport.gradle.testkit.GradleBuild;
import org.springframework.boot.testsupport.gradle.testkit.GradleBuildExtension; import org.springframework.boot.testsupport.gradle.testkit.GradleBuildExtension;
import org.springframework.boot.testsupport.gradle.testkit.GradleVersions; import org.springframework.boot.testsupport.gradle.testkit.GradleVersions;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry; import static org.assertj.core.api.Assertions.entry;
@ -85,6 +84,7 @@ class PaketoBuilderTests {
ImageReference imageReference = ImageReference.of(ImageName.of(imageName)); ImageReference imageReference = ImageReference.of(ImageName.of(imageName));
BuildResult result = buildImage(imageName); BuildResult result = buildImage(imageName);
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("Running creator");
try (GenericContainer<?> container = new GenericContainer<>(imageName)) { try (GenericContainer<?> container = new GenericContainer<>(imageName)) {
container.withExposedPorts(8080); container.withExposedPorts(8080);
container.waitingFor(Wait.forHttp("/test")).start(); container.waitingFor(Wait.forHttp("/test")).start();
@ -116,6 +116,7 @@ class PaketoBuilderTests {
ImageReference imageReference = ImageReference.of(ImageName.of(imageName)); ImageReference imageReference = ImageReference.of(ImageName.of(imageName));
BuildResult result = buildImage(imageName); BuildResult result = buildImage(imageName);
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("Running creator");
try (GenericContainer<?> container = new GenericContainer<>(imageName)) { try (GenericContainer<?> container = new GenericContainer<>(imageName)) {
container.withCommand("--server.port=9090"); container.withCommand("--server.port=9090");
container.withExposedPorts(9090); container.withExposedPorts(9090);
@ -133,6 +134,7 @@ class PaketoBuilderTests {
ImageReference imageReference = ImageReference.of(ImageName.of(imageName)); ImageReference imageReference = ImageReference.of(ImageName.of(imageName));
BuildResult result = buildImage(imageName); BuildResult result = buildImage(imageName);
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("Running creator");
try (GenericContainer<?> container = new GenericContainer<>(imageName)) { try (GenericContainer<?> container = new GenericContainer<>(imageName)) {
container.withExposedPorts(8080); container.withExposedPorts(8080);
container.waitingFor(Wait.forHttp("/test")).start(); container.waitingFor(Wait.forHttp("/test")).start();
@ -159,6 +161,7 @@ class PaketoBuilderTests {
ImageReference imageReference = ImageReference.of(ImageName.of(imageName)); ImageReference imageReference = ImageReference.of(ImageName.of(imageName));
BuildResult result = buildImage(imageName, "assemble", "bootDistZip"); BuildResult result = buildImage(imageName, "assemble", "bootDistZip");
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("Running creator");
try (GenericContainer<?> container = new GenericContainer<>(imageName)) { try (GenericContainer<?> container = new GenericContainer<>(imageName)) {
container.withExposedPorts(8080); container.withExposedPorts(8080);
container.waitingFor(Wait.forHttp("/test")).start(); container.waitingFor(Wait.forHttp("/test")).start();
@ -195,6 +198,7 @@ class PaketoBuilderTests {
ImageReference imageReference = ImageReference.of(ImageName.of(imageName)); ImageReference imageReference = ImageReference.of(ImageName.of(imageName));
BuildResult result = buildImage(imageName, "assemble", "bootDistZip"); BuildResult result = buildImage(imageName, "assemble", "bootDistZip");
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("Running creator");
try (GenericContainer<?> container = new GenericContainer<>(imageName)) { try (GenericContainer<?> container = new GenericContainer<>(imageName)) {
container.withExposedPorts(8080); container.withExposedPorts(8080);
container.waitingFor(Wait.forHttp("/test")).start(); container.waitingFor(Wait.forHttp("/test")).start();
@ -232,6 +236,7 @@ class PaketoBuilderTests {
ImageReference imageReference = ImageReference.of(ImageName.of(imageName)); ImageReference imageReference = ImageReference.of(ImageName.of(imageName));
BuildResult result = buildImage(imageName); BuildResult result = buildImage(imageName);
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("Running creator");
try (GenericContainer<?> container = new GenericContainer<>(imageName)) { try (GenericContainer<?> container = new GenericContainer<>(imageName)) {
container.withExposedPorts(8080); container.withExposedPorts(8080);
container.waitingFor(Wait.forHttp("/test")).start(); container.waitingFor(Wait.forHttp("/test")).start();
@ -264,6 +269,7 @@ class PaketoBuilderTests {
ImageReference imageReference = ImageReference.of(ImageName.of(imageName)); ImageReference imageReference = ImageReference.of(ImageName.of(imageName));
BuildResult result = buildImage(imageName); BuildResult result = buildImage(imageName);
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("Running creator");
try (GenericContainer<?> container = new GenericContainer<>(imageName)) { try (GenericContainer<?> container = new GenericContainer<>(imageName)) {
container.withExposedPorts(8080); container.withExposedPorts(8080);
container.waitingFor(Wait.forHttp("/test")).start(); container.waitingFor(Wait.forHttp("/test")).start();
@ -310,6 +316,7 @@ class PaketoBuilderTests {
ImageReference imageReference = ImageReference.of(ImageName.of(imageName)); ImageReference imageReference = ImageReference.of(ImageName.of(imageName));
BuildResult result = buildImage(imageName); BuildResult result = buildImage(imageName);
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
assertThat(result.getOutput()).contains("Running creator");
try (GenericContainer<?> container = new GenericContainer<>(imageName)) { try (GenericContainer<?> container = new GenericContainer<>(imageName)) {
container.withExposedPorts(8080); container.withExposedPorts(8080);
container.waitingFor(Wait.forHttp("/test")).start(); container.waitingFor(Wait.forHttp("/test")).start();
@ -333,9 +340,11 @@ class PaketoBuilderTests {
} }
private BuildResult buildImage(String imageName, String... arguments) { private BuildResult buildImage(String imageName, String... arguments) {
String[] buildImageArgs = { "bootBuildImage", "--imageName=" + imageName, "--pullPolicy=IF_NOT_PRESENT" }; List<String> args = new ArrayList<>(List.of(arguments));
String[] args = StringUtils.concatenateStringArrays(arguments, buildImageArgs); args.add("bootBuildImage");
return this.gradleBuild.build(args); args.add("--imageName=" + imageName);
args.add("--pullPolicy=IF_NOT_PRESENT");
return this.gradleBuild.build(args.toArray(new String[0]));
} }
private void writeMainClass() throws IOException { private void writeMainClass() throws IOException {