Set platform API version when invoking image builder
The CNB specifications allow builders to support multiple platform API versions. The supported versions are published in the builder image metadata as an array of version numbers, while a single supported version number was published in earlier builder metadata. These changes read the supported versions from the builder metadata and fall back to the single version if the array is not present. A CNB_PLATFORM_API environment variable is set on each lifecycle phase invocation to request a specific version as recommended in the CNB platform spec. Fixes gh-23682
This commit is contained in:
parent
3d2a97f102
commit
5b1b03c56c
|
|
@ -105,7 +105,7 @@ final class ApiVersion {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "v" + this.major + "." + this.minor;
|
return this.major + "." + this.minor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -41,17 +41,24 @@ final class ApiVersions {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assert that the specified version is supported by these API versions.
|
* Find the latest version among the specified versions that is supported by these API
|
||||||
* @param other the version to check against
|
* versions.
|
||||||
|
* @param others the versions to check against
|
||||||
|
* @return the version
|
||||||
*/
|
*/
|
||||||
void assertSupports(ApiVersion other) {
|
ApiVersion findLatestSupported(String... others) {
|
||||||
for (ApiVersion apiVersion : this.apiVersions) {
|
for (int versionsIndex = this.apiVersions.length - 1; versionsIndex >= 0; versionsIndex--) {
|
||||||
if (apiVersion.supports(other)) {
|
ApiVersion apiVersion = this.apiVersions[versionsIndex];
|
||||||
return;
|
for (int otherIndex = others.length - 1; otherIndex >= 0; otherIndex--) {
|
||||||
|
ApiVersion other = ApiVersion.parse(others[otherIndex]);
|
||||||
|
if (apiVersion.supports(other)) {
|
||||||
|
return apiVersion;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"Detected platform API version '" + other + "' is not included in supported versions '" + this + "'");
|
"Detected platform API versions '" + StringUtils.arrayToCommaDelimitedString(others)
|
||||||
|
+ "' are not included in supported versions '" + this + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import org.springframework.util.StringUtils;
|
||||||
*
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
|
* @author Scott Frederick
|
||||||
*/
|
*/
|
||||||
class BuilderMetadata extends MappedObject {
|
class BuilderMetadata extends MappedObject {
|
||||||
|
|
||||||
|
|
@ -184,30 +185,59 @@ class BuilderMetadata extends MappedObject {
|
||||||
String getVersion();
|
String getVersion();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the API versions.
|
* Return the default API versions.
|
||||||
* @return the API versions
|
* @return the API versions
|
||||||
*/
|
*/
|
||||||
Api getApi();
|
Api getApi();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API versions.
|
* Return the supported API versions.
|
||||||
|
* @return the API versions
|
||||||
|
*/
|
||||||
|
Apis getApis();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default API versions.
|
||||||
*/
|
*/
|
||||||
interface Api {
|
interface Api {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the buildpack API version.
|
* Return the default buildpack API version.
|
||||||
* @return the buildpack version
|
* @return the buildpack version
|
||||||
*/
|
*/
|
||||||
String getBuildpack();
|
String getBuildpack();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the platform API version.
|
* Return the default platform API version.
|
||||||
* @return the platform version
|
* @return the platform version
|
||||||
*/
|
*/
|
||||||
String getPlatform();
|
String getPlatform();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supported API versions.
|
||||||
|
*/
|
||||||
|
interface Apis {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the supported buildpack API versions.
|
||||||
|
* @return the buildpack versions
|
||||||
|
*/
|
||||||
|
default String[] getBuildpack() {
|
||||||
|
return valueAt(this, "/buildpack/supported", String[].class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the supported platform API versions.
|
||||||
|
* @return the platform versions
|
||||||
|
*/
|
||||||
|
default String[] getPlatform() {
|
||||||
|
return valueAt(this, "/platform/supported", String[].class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,8 @@ class Lifecycle implements Closeable {
|
||||||
|
|
||||||
private static final LifecycleVersion LOGGING_MINIMUM_VERSION = LifecycleVersion.parse("0.0.5");
|
private static final LifecycleVersion LOGGING_MINIMUM_VERSION = LifecycleVersion.parse("0.0.5");
|
||||||
|
|
||||||
|
private static final String PLATFORM_API_VERSION_KEY = "CNB_PLATFORM_API";
|
||||||
|
|
||||||
private final BuildLog log;
|
private final BuildLog log;
|
||||||
|
|
||||||
private final DockerApi docker;
|
private final DockerApi docker;
|
||||||
|
|
@ -79,12 +81,11 @@ class Lifecycle implements Closeable {
|
||||||
this.request = request;
|
this.request = request;
|
||||||
this.builder = builder;
|
this.builder = builder;
|
||||||
this.lifecycleVersion = LifecycleVersion.parse(builder.getBuilderMetadata().getLifecycle().getVersion());
|
this.lifecycleVersion = LifecycleVersion.parse(builder.getBuilderMetadata().getLifecycle().getVersion());
|
||||||
this.platformVersion = ApiVersion.parse(builder.getBuilderMetadata().getLifecycle().getApi().getPlatform());
|
this.platformVersion = getPlatformVersion(builder.getBuilderMetadata().getLifecycle());
|
||||||
this.layersVolume = createRandomVolumeName("pack-layers-");
|
this.layersVolume = createRandomVolumeName("pack-layers-");
|
||||||
this.applicationVolume = createRandomVolumeName("pack-app-");
|
this.applicationVolume = createRandomVolumeName("pack-app-");
|
||||||
this.buildCacheVolume = createCacheVolumeName(request, ".build");
|
this.buildCacheVolume = createCacheVolumeName(request, ".build");
|
||||||
this.launchCacheVolume = createCacheVolumeName(request, ".launch");
|
this.launchCacheVolume = createCacheVolumeName(request, ".launch");
|
||||||
checkPlatformVersion(this.platformVersion);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected VolumeName createRandomVolumeName(String prefix) {
|
protected VolumeName createRandomVolumeName(String prefix) {
|
||||||
|
|
@ -95,8 +96,13 @@ class Lifecycle implements Closeable {
|
||||||
return VolumeName.basedOn(request.getName(), ImageReference::toLegacyString, "pack-cache-", suffix, 6);
|
return VolumeName.basedOn(request.getName(), ImageReference::toLegacyString, "pack-cache-", suffix, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkPlatformVersion(ApiVersion platformVersion) {
|
private ApiVersion getPlatformVersion(BuilderMetadata.Lifecycle lifecycle) {
|
||||||
ApiVersions.SUPPORTED_PLATFORMS.assertSupports(platformVersion);
|
if (lifecycle.getApis().getPlatform() != null) {
|
||||||
|
String[] supportedVersions = lifecycle.getApis().getPlatform();
|
||||||
|
return ApiVersions.SUPPORTED_PLATFORMS.findLatestSupported(supportedVersions);
|
||||||
|
}
|
||||||
|
String version = lifecycle.getApi().getPlatform();
|
||||||
|
return ApiVersions.SUPPORTED_PLATFORMS.findLatestSupported(version);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -133,6 +139,7 @@ class Lifecycle implements Closeable {
|
||||||
phase.withBinds(this.applicationVolume, Directory.APPLICATION);
|
phase.withBinds(this.applicationVolume, Directory.APPLICATION);
|
||||||
phase.withBinds(this.buildCacheVolume, Directory.CACHE);
|
phase.withBinds(this.buildCacheVolume, Directory.CACHE);
|
||||||
phase.withBinds(this.launchCacheVolume, Directory.LAUNCH_CACHE);
|
phase.withBinds(this.launchCacheVolume, Directory.LAUNCH_CACHE);
|
||||||
|
phase.withEnv(PLATFORM_API_VERSION_KEY, this.platformVersion.toString());
|
||||||
return phase;
|
return phase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,8 @@ class Phase {
|
||||||
|
|
||||||
private final Map<VolumeName, String> binds = new LinkedHashMap<>();
|
private final Map<VolumeName, String> binds = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
private final Map<String, String> env = new LinkedHashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
|
@ -91,6 +93,15 @@ class Phase {
|
||||||
this.binds.put(source, dest);
|
this.binds.put(source, dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update this phase with an additional environment variable.
|
||||||
|
* @param name the variable name
|
||||||
|
* @param value the variable value
|
||||||
|
*/
|
||||||
|
void withEnv(String name, String value) {
|
||||||
|
this.env.put(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the name of the phase.
|
* Return the name of the phase.
|
||||||
* @return the phase name
|
* @return the phase name
|
||||||
|
|
@ -116,6 +127,7 @@ class Phase {
|
||||||
update.withCommand("/cnb/lifecycle/" + this.name, StringUtils.toStringArray(this.args));
|
update.withCommand("/cnb/lifecycle/" + this.name, StringUtils.toStringArray(this.args));
|
||||||
update.withLabel("author", "spring-boot");
|
update.withLabel("author", "spring-boot");
|
||||||
this.binds.forEach(update::withBind);
|
this.binds.forEach(update::withBind);
|
||||||
|
this.env.forEach(update::withEnv);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ import org.springframework.util.StringUtils;
|
||||||
* Configuration used when creating a new container.
|
* Configuration used when creating a new container.
|
||||||
*
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
|
* @author Scott Frederick
|
||||||
* @since 2.3.0
|
* @since 2.3.0
|
||||||
*/
|
*/
|
||||||
public class ContainerConfig {
|
public class ContainerConfig {
|
||||||
|
|
@ -46,7 +47,7 @@ public class ContainerConfig {
|
||||||
private final String json;
|
private final String json;
|
||||||
|
|
||||||
ContainerConfig(String user, ImageReference image, String command, List<String> args, Map<String, String> labels,
|
ContainerConfig(String user, ImageReference image, String command, List<String> args, Map<String, String> labels,
|
||||||
Map<String, String> binds) throws IOException {
|
Map<String, String> binds, Map<String, String> env) throws IOException {
|
||||||
Assert.notNull(image, "Image must not be null");
|
Assert.notNull(image, "Image must not be null");
|
||||||
Assert.hasText(command, "Command must not be empty");
|
Assert.hasText(command, "Command must not be empty");
|
||||||
ObjectMapper objectMapper = SharedObjectMapper.get();
|
ObjectMapper objectMapper = SharedObjectMapper.get();
|
||||||
|
|
@ -58,6 +59,8 @@ public class ContainerConfig {
|
||||||
ArrayNode commandNode = node.putArray("Cmd");
|
ArrayNode commandNode = node.putArray("Cmd");
|
||||||
commandNode.add(command);
|
commandNode.add(command);
|
||||||
args.forEach(commandNode::add);
|
args.forEach(commandNode::add);
|
||||||
|
ArrayNode envNode = node.putArray("Env");
|
||||||
|
env.forEach((name, value) -> envNode.add(name + "=" + value));
|
||||||
ObjectNode labelsNode = node.putObject("Labels");
|
ObjectNode labelsNode = node.putObject("Labels");
|
||||||
labels.forEach(labelsNode::put);
|
labels.forEach(labelsNode::put);
|
||||||
ObjectNode hostConfigNode = node.putObject("HostConfig");
|
ObjectNode hostConfigNode = node.putObject("HostConfig");
|
||||||
|
|
@ -109,6 +112,8 @@ public class ContainerConfig {
|
||||||
|
|
||||||
private final Map<String, String> binds = new LinkedHashMap<>();
|
private final Map<String, String> binds = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
private final Map<String, String> env = new LinkedHashMap<>();
|
||||||
|
|
||||||
Update(ImageReference image) {
|
Update(ImageReference image) {
|
||||||
this.image = image;
|
this.image = image;
|
||||||
}
|
}
|
||||||
|
|
@ -116,7 +121,8 @@ public class ContainerConfig {
|
||||||
private ContainerConfig run(Consumer<Update> update) {
|
private ContainerConfig run(Consumer<Update> update) {
|
||||||
update.accept(this);
|
update.accept(this);
|
||||||
try {
|
try {
|
||||||
return new ContainerConfig(this.user, this.image, this.command, this.args, this.labels, this.binds);
|
return new ContainerConfig(this.user, this.image, this.command, this.args, this.labels, this.binds,
|
||||||
|
this.env);
|
||||||
}
|
}
|
||||||
catch (IOException ex) {
|
catch (IOException ex) {
|
||||||
throw new IllegalStateException(ex);
|
throw new IllegalStateException(ex);
|
||||||
|
|
@ -177,6 +183,15 @@ public class ContainerConfig {
|
||||||
this.binds.put(source, dest);
|
this.binds.put(source, dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the container config with an additional environment variable.
|
||||||
|
* @param name the variable name
|
||||||
|
* @param value the variable value
|
||||||
|
*/
|
||||||
|
public void withEnv(String name, String value) {
|
||||||
|
this.env.put(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ class ApiVersionTests {
|
||||||
void assertSupportsWhenDoesNotSupportThrowsException() {
|
void assertSupportsWhenDoesNotSupportThrowsException() {
|
||||||
assertThatIllegalStateException()
|
assertThatIllegalStateException()
|
||||||
.isThrownBy(() -> ApiVersion.parse("1.2").assertSupports(ApiVersion.parse("1.3")))
|
.isThrownBy(() -> ApiVersion.parse("1.2").assertSupports(ApiVersion.parse("1.3")))
|
||||||
.withMessage("Detected platform API version 'v1.3' does not match supported version 'v1.2'");
|
.withMessage("Detected platform API version '1.3' does not match supported version '1.2'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -99,7 +99,7 @@ class ApiVersionTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void toStringReturnsString() {
|
void toStringReturnsString() {
|
||||||
assertThat(ApiVersion.parse("1.2").toString()).isEqualTo("v1.2");
|
assertThat(ApiVersion.parse("1.2").toString()).isEqualTo("1.2");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
|
|
@ -29,20 +29,45 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||||
class ApiVersionsTests {
|
class ApiVersionsTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void assertSupportsWhenAllSupports() {
|
void findsLatestWhenOneMatchesMajor() {
|
||||||
ApiVersions.parse("1.1", "2.2").assertSupports(ApiVersion.parse("1.0"));
|
ApiVersion version = ApiVersions.parse("1.1", "2.2").findLatestSupported("1.0");
|
||||||
|
assertThat(version).isEqualTo(ApiVersion.parse("1.1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void assertSupportsWhenDoesNoneSupportedThrowsException() {
|
void findsLatestWhenOneMatchesWithReleaseVersions() {
|
||||||
|
ApiVersion version = ApiVersions.parse("1.1", "1.2").findLatestSupported("1.1");
|
||||||
|
assertThat(version).isEqualTo(ApiVersion.parse("1.2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findsLatestWhenOneMatchesWithPreReleaseVersions() {
|
||||||
|
ApiVersion version = ApiVersions.parse("0.2", "0.3").findLatestSupported("0.2");
|
||||||
|
assertThat(version).isEqualTo(ApiVersion.parse("0.2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findsLatestWhenMultipleMatchesWithReleaseVersions() {
|
||||||
|
ApiVersion version = ApiVersions.parse("1.1", "1.2").findLatestSupported("1.1", "1.2");
|
||||||
|
assertThat(version).isEqualTo(ApiVersion.parse("1.2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findsLatestWhenMultipleMatchesWithPreReleaseVersions() {
|
||||||
|
ApiVersion version = ApiVersions.parse("0.2", "0.3").findLatestSupported("0.2", "0.3");
|
||||||
|
assertThat(version).isEqualTo(ApiVersion.parse("0.3"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void findLatestWhenNoneSupportedThrowsException() {
|
||||||
assertThatIllegalStateException()
|
assertThatIllegalStateException()
|
||||||
.isThrownBy(() -> ApiVersions.parse("1.1", "1.2").assertSupports(ApiVersion.parse("1.3")))
|
.isThrownBy(() -> ApiVersions.parse("1.1", "1.2").findLatestSupported("1.3", "1.4")).withMessage(
|
||||||
.withMessage("Detected platform API version 'v1.3' is not included in supported versions 'v1.1,v1.2'");
|
"Detected platform API versions '1.3,1.4' are not included in supported versions '1.1,1.2'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void toStringReturnsString() {
|
void toStringReturnsString() {
|
||||||
assertThat(ApiVersions.parse("1.1", "2.2", "3.3").toString()).isEqualTo("v1.1,v2.2,v3.3");
|
assertThat(ApiVersions.parse("1.1", "2.2", "3.3").toString()).isEqualTo("1.1,2.2,3.3");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,12 @@
|
||||||
|
|
||||||
package org.springframework.boot.buildpack.platform.build;
|
package org.springframework.boot.buildpack.platform.build;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
|
@ -76,6 +80,28 @@ class BuilderMetadataTests extends AbstractJsonTests {
|
||||||
.withMessage("No 'io.buildpacks.builder.metadata' label found in image config labels 'alpha'");
|
.withMessage("No 'io.buildpacks.builder.metadata' label found in image config labels 'alpha'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void fromJsonLoadsMetadataWithoutSupportedApis() throws IOException {
|
||||||
|
BuilderMetadata metadata = BuilderMetadata.fromJson(getContentAsString("builder-metadata.json"));
|
||||||
|
assertThat(metadata.getStack().getRunImage().getImage()).isEqualTo("cloudfoundry/run:base-cnb");
|
||||||
|
assertThat(metadata.getStack().getRunImage().getMirrors()).isEmpty();
|
||||||
|
assertThat(metadata.getLifecycle().getVersion()).isEqualTo("0.7.2");
|
||||||
|
assertThat(metadata.getLifecycle().getApi().getBuildpack()).isEqualTo("0.2");
|
||||||
|
assertThat(metadata.getLifecycle().getApi().getPlatform()).isEqualTo("0.3");
|
||||||
|
assertThat(metadata.getLifecycle().getApis().getBuildpack()).isNull();
|
||||||
|
assertThat(metadata.getLifecycle().getApis().getPlatform()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void fromJsonLoadsMetadataWithSupportedApis() throws IOException {
|
||||||
|
BuilderMetadata metadata = BuilderMetadata.fromJson(getContentAsString("builder-metadata-supported-apis.json"));
|
||||||
|
assertThat(metadata.getLifecycle().getVersion()).isEqualTo("0.7.2");
|
||||||
|
assertThat(metadata.getLifecycle().getApi().getBuildpack()).isEqualTo("0.2");
|
||||||
|
assertThat(metadata.getLifecycle().getApi().getPlatform()).isEqualTo("0.4");
|
||||||
|
assertThat(metadata.getLifecycle().getApis().getBuildpack()).containsExactly("0.1", "0.2", "0.3");
|
||||||
|
assertThat(metadata.getLifecycle().getApis().getPlatform()).containsExactly("0.3", "0.4");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void copyWithUpdatedCreatedByReturnsNewMetadata() throws IOException {
|
void copyWithUpdatedCreatedByReturnsNewMetadata() throws IOException {
|
||||||
Image image = Image.of(getContent("image.json"));
|
Image image = Image.of(getContent("image.json"));
|
||||||
|
|
@ -98,4 +124,9 @@ class BuilderMetadataTests extends AbstractJsonTests {
|
||||||
.isEqualTo(metadata.getStack().getRunImage().getImage());
|
.isEqualTo(metadata.getStack().getRunImage().getImage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getContentAsString(String name) {
|
||||||
|
return new BufferedReader(new InputStreamReader(getContent(name), StandardCharsets.UTF_8)).lines()
|
||||||
|
.collect(Collectors.joining("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,35 @@ class LifecycleTests {
|
||||||
verify(this.docker.volume()).delete(name, true);
|
verify(this.docker.volume()).delete(name, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void executeWhenPlatformApiNotSupportedThrowsException() 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));
|
||||||
|
assertThatIllegalStateException()
|
||||||
|
.isThrownBy(() -> createLifecycle("builder-metadata-unsupported-api.json").execute())
|
||||||
|
.withMessage("Detected platform API versions '0.2' are not included in supported versions '0.3'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void executeWhenMultiplePlatformApisNotSupportedThrowsException() 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));
|
||||||
|
assertThatIllegalStateException()
|
||||||
|
.isThrownBy(() -> createLifecycle("builder-metadata-unsupported-apis.json").execute())
|
||||||
|
.withMessage("Detected platform API versions '0.4,0.5' are not included in supported versions '0.3'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void executeWhenMultiplePlatformApisSupportedExecutesPhase() 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("builder-metadata-supported-apis.json").execute();
|
||||||
|
assertPhaseWasRun("creator", withExpectedConfig("lifecycle-creator.json"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void closeClearsVolumes() throws Exception {
|
void closeClearsVolumes() throws Exception {
|
||||||
createLifecycle().close();
|
createLifecycle().close();
|
||||||
|
|
@ -159,12 +188,25 @@ class LifecycleTests {
|
||||||
|
|
||||||
private Lifecycle createLifecycle(BuildRequest request) throws IOException {
|
private Lifecycle createLifecycle(BuildRequest request) throws IOException {
|
||||||
EphemeralBuilder builder = mockEphemeralBuilder();
|
EphemeralBuilder builder = mockEphemeralBuilder();
|
||||||
return new TestLifecycle(BuildLog.to(this.out), this.docker, request, builder);
|
return createLifecycle(request, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Lifecycle createLifecycle(String builderMetadata) throws IOException {
|
||||||
|
EphemeralBuilder builder = mockEphemeralBuilder(builderMetadata);
|
||||||
|
return createLifecycle(getTestRequest(), builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Lifecycle createLifecycle(BuildRequest request, EphemeralBuilder ephemeralBuilder) {
|
||||||
|
return new TestLifecycle(BuildLog.to(this.out), this.docker, request, ephemeralBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
private EphemeralBuilder mockEphemeralBuilder() throws IOException {
|
private EphemeralBuilder mockEphemeralBuilder() throws IOException {
|
||||||
|
return mockEphemeralBuilder("builder-metadata.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
private EphemeralBuilder mockEphemeralBuilder(String builderMetadata) throws IOException {
|
||||||
EphemeralBuilder builder = mock(EphemeralBuilder.class);
|
EphemeralBuilder builder = mock(EphemeralBuilder.class);
|
||||||
byte[] metadataContent = FileCopyUtils.copyToByteArray(getClass().getResourceAsStream("builder-metadata.json"));
|
byte[] metadataContent = FileCopyUtils.copyToByteArray(getClass().getResourceAsStream(builderMetadata));
|
||||||
BuilderMetadata metadata = BuilderMetadata.fromJson(new String(metadataContent, StandardCharsets.UTF_8));
|
BuilderMetadata metadata = BuilderMetadata.fromJson(new String(metadataContent, StandardCharsets.UTF_8));
|
||||||
given(builder.getName()).willReturn(ImageReference.of("pack.local/ephemeral-builder"));
|
given(builder.getName()).willReturn(ImageReference.of("pack.local/ephemeral-builder"));
|
||||||
given(builder.getBuilderMetadata()).willReturn(metadata);
|
given(builder.getBuilderMetadata()).willReturn(metadata);
|
||||||
|
|
|
||||||
|
|
@ -117,4 +117,18 @@ class PhaseTests {
|
||||||
verifyNoMoreInteractions(update);
|
verifyNoMoreInteractions(update);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void applyWhenWithEnvUpdatesConfigurationWithEnv() {
|
||||||
|
Phase phase = new Phase("test", false);
|
||||||
|
phase.withEnv("name1", "value1");
|
||||||
|
phase.withEnv("name2", "value2");
|
||||||
|
Update update = mock(Update.class);
|
||||||
|
phase.apply(update);
|
||||||
|
verify(update).withCommand("/cnb/lifecycle/test");
|
||||||
|
verify(update).withLabel("author", "spring-boot");
|
||||||
|
verify(update).withEnv("name1", "value1");
|
||||||
|
verify(update).withEnv("name2", "value2");
|
||||||
|
verifyNoMoreInteractions(update);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||||
* Tests for {@link ContainerConfig}.
|
* Tests for {@link ContainerConfig}.
|
||||||
*
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
|
* @author Scott Frederick
|
||||||
*/
|
*/
|
||||||
class ContainerConfigTests extends AbstractJsonTests {
|
class ContainerConfigTests extends AbstractJsonTests {
|
||||||
|
|
||||||
|
|
@ -56,6 +57,8 @@ class ContainerConfigTests extends AbstractJsonTests {
|
||||||
update.withArgs("-h");
|
update.withArgs("-h");
|
||||||
update.withLabel("spring", "boot");
|
update.withLabel("spring", "boot");
|
||||||
update.withBind("bind-source", "bind-dest");
|
update.withBind("bind-source", "bind-dest");
|
||||||
|
update.withEnv("name1", "value1");
|
||||||
|
update.withEnv("name2", "value2");
|
||||||
});
|
});
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
containerConfig.writeTo(outputStream);
|
containerConfig.writeTo(outputStream);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"description": "Ubuntu bionic base image with buildpacks for Java, NodeJS and Golang",
|
||||||
|
"buildpacks": [
|
||||||
|
{
|
||||||
|
"id": "org.cloudfoundry.springboot",
|
||||||
|
"version": "v1.2.13"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stack": {
|
||||||
|
"runImage": {
|
||||||
|
"image": "cloudfoundry/run:base-cnb",
|
||||||
|
"mirrors": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lifecycle": {
|
||||||
|
"version": "0.7.2",
|
||||||
|
"api": {
|
||||||
|
"buildpack": "0.2",
|
||||||
|
"platform": "0.4"
|
||||||
|
},
|
||||||
|
"apis": {
|
||||||
|
"buildpack": {
|
||||||
|
"deprecated": [],
|
||||||
|
"supported": [
|
||||||
|
"0.1",
|
||||||
|
"0.2",
|
||||||
|
"0.3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"platform": {
|
||||||
|
"deprecated": [],
|
||||||
|
"supported": [
|
||||||
|
"0.3",
|
||||||
|
"0.4"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"createdBy": {
|
||||||
|
"name": "Pack CLI",
|
||||||
|
"version": "v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"description": "Ubuntu bionic base image with buildpacks for Java, NodeJS and Golang",
|
||||||
|
"buildpacks": [
|
||||||
|
{
|
||||||
|
"id": "org.cloudfoundry.springboot",
|
||||||
|
"version": "v1.2.13"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stack": {
|
||||||
|
"runImage": {
|
||||||
|
"image": "cloudfoundry/run:base-cnb",
|
||||||
|
"mirrors": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lifecycle": {
|
||||||
|
"version": "0.7.2",
|
||||||
|
"api": {
|
||||||
|
"buildpack": "0.2",
|
||||||
|
"platform": "0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"createdBy": {
|
||||||
|
"name": "Pack CLI",
|
||||||
|
"version": "v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"description": "Ubuntu bionic base image with buildpacks for Java, NodeJS and Golang",
|
||||||
|
"buildpacks": [
|
||||||
|
{
|
||||||
|
"id": "org.cloudfoundry.springboot",
|
||||||
|
"version": "v1.2.13"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stack": {
|
||||||
|
"runImage": {
|
||||||
|
"image": "cloudfoundry/run:base-cnb",
|
||||||
|
"mirrors": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lifecycle": {
|
||||||
|
"version": "0.7.2",
|
||||||
|
"api": {
|
||||||
|
"buildpack": "0.2",
|
||||||
|
"platform": "0.3"
|
||||||
|
},
|
||||||
|
"apis": {
|
||||||
|
"buildpack": {
|
||||||
|
"deprecated": [],
|
||||||
|
"supported": [
|
||||||
|
"0.1",
|
||||||
|
"0.2",
|
||||||
|
"0.3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"platform": {
|
||||||
|
"deprecated": [],
|
||||||
|
"supported": [
|
||||||
|
"0.4",
|
||||||
|
"0.5"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"createdBy": {
|
||||||
|
"name": "Pack CLI",
|
||||||
|
"version": "v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
"User" : "root",
|
"User" : "root",
|
||||||
"Image" : "pack.local/ephemeral-builder",
|
"Image" : "pack.local/ephemeral-builder",
|
||||||
"Cmd" : [ "/cnb/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run:latest", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "-skip-restore", "docker.io/library/my-application:latest" ],
|
"Cmd" : [ "/cnb/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run:latest", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "-skip-restore", "docker.io/library/my-application:latest" ],
|
||||||
|
"Env" : [ "CNB_PLATFORM_API=0.3" ],
|
||||||
"Labels" : {
|
"Labels" : {
|
||||||
"author" : "spring-boot"
|
"author" : "spring-boot"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
"User" : "root",
|
"User" : "root",
|
||||||
"Image" : "pack.local/ephemeral-builder",
|
"Image" : "pack.local/ephemeral-builder",
|
||||||
"Cmd" : [ "/cnb/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run:latest", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "docker.io/library/my-application:latest" ],
|
"Cmd" : [ "/cnb/lifecycle/creator", "-app", "/workspace", "-platform", "/platform", "-run-image", "docker.io/cloudfoundry/run:latest", "-layers", "/layers", "-cache-dir", "/cache", "-launch-cache", "/launch-cache", "-daemon", "docker.io/library/my-application:latest" ],
|
||||||
|
"Env" : [ "CNB_PLATFORM_API=0.3" ],
|
||||||
"Labels" : {
|
"Labels" : {
|
||||||
"author" : "spring-boot"
|
"author" : "spring-boot"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,10 @@
|
||||||
"-l",
|
"-l",
|
||||||
"-h"
|
"-h"
|
||||||
],
|
],
|
||||||
|
"Env": [
|
||||||
|
"name1=value1",
|
||||||
|
"name2=value2"
|
||||||
|
],
|
||||||
"Labels": {
|
"Labels": {
|
||||||
"spring": "boot"
|
"spring": "boot"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue