Use CNB creator all-in-one lifecycle
This commit modifies the buildpack platform invocation logic used by the build plugins to invoke the single creator lifecycle introduced in the CNB API 0.3, instead of invoking discrete lifecycle phases separately. It also removes support for CNB API 0.2. Fixes gh-21273
This commit is contained in:
@@ -32,7 +32,7 @@ final class ApiVersions {
|
||||
/**
|
||||
* The platform API versions supported by this release.
|
||||
*/
|
||||
static final ApiVersions SUPPORTED_PLATFORMS = new ApiVersions(ApiVersion.of(0, 2), ApiVersion.of(0, 3));
|
||||
static final ApiVersions SUPPORTED_PLATFORMS = new ApiVersions(ApiVersion.of(0, 3));
|
||||
|
||||
private final ApiVersion[] apiVersions;
|
||||
|
||||
|
||||
@@ -115,87 +115,36 @@ class Lifecycle implements Closeable {
|
||||
if (this.request.isCleanCache()) {
|
||||
deleteVolume(this.buildCacheVolume);
|
||||
}
|
||||
run(detectPhase());
|
||||
run(analyzePhase());
|
||||
if (this.request.isCleanCache()) {
|
||||
this.log.skippingPhase("restorer", "due to cleaning cache");
|
||||
}
|
||||
else {
|
||||
run(restorePhase());
|
||||
}
|
||||
run(buildPhase());
|
||||
run(exportPhase());
|
||||
run(createPhase());
|
||||
this.log.executedLifecycle(this.request);
|
||||
}
|
||||
|
||||
private Phase detectPhase() {
|
||||
Phase phase = createPhase("detector");
|
||||
private Phase createPhase() {
|
||||
Phase phase = new Phase("creator", isVerboseLogging());
|
||||
phase.withDaemonAccess();
|
||||
phase.withLogLevelArg();
|
||||
phase.withArgs("-app", Directory.APPLICATION);
|
||||
phase.withArgs("-platform", Directory.PLATFORM);
|
||||
phase.withLogLevelArg();
|
||||
return phase;
|
||||
}
|
||||
|
||||
private Phase restorePhase() {
|
||||
Phase phase = createPhase("restorer");
|
||||
phase.withDaemonAccess();
|
||||
phase.withArgs("-cache-dir", Directory.CACHE);
|
||||
phase.withArgs("-run-image", this.runImageReference);
|
||||
phase.withArgs("-layers", Directory.LAYERS);
|
||||
phase.withLogLevelArg();
|
||||
phase.withBinds(this.buildCacheVolume, Directory.CACHE);
|
||||
return phase;
|
||||
}
|
||||
|
||||
private Phase analyzePhase() {
|
||||
Phase phase = createPhase("analyzer");
|
||||
phase.withDaemonAccess();
|
||||
phase.withLogLevelArg();
|
||||
phase.withArgs("-cache-dir", Directory.CACHE);
|
||||
phase.withArgs("-launch-cache", Directory.LAUNCH_CACHE);
|
||||
phase.withArgs("-daemon");
|
||||
if (this.request.isCleanCache()) {
|
||||
phase.withArgs("-skip-layers");
|
||||
phase.withArgs("-skip-restore");
|
||||
}
|
||||
else {
|
||||
phase.withArgs("-cache-dir", Directory.CACHE);
|
||||
}
|
||||
phase.withArgs("-layers", Directory.LAYERS);
|
||||
phase.withArgs(this.request.getName());
|
||||
phase.withBinds(this.buildCacheVolume, Directory.CACHE);
|
||||
return phase;
|
||||
}
|
||||
|
||||
private Phase buildPhase() {
|
||||
Phase phase = createPhase("builder");
|
||||
phase.withArgs("-layers", Directory.LAYERS);
|
||||
phase.withArgs("-app", Directory.APPLICATION);
|
||||
phase.withArgs("-platform", Directory.PLATFORM);
|
||||
return phase;
|
||||
}
|
||||
|
||||
private Phase exportPhase() {
|
||||
Phase phase = createPhase("exporter");
|
||||
phase.withDaemonAccess();
|
||||
phase.withLogLevelArg();
|
||||
phase.withArgs("-image", this.runImageReference);
|
||||
phase.withArgs("-layers", Directory.LAYERS);
|
||||
phase.withArgs("-app", Directory.APPLICATION);
|
||||
phase.withArgs("-daemon");
|
||||
phase.withArgs("-launch-cache", Directory.LAUNCH_CACHE);
|
||||
phase.withArgs("-cache-dir", Directory.CACHE);
|
||||
phase.withArgs(this.request.getName());
|
||||
phase.withBinds(this.launchCacheVolume, Directory.LAUNCH_CACHE);
|
||||
phase.withBinds(this.buildCacheVolume, Directory.CACHE);
|
||||
return phase;
|
||||
}
|
||||
|
||||
private Phase createPhase(String name) {
|
||||
boolean verboseLogging = this.request.isVerboseLogging()
|
||||
&& this.lifecycleVersion.isEqualOrGreaterThan(LOGGING_MINIMUM_VERSION);
|
||||
Phase phase = new Phase(name, verboseLogging);
|
||||
phase.withBinds(this.layersVolume, Directory.LAYERS);
|
||||
phase.withBinds(this.applicationVolume, Directory.APPLICATION);
|
||||
phase.withBinds(this.buildCacheVolume, Directory.CACHE);
|
||||
phase.withBinds(this.launchCacheVolume, Directory.LAUNCH_CACHE);
|
||||
return phase;
|
||||
}
|
||||
|
||||
private boolean isVerboseLogging() {
|
||||
return this.request.isVerboseLogging() && this.lifecycleVersion.isEqualOrGreaterThan(LOGGING_MINIMUM_VERSION);
|
||||
}
|
||||
|
||||
private void run(Phase phase) throws IOException {
|
||||
Consumer<LogUpdateEvent> logConsumer = this.log.runningPhase(this.request, phase.getName());
|
||||
ContainerConfig containerConfig = ContainerConfig.of(this.builder.getName(), phase::apply);
|
||||
|
||||
@@ -79,11 +79,7 @@ class BuilderTests {
|
||||
Builder builder = new Builder(BuildLog.to(out), docker);
|
||||
BuildRequest request = getTestRequest();
|
||||
builder.build(request);
|
||||
assertThat(out.toString()).contains("Running detector");
|
||||
assertThat(out.toString()).contains("Running restorer");
|
||||
assertThat(out.toString()).contains("Running analyzer");
|
||||
assertThat(out.toString()).contains("Running builder");
|
||||
assertThat(out.toString()).contains("Running exporter");
|
||||
assertThat(out.toString()).contains("Running creator");
|
||||
assertThat(out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
|
||||
ArgumentCaptor<ImageArchive> archive = ArgumentCaptor.forClass(ImageArchive.class);
|
||||
verify(docker.image()).load(archive.capture(), any());
|
||||
@@ -119,7 +115,7 @@ class BuilderTests {
|
||||
Builder builder = new Builder(BuildLog.to(out), docker);
|
||||
BuildRequest request = getTestRequest();
|
||||
assertThatExceptionOfType(BuilderException.class).isThrownBy(() -> builder.build(request))
|
||||
.withMessage("Builder lifecycle 'detector' failed with status code 9");
|
||||
.withMessage("Builder lifecycle 'creator' failed with status code 9");
|
||||
}
|
||||
|
||||
private DockerApi mockDockerApi() throws IOException {
|
||||
|
||||
@@ -68,9 +68,9 @@ class LifecycleTests {
|
||||
|
||||
private DockerApi docker;
|
||||
|
||||
private Map<String, ContainerConfig> configs = new LinkedHashMap<>();
|
||||
private final Map<String, ContainerConfig> configs = new LinkedHashMap<>();
|
||||
|
||||
private Map<String, ContainerContent> content = new LinkedHashMap<>();
|
||||
private final Map<String, ContainerContent> content = new LinkedHashMap<>();
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
@@ -84,11 +84,7 @@ class LifecycleTests {
|
||||
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
|
||||
createLifecycle().execute();
|
||||
assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json"));
|
||||
assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer.json"));
|
||||
assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer.json"));
|
||||
assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json"));
|
||||
assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter.json"));
|
||||
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator.json"));
|
||||
assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
|
||||
}
|
||||
|
||||
@@ -118,7 +114,7 @@ class LifecycleTests {
|
||||
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(9, null));
|
||||
assertThatExceptionOfType(BuilderException.class).isThrownBy(() -> createLifecycle().execute())
|
||||
.withMessage("Builder lifecycle 'detector' failed with status code 9");
|
||||
.withMessage("Builder lifecycle 'creator' failed with status code 9");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -128,11 +124,7 @@ class LifecycleTests {
|
||||
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
|
||||
BuildRequest request = getTestRequest().withCleanCache(true);
|
||||
createLifecycle(request).execute();
|
||||
assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json"));
|
||||
assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer-clean-cache.json"));
|
||||
assertPhaseWasNotRun("restorer");
|
||||
assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json"));
|
||||
assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter.json"));
|
||||
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator-clean-cache.json"));
|
||||
VolumeName name = VolumeName.of("pack-cache-b35197ac41ea.build");
|
||||
verify(this.docker.volume()).delete(name, true);
|
||||
}
|
||||
@@ -206,11 +198,6 @@ class LifecycleTests {
|
||||
configConsumer.accept(this.configs.get(containerReference.toString()));
|
||||
}
|
||||
|
||||
private void assertPhaseWasNotRun(String name) {
|
||||
ContainerReference containerReference = ContainerReference.of("lifecycle-" + name);
|
||||
assertThat(this.configs.get(containerReference.toString())).isNull();
|
||||
}
|
||||
|
||||
private IOConsumer<ContainerConfig> withExpectedConfig(String name) {
|
||||
return (config) -> {
|
||||
InputStream in = getClass().getResourceAsStream(name);
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"User" : "root",
|
||||
"Image" : "pack.local/ephemeral-builder",
|
||||
"Cmd" : [ "/lifecycle/analyzer", "-daemon", "-skip-layers", "-layers", "/layers", "docker.io/library/my-application:latest" ],
|
||||
"Labels" : {
|
||||
"author" : "spring-boot"
|
||||
},
|
||||
"HostConfig" : {
|
||||
"Binds" : [ "/var/run/docker.sock:/var/run/docker.sock", "pack-layers-aaaaaaaaaa:/layers", "pack-app-aaaaaaaaaa:/workspace", "pack-cache-b35197ac41ea.build:/cache" ]
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"User" : "root",
|
||||
"Image" : "pack.local/ephemeral-builder",
|
||||
"Cmd" : [ "/lifecycle/analyzer", "-daemon", "-cache-dir", "/cache", "-layers", "/layers", "docker.io/library/my-application:latest" ],
|
||||
"Labels" : {
|
||||
"author" : "spring-boot"
|
||||
},
|
||||
"HostConfig" : {
|
||||
"Binds" : [ "/var/run/docker.sock:/var/run/docker.sock", "pack-layers-aaaaaaaaaa:/layers", "pack-app-aaaaaaaaaa:/workspace", "pack-cache-b35197ac41ea.build:/cache" ]
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"Image" : "pack.local/ephemeral-builder",
|
||||
"Cmd" : [ "/lifecycle/builder", "-layers", "/layers", "-app", "/workspace", "-platform", "/platform" ],
|
||||
"Labels" : {
|
||||
"author" : "spring-boot"
|
||||
},
|
||||
"HostConfig" : {
|
||||
"Binds" : [ "pack-layers-aaaaaaaaaa:/layers", "pack-app-aaaaaaaaaa:/workspace" ]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"User" : "root",
|
||||
"Image" : "pack.local/ephemeral-builder",
|
||||
"Cmd" : [ "/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "-skip-restore", "docker.io/library/my-application:latest" ],
|
||||
"Labels" : {
|
||||
"author" : "spring-boot"
|
||||
},
|
||||
"HostConfig" : {
|
||||
"Binds" : [ "/var/run/docker.sock:/var/run/docker.sock", "pack-layers-aaaaaaaaaa:/layers", "pack-app-aaaaaaaaaa:/workspace", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache" ]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"User" : "root",
|
||||
"Image" : "pack.local/ephemeral-builder",
|
||||
"Cmd" : [ "/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "docker.io/library/my-application:latest" ],
|
||||
"Labels" : {
|
||||
"author" : "spring-boot"
|
||||
},
|
||||
"HostConfig" : {
|
||||
"Binds" : [ "/var/run/docker.sock:/var/run/docker.sock", "pack-layers-aaaaaaaaaa:/layers", "pack-app-aaaaaaaaaa:/workspace", "pack-cache-b35197ac41ea.build:/cache", "pack-cache-b35197ac41ea.launch:/launch-cache" ]
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"Image" : "pack.local/ephemeral-builder",
|
||||
"Cmd" : [ "/lifecycle/detector", "-app", "/workspace", "-platform", "/platform" ],
|
||||
"Labels" : {
|
||||
"author" : "spring-boot"
|
||||
},
|
||||
"HostConfig" : {
|
||||
"Binds" : [ "pack-layers-aaaaaaaaaa:/layers", "pack-app-aaaaaaaaaa:/workspace" ]
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"User" : "root",
|
||||
"Image" : "pack.local/ephemeral-builder",
|
||||
"Cmd" : [ "/lifecycle/exporter", "-image", "docker.io/cloudfoundry/run", "-layers", "/layers", "-app", "/workspace", "-daemon", "-launch-cache", "/launch-cache", "-cache-dir", "/cache", "docker.io/library/my-application:latest" ],
|
||||
"Labels" : {
|
||||
"author" : "spring-boot"
|
||||
},
|
||||
"HostConfig" : {
|
||||
"Binds" : [ "/var/run/docker.sock:/var/run/docker.sock", "pack-layers-aaaaaaaaaa:/layers", "pack-app-aaaaaaaaaa:/workspace", "pack-cache-b35197ac41ea.launch:/launch-cache", "pack-cache-b35197ac41ea.build:/cache" ]
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"User" : "root",
|
||||
"Image" : "pack.local/ephemeral-builder",
|
||||
"Cmd" : [ "/lifecycle/restorer", "-cache-dir", "/cache", "-layers", "/layers" ],
|
||||
"Labels" : {
|
||||
"author" : "spring-boot"
|
||||
},
|
||||
"HostConfig" : {
|
||||
"Binds" : [ "/var/run/docker.sock:/var/run/docker.sock", "pack-layers-aaaaaaaaaa:/layers", "pack-app-aaaaaaaaaa:/workspace", "pack-cache-b35197ac41ea.build:/cache" ]
|
||||
}
|
||||
}
|
||||
@@ -89,14 +89,14 @@ class BootBuildImageIntegrationTests {
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void buildsImageWithV2Builder() throws IOException {
|
||||
void buildsImageWithCustomBuilder() throws IOException {
|
||||
writeMainClass();
|
||||
writeLongNameResource();
|
||||
BuildResult result = this.gradleBuild.build("bootBuildImage");
|
||||
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertThat(result.getOutput()).contains("example/test-image-v2");
|
||||
assertThat(result.getOutput()).contains("paketo-buildpacks/builder:base-platform-api-0.2");
|
||||
ImageReference imageReference = ImageReference.of(ImageName.of("example/test-image-v2"));
|
||||
assertThat(result.getOutput()).contains("example/test-image-custom");
|
||||
assertThat(result.getOutput()).contains("paketo-buildpacks/builder:full-cf-platform-api-0.3");
|
||||
ImageReference imageReference = ImageReference.of(ImageName.of("example/test-image-custom"));
|
||||
try (GenericContainer<?> container = new GenericContainer<>(imageReference.toString())) {
|
||||
container.waitingFor(Wait.forLogMessage("Launched\\n", 1)).start();
|
||||
}
|
||||
@@ -109,12 +109,12 @@ class BootBuildImageIntegrationTests {
|
||||
void buildsImageWithCommandLineOptions() throws IOException {
|
||||
writeMainClass();
|
||||
writeLongNameResource();
|
||||
BuildResult result = this.gradleBuild.build("bootBuildImage", "--imageName=example/test-image-v2",
|
||||
"--builder=gcr.io/paketo-buildpacks/builder:base-platform-api-0.2");
|
||||
BuildResult result = this.gradleBuild.build("bootBuildImage", "--imageName=example/test-image-cmd",
|
||||
"--builder=gcr.io/paketo-buildpacks/builder:full-cf-platform-api-0.3");
|
||||
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertThat(result.getOutput()).contains("example/test-image-v2");
|
||||
assertThat(result.getOutput()).contains("paketo-buildpacks/builder:base-platform-api-0.2");
|
||||
ImageReference imageReference = ImageReference.of(ImageName.of("example/test-image-v2"));
|
||||
assertThat(result.getOutput()).contains("example/test-image-cmd");
|
||||
assertThat(result.getOutput()).contains("paketo-buildpacks/builder:full-cf-platform-api-0.3");
|
||||
ImageReference imageReference = ImageReference.of(ImageName.of("example/test-image-cmd"));
|
||||
try (GenericContainer<?> container = new GenericContainer<>(imageReference.toString())) {
|
||||
container.waitingFor(Wait.forLogMessage("Launched\\n", 1)).start();
|
||||
}
|
||||
@@ -129,7 +129,7 @@ class BootBuildImageIntegrationTests {
|
||||
writeLongNameResource();
|
||||
BuildResult result = this.gradleBuild.buildAndFail("bootBuildImage");
|
||||
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.FAILED);
|
||||
assertThat(result.getOutput()).contains("Builder lifecycle 'builder' failed with status code");
|
||||
assertThat(result.getOutput()).containsPattern("Builder lifecycle '.*' failed with status code");
|
||||
}
|
||||
|
||||
private void writeMainClass() {
|
||||
|
||||
@@ -7,6 +7,6 @@ sourceCompatibility = '1.8'
|
||||
targetCompatibility = '1.8'
|
||||
|
||||
bootBuildImage {
|
||||
imageName = "example/test-image-v2"
|
||||
builder = "gcr.io/paketo-buildpacks/builder:base-platform-api-0.2"
|
||||
imageName = "example/test-image-custom"
|
||||
builder = "gcr.io/paketo-buildpacks/builder:full-cf-platform-api-0.3"
|
||||
}
|
||||
@@ -94,11 +94,11 @@ public class BuildImageTests extends AbstractArchiveIntegrationTests {
|
||||
mavenBuild.project("build-image").goals("package")
|
||||
.systemProperty("spring-boot.build-image.imageName", "example.com/test/cmd-property-name:v1")
|
||||
.systemProperty("spring-boot.build-image.builder",
|
||||
"gcr.io/paketo-buildpacks/builder:base-platform-api-0.2")
|
||||
"gcr.io/paketo-buildpacks/builder:full-cf-platform-api-0.3")
|
||||
.execute((project) -> {
|
||||
assertThat(buildLog(project)).contains("Building image")
|
||||
.contains("example.com/test/cmd-property-name:v1")
|
||||
.contains("paketo-buildpacks/builder:base-platform-api-0.2")
|
||||
.contains("paketo-buildpacks/builder:full-cf-platform-api-0.3")
|
||||
.contains("Successfully built image");
|
||||
ImageReference imageReference = ImageReference.of("example.com/test/cmd-property-name:v1");
|
||||
try (GenericContainer<?> container = new GenericContainer<>(imageReference.toString())) {
|
||||
@@ -111,10 +111,10 @@ public class BuildImageTests extends AbstractArchiveIntegrationTests {
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void whenBuildImageIsInvokedWithV2BuilderImage(MavenBuild mavenBuild) {
|
||||
mavenBuild.project("build-image-v2-builder").goals("package").execute((project) -> {
|
||||
void whenBuildImageIsInvokedWithCustomBuilderImage(MavenBuild mavenBuild) {
|
||||
mavenBuild.project("build-image-custom-builder").goals("package").execute((project) -> {
|
||||
assertThat(buildLog(project)).contains("Building image")
|
||||
.contains("paketo-buildpacks/builder:base-platform-api-0.2")
|
||||
.contains("paketo-buildpacks/builder:full-cf-platform-api-0.3")
|
||||
.contains("docker.io/library/build-image-v2-builder:0.0.1.BUILD-SNAPSHOT")
|
||||
.contains("Successfully built image");
|
||||
ImageReference imageReference = ImageReference
|
||||
@@ -132,7 +132,7 @@ public class BuildImageTests extends AbstractArchiveIntegrationTests {
|
||||
void failsWhenBuilderFails(MavenBuild mavenBuild) {
|
||||
mavenBuild.project("build-image-builder-error").goals("package")
|
||||
.executeAndFail((project) -> assertThat(buildLog(project)).contains("Building image")
|
||||
.contains("Builder lifecycle 'builder' failed with status code"));
|
||||
.containsPattern("Builder lifecycle '.*' failed with status code"));
|
||||
}
|
||||
|
||||
private void writeLongNameResource(File project) {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</goals>
|
||||
<configuration>
|
||||
<image>
|
||||
<builder>gcr.io/paketo-buildpacks/builder:base-platform-api-0.2</builder>
|
||||
<builder>gcr.io/paketo-buildpacks/builder:full-cf-platform-api-0.3</builder>
|
||||
</image>
|
||||
</configuration>
|
||||
</execution>
|
||||
Reference in New Issue
Block a user