Add support for CNB platform API v0.2

Cloud Native Buildpacks platform API version 0.2 introduced
two breaking changes: the order of invoking the restore and analyze
phases was reversed, and the cache phase was removed in favor of
distributing caching across other phases.

This commit adds support for Cloud Native Buildpacks builders that
implement platform API version 0.2, while maintaining compatibility
with builders that implement Lifecycle version platform API
version 0.1.

Closes gh-19829
pull/20058/head
Scott Frederick 5 years ago
parent 4add9632de
commit d07062652e

@ -22,16 +22,14 @@ import java.util.regex.Pattern;
import org.springframework.util.Assert;
/**
* API Version number comprised a major and minor value.
* API Version number comprised of a major and minor value.
*
* @author Phillip Webb
* @author Scott Frederick
*/
final class ApiVersion {
/**
* The platform API version supported by this release.
*/
static final ApiVersion PLATFORM = new ApiVersion(0, 1);
private static final ApiVersion PLATFORM_0_1 = new ApiVersion(0, 1);
private static final Pattern PATTERN = Pattern.compile("^v?(\\d+)\\.(\\d*)$");
@ -68,7 +66,7 @@ final class ApiVersion {
void assertSupports(ApiVersion other) {
if (!supports(other)) {
throw new IllegalStateException(
"Version '" + other + "' is not supported by this version ('" + this + "')");
"Detected platform API version '" + other + "' does not match supported version '" + this + "'");
}
}
@ -77,7 +75,7 @@ final class ApiVersion {
* the same version number. A 1.x or higher release matches when the versions have the
* same major version and a minor that is equal or greater.
* @param other the version to check against
* @return of the specified API is supported
* @return if the specified API is supported
* @see #assertSupports(ApiVersion)
*/
boolean supports(ApiVersion other) {
@ -90,6 +88,25 @@ final class ApiVersion {
return this.minor >= other.minor;
}
/**
* Returns a value indicating whether the API version has a separate cache phase, or
* caching is distributed across other stages.
* @return {@code true} if the API has a separate cache phase, {@code false} otherwise
*/
boolean hasCachePhase() {
return supports(PLATFORM_0_1);
}
/**
* Returns a value indicating whether the API version requires that the analyze phase
* must follow the restore phase, or if the reverse order is required.
* @return {@code true} if the analyze phase follows restore, {@code false} if restore
* follows analyze
*/
boolean analyzeFollowsRestore() {
return supports(PLATFORM_0_1);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
@ -132,4 +149,8 @@ final class ApiVersion {
}
}
static ApiVersion of(int major, int minor) {
return new ApiVersion(major, minor);
}
}

@ -0,0 +1,91 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.buildpack.platform.build;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.util.StringUtils;
/**
* A set of API Version numbers comprised of major and minor values.
*
* @author Scott Frederick
*/
final class ApiVersions {
/**
* The platform API versions supported by this release.
*/
static final ApiVersions SUPPORTED_PLATFORMS = new ApiVersions(ApiVersion.of(0, 1), ApiVersion.of(0, 2));
private final ApiVersion[] apiVersions;
private ApiVersions(ApiVersion... versions) {
this.apiVersions = versions;
}
/**
* Assert that the specified version is supported by these API versions.
* @param other the version to check against
*/
void assertSupports(ApiVersion other) {
for (ApiVersion apiVersion : this.apiVersions) {
if (apiVersion.supports(other)) {
return;
}
}
throw new IllegalStateException(
"Detected platform API version '" + other + "' is not included in supported versions '" + this + "'");
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
ApiVersions other = (ApiVersions) obj;
return Arrays.equals(this.apiVersions, other.apiVersions);
}
@Override
public int hashCode() {
return Arrays.hashCode(this.apiVersions);
}
@Override
public String toString() {
return StringUtils.arrayToCommaDelimitedString(this.apiVersions);
}
/**
* Factory method to parse strings into an {@link ApiVersions} instance.
* @param values the values to parse.
* @return the corresponding {@link ApiVersions}
* @throws IllegalArgumentException if any values could not be parsed
*/
static ApiVersions parse(String... values) {
List<ApiVersion> versions = Arrays.stream(values).map(ApiVersion::parse).collect(Collectors.toList());
return new ApiVersions(versions.toArray(new ApiVersion[] {}));
}
}

@ -30,7 +30,6 @@ import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.docker.type.VolumeName;
import org.springframework.boot.buildpack.platform.io.TarArchive;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* A buildpack lifecycle used to run the build {@link Phase phases} needed to package an
@ -40,7 +39,7 @@ import org.springframework.util.StringUtils;
*/
class Lifecycle implements Closeable {
private static final LifecycleVersion LOGGING_SUPPORTED_VERSION = LifecycleVersion.parse("0.0.5");
private static final LifecycleVersion LOGGING_MINIMUM_VERSION = LifecycleVersion.parse("0.0.5");
private final BuildLog log;
@ -52,7 +51,9 @@ class Lifecycle implements Closeable {
private final EphemeralBuilder builder;
private final LifecycleVersion version;
private final LifecycleVersion lifecycleVersion;
private final ApiVersion platformVersion;
private final VolumeName layersVolume;
@ -76,17 +77,18 @@ class Lifecycle implements Closeable {
*/
Lifecycle(BuildLog log, DockerApi docker, BuildRequest request, ImageReference runImageReference,
EphemeralBuilder builder) {
checkPlatformVersion(builder);
this.log = log;
this.docker = docker;
this.request = request;
this.runImageReference = runImageReference;
this.builder = builder;
this.version = LifecycleVersion.parse(builder.getBuilderMetadata().getLifecycle().getVersion());
this.lifecycleVersion = LifecycleVersion.parse(builder.getBuilderMetadata().getLifecycle().getVersion());
this.platformVersion = ApiVersion.parse(builder.getBuilderMetadata().getLifecycle().getApi().getPlatform());
this.layersVolume = createRandomVolumeName("pack-layers-");
this.applicationVolume = createRandomVolumeName("pack-app-");
this.buildCacheVolume = createCacheVolumeName(request, ".build");
this.launchCacheVolume = createCacheVolumeName(request, ".launch");
checkPlatformVersion(this.platformVersion);
}
protected VolumeName createRandomVolumeName(String prefix) {
@ -97,11 +99,8 @@ class Lifecycle implements Closeable {
return VolumeName.basedOn(request.getName(), ImageReference::toLegacyString, "pack-cache-", suffix, 6);
}
private void checkPlatformVersion(EphemeralBuilder ephemeralBuilder) {
String platformVersion = ephemeralBuilder.getBuilderMetadata().getLifecycle().getApi().getPlatform();
if (StringUtils.hasText(platformVersion)) {
ApiVersion.PLATFORM.assertSupports(ApiVersion.parse(platformVersion));
}
private void checkPlatformVersion(ApiVersion platformVersion) {
ApiVersions.SUPPORTED_PLATFORMS.assertSupports(platformVersion);
}
/**
@ -111,16 +110,24 @@ class Lifecycle implements Closeable {
void execute() throws IOException {
Assert.state(!this.executed, "Lifecycle has already been executed");
this.executed = true;
this.log.executingLifecycle(this.request, this.version, this.buildCacheVolume);
this.log.executingLifecycle(this.request, this.lifecycleVersion, this.buildCacheVolume);
if (this.request.isCleanCache()) {
deleteVolume(this.buildCacheVolume);
}
run(detectPhase());
run(restorePhase());
run(analyzePhase());
if (this.platformVersion.analyzeFollowsRestore()) {
run(restorePhase());
run(analyzePhase());
}
else {
run(analyzePhase());
run(restorePhase());
}
run(buildPhase());
run(exportPhase());
run(cachePhase());
if (this.platformVersion.hasCachePhase()) {
run(cachePhase());
}
this.log.executedLifecycle(this.request);
}
@ -133,9 +140,10 @@ class Lifecycle implements Closeable {
}
private Phase restorePhase() {
String cacheDirArg = this.platformVersion.hasCachePhase() ? "-path" : "-cache-dir";
Phase phase = createPhase("restorer");
phase.withDaemonAccess();
phase.withArgs("-path", Folder.CACHE);
phase.withArgs(cacheDirArg, Folder.CACHE);
phase.withArgs("-layers", Folder.LAYERS);
phase.withLogLevelArg();
phase.withBinds(this.buildCacheVolume, Folder.CACHE);
@ -151,7 +159,13 @@ class Lifecycle implements Closeable {
}
phase.withArgs("-daemon");
phase.withArgs("-layers", Folder.LAYERS);
if (!this.platformVersion.hasCachePhase()) {
phase.withArgs("-cache-dir", Folder.CACHE);
}
phase.withArgs(this.request.getName());
if (!this.platformVersion.hasCachePhase()) {
phase.withBinds(this.buildCacheVolume, Folder.CACHE);
}
return phase;
}
@ -172,8 +186,14 @@ class Lifecycle implements Closeable {
phase.withArgs("-app", Folder.APPLICATION);
phase.withArgs("-daemon");
phase.withArgs("-launch-cache", Folder.LAUNCH_CACHE);
if (!this.platformVersion.hasCachePhase()) {
phase.withArgs("-cache-dir", Folder.CACHE);
}
phase.withArgs(this.request.getName());
phase.withBinds(this.launchCacheVolume, Folder.LAUNCH_CACHE);
if (!this.platformVersion.hasCachePhase()) {
phase.withBinds(this.buildCacheVolume, Folder.CACHE);
}
return phase;
}
@ -189,7 +209,7 @@ class Lifecycle implements Closeable {
private Phase createPhase(String name) {
boolean verboseLogging = this.request.isVerboseLogging()
&& this.version.isEqualOrGreaterThan(LOGGING_SUPPORTED_VERSION);
&& this.lifecycleVersion.isEqualOrGreaterThan(LOGGING_MINIMUM_VERSION);
Phase phase = new Phase(name, verboseLogging);
phase.withBinds(this.layersVolume, Folder.LAYERS);
phase.withBinds(this.applicationVolume, Folder.APPLICATION);

@ -26,6 +26,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
* Tests for {@link ApiVersion}.
*
* @author Phillip Webb
* @author Scott Frederick
*/
class ApiVersionTests {
@ -63,7 +64,7 @@ class ApiVersionTests {
void assertSupportsWhenDoesNotSupportThrowsException() {
assertThatIllegalStateException()
.isThrownBy(() -> ApiVersion.parse("1.2").assertSupports(ApiVersion.parse("1.3")))
.withMessage("Version 'v1.3' is not supported by this version ('v1.2')");
.withMessage("Detected platform API version 'v1.3' does not match supported version 'v1.2'");
}
@Test

@ -0,0 +1,57 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.buildpack.platform.build;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link ApiVersions}.
*
* @author Scott Frederick
*/
class ApiVersionsTests {
@Test
void assertSupportsWhenAllSupports() {
ApiVersions.parse("1.1", "2.2").assertSupports(ApiVersion.parse("1.0"));
}
@Test
void assertSupportsWhenDoesNoneSupportedThrowsException() {
assertThatIllegalStateException()
.isThrownBy(() -> ApiVersions.parse("1.1", "1.2").assertSupports(ApiVersion.parse("1.3")))
.withMessage("Detected platform API version 'v1.3' is not included in supported versions 'v1.1,v1.2'");
}
@Test
void toStringReturnsString() {
assertThat(ApiVersions.parse("1.1", "2.2", "3.3").toString()).isEqualTo("v1.1,v2.2,v3.3");
}
@Test
void equalsAndHashCode() {
ApiVersions v12a = ApiVersions.parse("1.2", "2.3");
ApiVersions v12b = ApiVersions.parse("1.2", "2.3");
ApiVersions v13 = ApiVersions.parse("1.3", "2.4");
assertThat(v12a.hashCode()).isEqualTo(v12b.hashCode());
assertThat(v12a).isEqualTo(v12a).isEqualTo(v12b).isNotEqualTo(v13);
}
}

@ -60,6 +60,7 @@ import static org.mockito.Mockito.verify;
* Tests for {@link Lifecycle}.
*
* @author Phillip Webb
* @author Scott Frederick
*/
class LifecycleTests {
@ -67,43 +68,43 @@ class LifecycleTests {
private DockerApi docker;
private Lifecycle lifecycle;
private Map<String, ContainerConfig> configs = new LinkedHashMap<>();
private Map<String, ContainerContent> content = new LinkedHashMap<>();
@BeforeEach
void setup() throws Exception {
void setup() {
this.out = new TestPrintStream();
this.docker = mockDockerApi();
BuildRequest request = getTestRequest();
this.lifecycle = createLifecycle(request);
}
private DockerApi mockDockerApi() {
DockerApi docker = mock(DockerApi.class);
ImageApi imageApi = mock(ImageApi.class);
ContainerApi containerApi = mock(ContainerApi.class);
VolumeApi volumeApi = mock(VolumeApi.class);
given(docker.image()).willReturn(imageApi);
given(docker.container()).willReturn(containerApi);
given(docker.volume()).willReturn(volumeApi);
return docker;
@Test
void executeExecutesPhasesWithCacherPhase() throws Exception {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
createLifecycle("0.5").execute();
assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json"));
assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer-0.1.json"));
assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer-0.1.json"));
assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json"));
assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-0.1.json"));
assertPhaseWasRun("cacher", withExpectedConfig("lifecycle-cacher.json"));
assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
}
@Test
void executeExecutesPhases() throws Exception {
void executeExecutesPhasesWithEmbeddedCaching() throws Exception {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
this.lifecycle.execute();
createLifecycle("0.6").execute();
assertPhaseWasRun("detector", withExpectedConfig("lifecycle-detector.json"));
assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer.json"));
assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer.json"));
assertPhaseWasRun("analyzer", withExpectedConfig("lifecycle-analyzer-0.2.json"));
assertPhaseWasRun("restorer", withExpectedConfig("lifecycle-restorer-0.2.json"));
assertPhaseWasRun("builder", withExpectedConfig("lifecycle-builder.json"));
assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter.json"));
assertPhaseWasRun("cacher", withExpectedConfig("lifecycle-cacher.json"));
assertPhaseWasRun("exporter", withExpectedConfig("lifecycle-exporter-0.2.json"));
assertPhaseWasNotRun("cacher");
assertThat(this.out.toString()).contains("Successfully built image 'docker.io/library/my-application:latest'");
}
@ -112,7 +113,7 @@ class LifecycleTests {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
this.lifecycle.execute();
createLifecycle().execute();
assertThat(this.content).hasSize(1);
}
@ -121,8 +122,9 @@ class LifecycleTests {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(0, null));
this.lifecycle.execute();
assertThatIllegalStateException().isThrownBy(this.lifecycle::execute)
Lifecycle lifecycle = createLifecycle();
lifecycle.execute();
assertThatIllegalStateException().isThrownBy(lifecycle::execute)
.withMessage("Lifecycle has already been executed");
}
@ -131,7 +133,7 @@ class LifecycleTests {
given(this.docker.container().create(any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().create(any(), any())).willAnswer(answerWithGeneratedContainerId());
given(this.docker.container().wait(any())).willReturn(ContainerStatus.of(9, null));
assertThatExceptionOfType(BuilderException.class).isThrownBy(() -> this.lifecycle.execute())
assertThatExceptionOfType(BuilderException.class).isThrownBy(() -> createLifecycle().execute())
.withMessage("Builder lifecycle 'detector' failed with status code 9");
}
@ -148,27 +150,50 @@ class LifecycleTests {
@Test
void closeClearsVolumes() throws Exception {
this.lifecycle.close();
createLifecycle().close();
verify(this.docker.volume()).delete(VolumeName.of("pack-layers-aaaaaaaaaa"), true);
verify(this.docker.volume()).delete(VolumeName.of("pack-app-aaaaaaaaaa"), true);
}
private DockerApi mockDockerApi() {
DockerApi docker = mock(DockerApi.class);
ImageApi imageApi = mock(ImageApi.class);
ContainerApi containerApi = mock(ContainerApi.class);
VolumeApi volumeApi = mock(VolumeApi.class);
given(docker.image()).willReturn(imageApi);
given(docker.container()).willReturn(containerApi);
given(docker.volume()).willReturn(volumeApi);
return docker;
}
private BuildRequest getTestRequest() {
TarArchive content = mock(TarArchive.class);
ImageReference name = ImageReference.of("my-application");
BuildRequest request = BuildRequest.of(name, (owner) -> content);
return request;
return BuildRequest.of(name, (owner) -> content);
}
private Lifecycle createLifecycle() throws IOException {
return createLifecycle("0.6");
}
private Lifecycle createLifecycle(String version) throws IOException {
return createLifecycle(getTestRequest(), version);
}
private Lifecycle createLifecycle(BuildRequest request) throws IOException {
EphemeralBuilder builder = mockEphemeralBuilder();
return createLifecycle(request, "0.6");
}
private Lifecycle createLifecycle(BuildRequest request, String version) throws IOException {
EphemeralBuilder builder = mockEphemeralBuilder((version != null) ? version : "0.5");
return new TestLifecycle(BuildLog.to(this.out), this.docker, request, ImageReference.of("cloudfoundry/run"),
builder);
}
private EphemeralBuilder mockEphemeralBuilder() throws IOException {
private EphemeralBuilder mockEphemeralBuilder(String version) throws IOException {
EphemeralBuilder builder = mock(EphemeralBuilder.class);
byte[] metadataContent = FileCopyUtils.copyToByteArray(getClass().getResourceAsStream("builder-metadata.json"));
byte[] metadataContent = FileCopyUtils
.copyToByteArray(getClass().getResourceAsStream("builder-metadata-version-" + version + ".json"));
BuilderMetadata metadata = BuilderMetadata.fromJson(new String(metadataContent, StandardCharsets.UTF_8));
given(builder.getName()).willReturn(ImageReference.of("pack.local/ephemeral-builder"));
given(builder.getBuilderMetadata()).willReturn(metadata);
@ -201,6 +226,11 @@ 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);

@ -0,0 +1,222 @@
{
"description": "Ubuntu bionic base image with buildpacks for Java, NodeJS and Golang",
"buildpacks": [
{
"id": "org.cloudfoundry.debug",
"version": "v1.0.106",
"latest": true
},
{
"id": "org.cloudfoundry.go",
"version": "v0.0.2",
"latest": true
},
{
"id": "org.cloudfoundry.springautoreconfiguration",
"version": "v1.0.113",
"latest": true
},
{
"id": "org.cloudfoundry.buildsystem",
"version": "v1.0.133",
"latest": true
},
{
"id": "org.cloudfoundry.procfile",
"version": "v1.0.41",
"latest": true
},
{
"id": "org.cloudfoundry.nodejs",
"version": "v0.0.5",
"latest": true
},
{
"id": "org.cloudfoundry.distzip",
"version": "v1.0.101",
"latest": true
},
{
"id": "org.cloudfoundry.jdbc",
"version": "v1.0.107",
"latest": true
},
{
"id": "org.cloudfoundry.azureapplicationinsights",
"version": "v1.0.105",
"latest": true
},
{
"id": "org.cloudfoundry.springboot",
"version": "v1.0.112",
"latest": true
},
{
"id": "org.cloudfoundry.openjdk",
"version": "v1.0.58",
"latest": true
},
{
"id": "org.cloudfoundry.tomcat",
"version": "v1.1.24",
"latest": true
},
{
"id": "org.cloudfoundry.googlestackdriver",
"version": "v1.0.54",
"latest": true
},
{
"id": "org.cloudfoundry.jmx",
"version": "v1.0.107",
"latest": true
},
{
"id": "org.cloudfoundry.archiveexpanding",
"version": "v1.0.99",
"latest": true
},
{
"id": "org.cloudfoundry.jvmapplication",
"version": "v1.0.82",
"latest": true
},
{
"id": "org.cloudfoundry.dep",
"version": "0.0.64",
"latest": true
},
{
"id": "org.cloudfoundry.go-compiler",
"version": "0.0.55",
"latest": true
},
{
"id": "org.cloudfoundry.go-mod",
"version": "0.0.58",
"latest": true
},
{
"id": "org.cloudfoundry.node-engine",
"version": "0.0.102",
"latest": true
},
{
"id": "org.cloudfoundry.npm",
"version": "0.0.63",
"latest": true
},
{
"id": "org.cloudfoundry.yarn",
"version": "0.0.69",
"latest": true
}
],
"groups": [
{
"buildpacks": [
{
"id": "org.cloudfoundry.archiveexpanding",
"version": "v1.0.99",
"optional": true
},
{
"id": "org.cloudfoundry.openjdk",
"version": "v1.0.58"
},
{
"id": "org.cloudfoundry.buildsystem",
"version": "v1.0.133",
"optional": true
},
{
"id": "org.cloudfoundry.jvmapplication",
"version": "v1.0.82"
},
{
"id": "org.cloudfoundry.tomcat",
"version": "v1.1.24",
"optional": true
},
{
"id": "org.cloudfoundry.springboot",
"version": "v1.0.112",
"optional": true
},
{
"id": "org.cloudfoundry.distzip",
"version": "v1.0.101",
"optional": true
},
{
"id": "org.cloudfoundry.procfile",
"version": "v1.0.41",
"optional": true
},
{
"id": "org.cloudfoundry.azureapplicationinsights",
"version": "v1.0.105",
"optional": true
},
{
"id": "org.cloudfoundry.debug",
"version": "v1.0.106",
"optional": true
},
{
"id": "org.cloudfoundry.googlestackdriver",
"version": "v1.0.54",
"optional": true
},
{
"id": "org.cloudfoundry.jdbc",
"version": "v1.0.107",
"optional": true
},
{
"id": "org.cloudfoundry.jmx",
"version": "v1.0.107",
"optional": true
},
{
"id": "org.cloudfoundry.springautoreconfiguration",
"version": "v1.0.113",
"optional": true
}
]
},
{
"buildpacks": [
{
"id": "org.cloudfoundry.nodejs",
"version": "v0.0.5"
}
]
},
{
"buildpacks": [
{
"id": "org.cloudfoundry.go",
"version": "v0.0.2"
}
]
}
],
"stack": {
"runImage": {
"image": "cloudfoundry/run:base-cnb",
"mirrors": null
}
},
"lifecycle": {
"version": "0.6.0",
"api": {
"buildpack": "0.2",
"platform": "0.2"
}
},
"createdBy": {
"name": "Pack CLI",
"version": "dev-2019-11-19-22:34:59"
}
}

@ -0,0 +1,11 @@
{
"User" : "root",
"Image" : "pack.local/ephemeral-builder",
"Cmd" : [ "/lifecycle/analyzer", "-daemon", "-layers", "/layers", "-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.build:/cache" ]
}
}

@ -0,0 +1,11 @@
{
"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" ]
}
}

@ -0,0 +1,11 @@
{
"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" ]
}
}
Loading…
Cancel
Save