Add buildpack option for image building

This commit adds configuration to the Maven and Gradle plugins to
allow a list of buildpacks to be provided to the image building
goal and task.

Fixes gh-21722
This commit is contained in:
Scott Frederick
2021-02-18 17:28:25 -06:00
parent d8fe9de682
commit f54f784f80
74 changed files with 3895 additions and 211 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
@@ -22,6 +22,7 @@ import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
@@ -163,6 +164,23 @@ public class BuildRequestTests {
.withMessage("Value must not be empty");
}
@Test
void withBuildpacksAddsBuildpacks() throws IOException {
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar"));
BuildpackReference buildpackReference1 = BuildpackReference.of("example/buildpack1");
BuildpackReference buildpackReference2 = BuildpackReference.of("example/buildpack2");
BuildRequest withBuildpacks = request.withBuildpacks(buildpackReference1, buildpackReference2);
assertThat(request.getBuildpacks()).isEmpty();
assertThat(withBuildpacks.getBuildpacks()).containsExactly(buildpackReference1, buildpackReference2);
}
@Test
void withBuildpacksWhenBuildpacksIsNullThrowsException() throws IOException {
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar"));
assertThatIllegalArgumentException().isThrownBy(() -> request.withBuildpacks((List<BuildpackReference>) null))
.withMessage("Buildpacks must not be null");
}
private void hasExpectedJarContent(TarArchive archive) {
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2012-2021 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.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.buildpack.platform.docker.type.Layer;
import org.springframework.boot.buildpack.platform.json.AbstractJsonTests;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link BuilderBuildpack}.
*
* @author Scott Frederick
*/
class BuilderBuildpackTests extends AbstractJsonTests {
private BuildpackResolverContext resolverContext;
@BeforeEach
void setUp() throws Exception {
BuilderMetadata metadata = BuilderMetadata.fromJson(getContentAsString("builder-metadata.json"));
this.resolverContext = mock(BuildpackResolverContext.class);
given(this.resolverContext.getBuildpackMetadata()).willReturn(metadata.getBuildpacks());
}
@Test
void resolveWhenFullyQualifiedBuildpackWithVersionResolves() throws Exception {
BuildpackReference reference = BuildpackReference.of("urn:cnb:builder:paketo-buildpacks/spring-boot@3.5.0");
Buildpack buildpack = BuilderBuildpack.resolve(this.resolverContext, reference);
assertThat(buildpack.getCoordinates())
.isEqualTo(BuildpackCoordinates.of("paketo-buildpacks/spring-boot", "3.5.0"));
assertThatNoLayersAreAdded(buildpack);
}
@Test
void resolveWhenFullyQualifiedBuildpackWithoutVersionResolves() throws Exception {
BuildpackReference reference = BuildpackReference.of("urn:cnb:builder:paketo-buildpacks/spring-boot");
Buildpack buildpack = BuilderBuildpack.resolve(this.resolverContext, reference);
assertThat(buildpack.getCoordinates())
.isEqualTo(BuildpackCoordinates.of("paketo-buildpacks/spring-boot", "3.5.0"));
assertThatNoLayersAreAdded(buildpack);
}
@Test
void resolveWhenUnqualifiedBuildpackWithVersionResolves() throws Exception {
BuildpackReference reference = BuildpackReference.of("paketo-buildpacks/spring-boot@3.5.0");
Buildpack buildpack = BuilderBuildpack.resolve(this.resolverContext, reference);
assertThat(buildpack.getCoordinates())
.isEqualTo(BuildpackCoordinates.of("paketo-buildpacks/spring-boot", "3.5.0"));
assertThatNoLayersAreAdded(buildpack);
}
@Test
void resolveWhenUnqualifiedBuildpackWithoutVersionResolves() throws Exception {
BuildpackReference reference = BuildpackReference.of("paketo-buildpacks/spring-boot");
Buildpack buildpack = BuilderBuildpack.resolve(this.resolverContext, reference);
assertThat(buildpack.getCoordinates())
.isEqualTo(BuildpackCoordinates.of("paketo-buildpacks/spring-boot", "3.5.0"));
assertThatNoLayersAreAdded(buildpack);
}
@Test
void resolveWhenFullyQualifiedBuildpackWithVersionNotInBuilderThrowsException() {
BuildpackReference reference = BuildpackReference.of("urn:cnb:builder:example/buildpack1@1.2.3");
assertThatIllegalArgumentException().isThrownBy(() -> BuilderBuildpack.resolve(this.resolverContext, reference))
.withMessageContaining("'urn:cnb:builder:example/buildpack1@1.2.3'")
.withMessageContaining("not found in builder");
}
@Test
void resolveWhenFullyQualifiedBuildpackWithoutVersionNotInBuilderThrowsException() {
BuildpackReference reference = BuildpackReference.of("urn:cnb:builder:example/buildpack1");
assertThatIllegalArgumentException().isThrownBy(() -> BuilderBuildpack.resolve(this.resolverContext, reference))
.withMessageContaining("'urn:cnb:builder:example/buildpack1'")
.withMessageContaining("not found in builder");
}
@Test
void resolveWhenUnqualifiedBuildpackNotInBuilderReturnsNull() {
BuildpackReference reference = BuildpackReference.of("example/buildpack1@1.2.3");
Buildpack buildpack = BuilderBuildpack.resolve(this.resolverContext, reference);
assertThat(buildpack).isNull();
}
private void assertThatNoLayersAreAdded(Buildpack buildpack) throws IOException {
List<Layer> layers = new ArrayList<>();
buildpack.apply(layers::add);
assertThat(layers).isEmpty();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
@@ -16,12 +16,8 @@
package org.springframework.boot.buildpack.platform.build;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
@@ -31,6 +27,7 @@ import org.springframework.boot.buildpack.platform.json.AbstractJsonTests;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.tuple;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
@@ -55,6 +52,14 @@ class BuilderMetadataTests extends AbstractJsonTests {
assertThat(metadata.getCreatedBy().getName()).isEqualTo("Pack CLI");
assertThat(metadata.getCreatedBy().getVersion())
.isEqualTo("v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)");
assertThat(metadata.getBuildpacks()).extracting(BuildpackMetadata::getId, BuildpackMetadata::getVersion)
.contains(tuple("paketo-buildpacks/java", "4.10.0"))
.contains(tuple("paketo-buildpacks/spring-boot", "3.5.0"))
.contains(tuple("paketo-buildpacks/executable-jar", "3.1.3"))
.contains(tuple("paketo-buildpacks/graalvm", "4.1.0"))
.contains(tuple("paketo-buildpacks/java-native-image", "4.7.0"))
.contains(tuple("paketo-buildpacks/spring-boot-native-image", "2.0.1"))
.contains(tuple("paketo-buildpacks/bellsoft-liberica", "6.2.0"));
}
@Test
@@ -124,9 +129,4 @@ class BuilderMetadataTests extends AbstractJsonTests {
.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"));
}
}

View File

@@ -320,10 +320,8 @@ class BuilderTests {
.willAnswer(withPulledImage(builderImage));
Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration);
BuildRequest request = getTestRequest();
assertThatIllegalStateException().isThrownBy(() -> builder.build(request))
.withMessageContaining(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)
.withMessageContaining("example.com/custom/run:latest")
.withMessageContaining("must be pulled from the same authenticated registry");
assertThatIllegalStateException().isThrownBy(() -> builder.build(request)).withMessage(
"Run image 'example.com/custom/run:latest' must be pulled from the 'docker.io' authenticated registry");
}
@Test
@@ -338,10 +336,26 @@ class BuilderTests {
.willAnswer(withPulledImage(builderImage));
Builder builder = new Builder(BuildLog.to(out), docker, dockerConfiguration);
BuildRequest request = getTestRequest().withRunImage(ImageReference.of("example.com/custom/run:latest"));
assertThatIllegalStateException().isThrownBy(() -> builder.build(request))
.withMessageContaining(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)
.withMessageContaining("example.com/custom/run:latest")
.withMessageContaining("must be pulled from the same authenticated registry");
assertThatIllegalStateException().isThrownBy(() -> builder.build(request)).withMessage(
"Run image 'example.com/custom/run:latest' must be pulled from the 'docker.io' authenticated registry");
}
@Test
void buildWhenRequestedBuildpackNotInBuilderThrowsException() throws Exception {
TestPrintStream out = new TestPrintStream();
DockerApi docker = mockDockerApiLifecycleError();
Image builderImage = loadImage("image.json");
Image runImage = loadImage("run-image.json");
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any(), isNull()))
.willAnswer(withPulledImage(builderImage));
given(docker.image().pull(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb")), any(), isNull()))
.willAnswer(withPulledImage(runImage));
Builder builder = new Builder(BuildLog.to(out), docker, null);
BuildpackReference reference = BuildpackReference.of("urn:cnb:builder:example/buildpack@1.2.3");
BuildRequest request = getTestRequest().withBuildpacks(reference);
assertThatIllegalArgumentException().isThrownBy(() -> builder.build(request))
.withMessageContaining("'urn:cnb:builder:example/buildpack@1.2.3'")
.withMessageContaining("not found in builder");
}
private DockerApi mockDockerApi() throws IOException {
@@ -349,15 +363,12 @@ class BuilderTests {
ContainerReference reference = ContainerReference.of("container-ref");
given(containerApi.create(any(), any())).willReturn(reference);
given(containerApi.wait(eq(reference))).willReturn(ContainerStatus.of(0, null));
ImageApi imageApi = mock(ImageApi.class);
VolumeApi volumeApi = mock(VolumeApi.class);
DockerApi docker = mock(DockerApi.class);
given(docker.image()).willReturn(imageApi);
given(docker.container()).willReturn(containerApi);
given(docker.volume()).willReturn(volumeApi);
return docker;
}
@@ -366,15 +377,12 @@ class BuilderTests {
ContainerReference reference = ContainerReference.of("container-ref");
given(containerApi.create(any(), any())).willReturn(reference);
given(containerApi.wait(eq(reference))).willReturn(ContainerStatus.of(9, null));
ImageApi imageApi = mock(ImageApi.class);
VolumeApi volumeApi = mock(VolumeApi.class);
DockerApi docker = mock(DockerApi.class);
given(docker.image()).willReturn(imageApi);
given(docker.container()).willReturn(containerApi);
given(docker.volume()).willReturn(volumeApi);
return docker;
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright 2012-2021 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.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.jupiter.api.Test;
import org.springframework.boot.buildpack.platform.json.AbstractJsonTests;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link BuildpackCoordinates}.
*
* @author Scott Frederick
* @author Phillip Webb
*/
class BuildpackCoordinatesTests extends AbstractJsonTests {
private final Path archive = Paths.get("/buildpack/path");
@Test
void fromToml() throws IOException {
BuildpackCoordinates coordinates = BuildpackCoordinates
.fromToml(createTomlStream("example/buildpack1", "0.0.1", true, false), this.archive);
assertThat(coordinates.getId()).isEqualTo("example/buildpack1");
assertThat(coordinates.getVersion()).isEqualTo("0.0.1");
}
@Test
void fromTomlWhenMissingDescriptorThrowsException() throws Exception {
ByteArrayInputStream coordinates = new ByteArrayInputStream("".getBytes());
assertThatIllegalArgumentException().isThrownBy(() -> BuildpackCoordinates.fromToml(coordinates, this.archive))
.withMessageContaining("Buildpack descriptor 'buildpack.toml' is required")
.withMessageContaining(this.archive.toString());
}
@Test
void fromTomlWhenMissingIDThrowsException() throws Exception {
InputStream coordinates = createTomlStream(null, null, true, false);
assertThatIllegalArgumentException().isThrownBy(() -> BuildpackCoordinates.fromToml(coordinates, this.archive))
.withMessageContaining("Buildpack descriptor must contain ID")
.withMessageContaining(this.archive.toString());
}
@Test
void fromTomlWhenMissingVersionThrowsException() throws Exception {
InputStream coordinates = createTomlStream("example/buildpack1", null, true, false);
assertThatIllegalArgumentException().isThrownBy(() -> BuildpackCoordinates.fromToml(coordinates, this.archive))
.withMessageContaining("Buildpack descriptor must contain version")
.withMessageContaining(this.archive.toString());
}
@Test
void fromTomlWhenMissingStacksAndOrderThrowsException() throws Exception {
InputStream coordinates = createTomlStream("example/buildpack1", "0.0.1", false, false);
assertThatIllegalArgumentException().isThrownBy(() -> BuildpackCoordinates.fromToml(coordinates, this.archive))
.withMessageContaining("Buildpack descriptor must contain either 'stacks' or 'order'")
.withMessageContaining(this.archive.toString());
}
@Test
void fromTomlWhenContainsBothStacksAndOrderThrowsException() throws Exception {
InputStream coordinates = createTomlStream("example/buildpack1", "0.0.1", true, true);
assertThatIllegalArgumentException().isThrownBy(() -> BuildpackCoordinates.fromToml(coordinates, this.archive))
.withMessageContaining("Buildpack descriptor must not contain both 'stacks' and 'order'")
.withMessageContaining(this.archive.toString());
}
@Test
void fromBuildpackMetadataWhenMetadataIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> BuildpackCoordinates.fromBuildpackMetadata(null))
.withMessage("BuildpackMetadata must not be null");
}
@Test
void fromBuildpackMetadataReturnsCoordinates() throws Exception {
BuildpackMetadata metadata = BuildpackMetadata.fromJson(getContentAsString("buildpack-metadata.json"));
BuildpackCoordinates coordinates = BuildpackCoordinates.fromBuildpackMetadata(metadata);
assertThat(coordinates.getId()).isEqualTo("example/hello-universe");
assertThat(coordinates.getVersion()).isEqualTo("0.0.1");
}
@Test
void ofWhenIdIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> BuildpackCoordinates.of(null, null))
.withMessage("ID must not be empty");
}
@Test
void ofReturnsCoordinates() {
BuildpackCoordinates coordinates = BuildpackCoordinates.of("id", "1");
assertThat(coordinates).hasToString("id@1");
}
@Test
void getIdReturnsId() {
BuildpackCoordinates coordinates = BuildpackCoordinates.of("id", "1");
assertThat(coordinates.getId()).isEqualTo("id");
}
@Test
void getVersionReturnsVersion() {
BuildpackCoordinates coordinates = BuildpackCoordinates.of("id", "1");
assertThat(coordinates.getVersion()).isEqualTo("1");
}
@Test
void getVersionWhenVersionIsNullReturnsNull() {
BuildpackCoordinates coordinates = BuildpackCoordinates.of("id", null);
assertThat(coordinates.getVersion()).isNull();
}
@Test
void toStringReturnsNiceString() {
BuildpackCoordinates coordinates = BuildpackCoordinates.of("id", "1");
assertThat(coordinates).hasToString("id@1");
}
@Test
void equalsAndHashCode() {
BuildpackCoordinates c1a = BuildpackCoordinates.of("id", "1");
BuildpackCoordinates c1b = BuildpackCoordinates.of("id", "1");
BuildpackCoordinates c2 = BuildpackCoordinates.of("id", "2");
assertThat(c1a).isEqualTo(c1a).isEqualTo(c1b).isNotEqualTo(c2);
assertThat(c1a.hashCode()).isEqualTo(c1b.hashCode());
}
private InputStream createTomlStream(String id, String version, boolean includeStacks, boolean includeOrder) {
StringBuilder builder = new StringBuilder();
builder.append("[buildpack]\n");
if (id != null) {
builder.append("id = \"").append(id).append("\"\n");
}
if (version != null) {
builder.append("version = \"").append(version).append("\"\n");
}
builder.append("name = \"Example buildpack\"\n");
builder.append("homepage = \"https://github.com/example/example-buildpack\"\n");
if (includeStacks) {
builder.append("[[stacks]]\n");
builder.append("id = \"io.buildpacks.stacks.bionic\"\n");
}
if (includeOrder) {
builder.append("[[order]]\n");
builder.append("group = [ { id = \"example/buildpack2\", version=\"0.0.2\" } ]\n");
}
return new ByteArrayInputStream(builder.toString().getBytes(StandardCharsets.UTF_8));
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2012-2021 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.io.IOException;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImageConfig;
import org.springframework.boot.buildpack.platform.json.AbstractJsonTests;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link BuildpackMetadata}.
*
* @author Scott Frederick
*/
class BuildpackMetadataTests extends AbstractJsonTests {
@Test
void fromImageLoadsMetadata() throws IOException {
Image image = Image.of(getContent("buildpack-image.json"));
BuildpackMetadata metadata = BuildpackMetadata.fromImage(image);
assertThat(metadata.getId()).isEqualTo("example/hello-universe");
assertThat(metadata.getVersion()).isEqualTo("0.0.1");
}
@Test
void fromImageWhenImageIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> BuildpackMetadata.fromImage(null))
.withMessage("Image must not be null");
}
@Test
void fromImageWhenImageConfigIsNullThrowsException() {
Image image = mock(Image.class);
assertThatIllegalArgumentException().isThrownBy(() -> BuildpackMetadata.fromImage(image))
.withMessage("ImageConfig must not be null");
}
@Test
void fromImageConfigWhenLabelIsMissingThrowsException() {
Image image = mock(Image.class);
ImageConfig imageConfig = mock(ImageConfig.class);
given(image.getConfig()).willReturn(imageConfig);
given(imageConfig.getLabels()).willReturn(Collections.singletonMap("alpha", "a"));
assertThatIllegalArgumentException().isThrownBy(() -> BuildpackMetadata.fromImage(image))
.withMessage("No 'io.buildpacks.buildpackage.metadata' label found in image config labels 'alpha'");
}
@Test
void fromJsonLoadsMetadata() throws IOException {
BuildpackMetadata metadata = BuildpackMetadata.fromJson(getContentAsString("buildpack-metadata.json"));
assertThat(metadata.getId()).isEqualTo("example/hello-universe");
assertThat(metadata.getVersion()).isEqualTo("0.0.1");
assertThat(metadata.getHomepage()).isEqualTo("https://github.com/example/tree/main/buildpacks/hello-universe");
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2012-2021 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.nio.file.Paths;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link BuildpackReference}.
*
* @author Phillip Webb
*/
class BuildpackReferenceTests {
@Test
void ofWhenValueIsEmptyThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> BuildpackReference.of(""))
.withMessage("Value must not be empty");
}
@Test
void ofCreatesInstance() {
BuildpackReference reference = BuildpackReference.of("test");
assertThat(reference).isNotNull();
}
@Test
void toStringReturnsValue() {
BuildpackReference reference = BuildpackReference.of("test");
assertThat(reference).hasToString("test");
}
@Test
void equalsAndHashCode() {
BuildpackReference a = BuildpackReference.of("test1");
BuildpackReference b = BuildpackReference.of("test1");
BuildpackReference c = BuildpackReference.of("test2");
assertThat(a).isEqualTo(a).isEqualTo(b).isNotEqualTo(c);
assertThat(a.hashCode()).isEqualTo(b.hashCode());
}
@Test
void hasPrefixWhenPrefixMatchReturnsTrue() {
BuildpackReference reference = BuildpackReference.of("test");
assertThat(reference.hasPrefix("te")).isTrue();
}
@Test
void hasPrefixWhenPrifixMismatchReturnsFalse() {
BuildpackReference reference = BuildpackReference.of("test");
assertThat(reference.hasPrefix("st")).isFalse();
}
@Test
void getSubReferenceWhenPrefixMatchReturnsSubReference() {
BuildpackReference reference = BuildpackReference.of("test");
assertThat(reference.getSubReference("te")).isEqualTo("st");
}
@Test
void getSubReferenceWhenPrefixMismatchReturnsNull() {
BuildpackReference reference = BuildpackReference.of("test");
assertThat(reference.getSubReference("st")).isNull();
}
@Test
void asPathWhenFileUrlReturnsPath() {
BuildpackReference reference = BuildpackReference.of("file:///test.dat");
assertThat(reference.asPath()).isEqualTo(Paths.get("/test.dat"));
}
@Test
void asPathWhenPathReturnsPath() {
BuildpackReference reference = BuildpackReference.of("/test.dat");
assertThat(reference.asPath()).isEqualTo(Paths.get("/test.dat"));
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2012-2021 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.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.json.AbstractJsonTests;
import org.springframework.util.FileCopyUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link BuildpackResolvers}.
*
* @author Scott Frederick
*/
class BuildpackResolversTests extends AbstractJsonTests {
private BuildpackResolverContext resolverContext;
@BeforeEach
void setup() throws Exception {
BuilderMetadata metadata = BuilderMetadata.fromJson(getContentAsString("builder-metadata.json"));
this.resolverContext = mock(BuildpackResolverContext.class);
given(this.resolverContext.getBuildpackMetadata()).willReturn(metadata.getBuildpacks());
}
@Test
void resolveAllWithBuilderBuildpackReferenceReturnsExpectedBuildpack() throws IOException {
BuildpackReference reference = BuildpackReference.of("urn:cnb:builder:paketo-buildpacks/spring-boot@3.5.0");
Buildpacks buildpacks = BuildpackResolvers.resolveAll(this.resolverContext, Collections.singleton(reference));
assertThat(buildpacks.getBuildpacks()).hasSize(1);
assertThat(buildpacks.getBuildpacks().get(0)).isInstanceOf(BuilderBuildpack.class);
}
@Test
void resolveAllWithDirectoryBuildpackReferenceReturnsExpectedBuildpack(@TempDir Path temp) throws IOException {
FileCopyUtils.copy(getClass().getResourceAsStream("buildpack.toml"),
Files.newOutputStream(temp.resolve("buildpack.toml")));
BuildpackReference reference = BuildpackReference.of(temp.toAbsolutePath().toString());
Buildpacks buildpacks = BuildpackResolvers.resolveAll(this.resolverContext, Collections.singleton(reference));
assertThat(buildpacks.getBuildpacks()).hasSize(1);
assertThat(buildpacks.getBuildpacks().get(0)).isInstanceOf(DirectoryBuildpack.class);
}
@Test
void resolveAllWithTarGzipBuildpackReferenceReturnsExpectedBuildpack(@TempDir File temp) throws Exception {
TestTarGzip testTarGzip = new TestTarGzip(temp);
Path archive = testTarGzip.createArchive();
BuildpackReference reference = BuildpackReference.of(archive.toString());
Buildpacks buildpacks = BuildpackResolvers.resolveAll(this.resolverContext, Collections.singleton(reference));
assertThat(buildpacks.getBuildpacks()).hasSize(1);
assertThat(buildpacks.getBuildpacks().get(0)).isInstanceOf(TarGzipBuildpack.class);
}
@Test
void resolveAllWithImageBuildpackReferenceReturnsExpectedBuildpack() throws IOException {
Image image = Image.of(getContent("buildpack-image.json"));
BuildpackResolverContext resolverContext = mock(BuildpackResolverContext.class);
given(resolverContext.fetchImage(any(), any())).willReturn(image);
BuildpackReference reference = BuildpackReference.of("docker://example/buildpack1:latest");
Buildpacks buildpacks = BuildpackResolvers.resolveAll(resolverContext, Collections.singleton(reference));
assertThat(buildpacks.getBuildpacks()).hasSize(1);
assertThat(buildpacks.getBuildpacks().get(0)).isInstanceOf(ImageBuildpack.class);
}
@Test
void resolveAllWithInvalidLocatorThrowsException() throws IOException {
BuildpackReference reference = BuildpackReference.of("unknown-buildpack@0.0.1");
assertThatIllegalArgumentException()
.isThrownBy(() -> BuildpackResolvers.resolveAll(this.resolverContext, Collections.singleton(reference)))
.withMessageContaining("Invalid buildpack reference")
.withMessageContaining("'unknown-buildpack@0.0.1'");
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2012-2021 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.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.junit.jupiter.api.Test;
import org.springframework.boot.buildpack.platform.docker.type.Layer;
import org.springframework.util.StreamUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link Buildpacks}.
*
* @author Scott Frederick
* @author Phillip Webb
*/
class BuildpacksTests {
@Test
void ofWhenBuildpacksIsNullReturnsEmpty() {
Buildpacks buildpacks = Buildpacks.of(null);
assertThat(buildpacks).isSameAs(Buildpacks.EMPTY);
assertThat(buildpacks.getBuildpacks()).isEmpty();
}
@Test
void ofReturnsBuildpacks() {
List<Buildpack> buildpackList = new ArrayList<>();
buildpackList.add(new TestBuildpack("example/buildpack1", "0.0.1"));
buildpackList.add(new TestBuildpack("example/buildpack2", "0.0.2"));
Buildpacks buildpacks = Buildpacks.of(buildpackList);
assertThat(buildpacks.getBuildpacks()).isEqualTo(buildpackList);
}
@Test
void applyWritesLayersAndOrderLayer() throws Exception {
List<Buildpack> buildpackList = new ArrayList<>();
buildpackList.add(new TestBuildpack("example/buildpack1", "0.0.1"));
buildpackList.add(new TestBuildpack("example/buildpack2", "0.0.2"));
buildpackList.add(new TestBuildpack("example/buildpack3", null));
Buildpacks buildpacks = Buildpacks.of(buildpackList);
List<Layer> layers = new ArrayList<>();
buildpacks.apply(layers::add);
assertThat(layers).hasSize(4);
assertThatLayerContentIsCorrect(layers.get(0), "example_buildpack1/0.0.1");
assertThatLayerContentIsCorrect(layers.get(1), "example_buildpack2/0.0.2");
assertThatLayerContentIsCorrect(layers.get(2), "example_buildpack3/null");
assertThatOrderLayerContentIsCorrect(layers.get(3));
}
private void assertThatLayerContentIsCorrect(Layer layer, String path) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
layer.writeTo(out);
try (TarArchiveInputStream tar = new TarArchiveInputStream(new ByteArrayInputStream(out.toByteArray()))) {
assertThat(tar.getNextEntry().getName()).isEqualTo("/cnb/buildpacks/" + path + "/buildpack.toml");
assertThat(tar.getNextEntry()).isNull();
}
}
private void assertThatOrderLayerContentIsCorrect(Layer layer) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
layer.writeTo(out);
try (TarArchiveInputStream tar = new TarArchiveInputStream(new ByteArrayInputStream(out.toByteArray()))) {
assertThat(tar.getNextEntry().getName()).isEqualTo("/cnb/order.toml");
byte[] content = StreamUtils.copyToByteArray(tar);
String toml = new String(content, StandardCharsets.UTF_8);
assertThat(toml).isEqualTo(getExpectedToml());
}
}
private String getExpectedToml() {
StringBuilder toml = new StringBuilder();
toml.append("[[order]]\n");
toml.append("group = [\n");
toml.append(" { id = \"example/buildpack1\", version = \"0.0.1\" }\n");
toml.append("]\n\n");
toml.append("[[order]]\n");
toml.append("group = [\n");
toml.append(" { id = \"example/buildpack2\", version = \"0.0.2\" }\n");
toml.append("]\n\n");
toml.append("[[order]]\n");
toml.append("group = [\n");
toml.append(" { id = \"example/buildpack3\" }\n");
toml.append("]\n\n");
return toml.toString();
}
}

View File

@@ -0,0 +1,174 @@
/*
* Copyright 2012-2021 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.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.io.TempDir;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.tuple;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link DirectoryBuildpack}.
*
* @author Scott Frederick
*/
@DisabledOnOs(OS.WINDOWS)
class DirectoryBuildpackTests {
@TempDir
File temp;
private File buildpackDir;
private BuildpackResolverContext resolverContext;
@BeforeEach
void setUp() {
this.buildpackDir = new File(this.temp, "buildpack");
this.buildpackDir.mkdirs();
this.resolverContext = mock(BuildpackResolverContext.class);
}
@Test
void resolveWhenPath() throws Exception {
writeBuildpackDescriptor();
writeScripts();
BuildpackReference reference = BuildpackReference.of(this.buildpackDir.toString());
Buildpack buildpack = DirectoryBuildpack.resolve(this.resolverContext, reference);
assertThat(buildpack).isNotNull();
assertThat(buildpack.getCoordinates()).hasToString("example/buildpack1@0.0.1");
assertHasExpectedLayers(buildpack);
}
@Test
void resolveWhenFileUrl() throws Exception {
writeBuildpackDescriptor();
writeScripts();
BuildpackReference reference = BuildpackReference.of("file://" + this.buildpackDir.toString());
Buildpack buildpack = DirectoryBuildpack.resolve(this.resolverContext, reference);
assertThat(buildpack).isNotNull();
assertThat(buildpack.getCoordinates()).hasToString("example/buildpack1@0.0.1");
assertHasExpectedLayers(buildpack);
}
@Test
void resolveWhenDirectoryWithoutBuildpackTomlThrowsException() throws Exception {
Files.createDirectories(this.buildpackDir.toPath());
BuildpackReference reference = BuildpackReference.of(this.buildpackDir.toString());
assertThatIllegalArgumentException()
.isThrownBy(() -> DirectoryBuildpack.resolve(this.resolverContext, reference))
.withMessageContaining("Buildpack descriptor 'buildpack.toml' is required")
.withMessageContaining(this.buildpackDir.getAbsolutePath());
}
@Test
void resolveWhenFileReturnsNull() throws Exception {
Path file = Files.createFile(Paths.get(this.buildpackDir.toString(), "test"));
BuildpackReference reference = BuildpackReference.of(file.toString());
Buildpack buildpack = DirectoryBuildpack.resolve(this.resolverContext, reference);
assertThat(buildpack).isNull();
}
@Test
void resolveWhenDirectoryDoesNotExistReturnsNull() {
BuildpackReference reference = BuildpackReference.of("/test/a/missing/buildpack");
Buildpack buildpack = DirectoryBuildpack.resolve(this.resolverContext, reference);
assertThat(buildpack).isNull();
}
@Test
void locateDirectoryAsUrlThatDoesNotExistThrowsException() {
BuildpackReference reference = BuildpackReference.of("file:///test/a/missing/buildpack");
Buildpack buildpack = DirectoryBuildpack.resolve(this.resolverContext, reference);
assertThat(buildpack).isNull();
}
private void assertHasExpectedLayers(Buildpack buildpack) throws IOException {
List<ByteArrayOutputStream> layers = new ArrayList<>();
buildpack.apply((layer) -> {
ByteArrayOutputStream out = new ByteArrayOutputStream();
layer.writeTo(out);
layers.add(out);
});
assertThat(layers).hasSize(1);
byte[] content = layers.get(0).toByteArray();
try (TarArchiveInputStream tar = new TarArchiveInputStream(new ByteArrayInputStream(content))) {
List<TarArchiveEntry> entries = new ArrayList<>();
TarArchiveEntry entry = tar.getNextTarEntry();
while (entry != null) {
entries.add(entry);
entry = tar.getNextTarEntry();
}
assertThat(entries).extracting("name", "mode").containsExactlyInAnyOrder(
tuple("/cnb/buildpacks/example_buildpack1/0.0.1/buildpack.toml", 0644),
tuple("/cnb/buildpacks/example_buildpack1/0.0.1/bin/detect", 0744),
tuple("/cnb/buildpacks/example_buildpack1/0.0.1/bin/build", 0744));
}
}
private void writeBuildpackDescriptor() throws IOException {
File descriptor = new File(this.buildpackDir, "buildpack.toml");
try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(descriptor.toPath()))) {
writer.println("[buildpack]");
writer.println("id = \"example/buildpack1\"");
writer.println("version = \"0.0.1\"");
writer.println("name = \"Example buildpack\"");
writer.println("homepage = \"https://github.com/example/example-buildpack\"");
writer.println("[[stacks]]");
writer.println("id = \"io.buildpacks.stacks.bionic\"");
}
}
private void writeScripts() throws IOException {
File binDirectory = new File(this.buildpackDir, "bin");
binDirectory.mkdirs();
Path detect = Files.createFile(Paths.get(binDirectory.getAbsolutePath(), "detect"),
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxr--r--")));
try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(detect))) {
writer.println("#!/usr/bin/env bash");
writer.println("echo \"---> detect\"");
}
Path build = Files.createFile(Paths.get(binDirectory.getAbsolutePath(), "build"),
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwxr--r--")));
try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(build))) {
writer.println("#!/usr/bin/env bash");
writer.println("echo \"---> build\"");
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
@@ -20,12 +20,17 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.compress.archivers.ArchiveEntry;
@@ -40,6 +45,7 @@ import org.springframework.boot.buildpack.platform.docker.type.ImageArchive;
import org.springframework.boot.buildpack.platform.docker.type.ImageConfig;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.json.AbstractJsonTests;
import org.springframework.util.FileCopyUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -62,7 +68,9 @@ class EphemeralBuilderTests extends AbstractJsonTests {
private Map<String, String> env;
private Creator creator = Creator.withVersion("dev");
private Buildpacks buildpacks;
private final Creator creator = Creator.withVersion("dev");
@BeforeEach
void setup() throws Exception {
@@ -75,15 +83,18 @@ class EphemeralBuilderTests extends AbstractJsonTests {
@Test
void getNameHasRandomName() throws Exception {
EphemeralBuilder b1 = new EphemeralBuilder(this.owner, this.image, this.metadata, this.creator, this.env);
EphemeralBuilder b2 = new EphemeralBuilder(this.owner, this.image, this.metadata, this.creator, this.env);
EphemeralBuilder b1 = new EphemeralBuilder(this.owner, this.image, this.metadata, this.creator, this.env,
this.buildpacks);
EphemeralBuilder b2 = new EphemeralBuilder(this.owner, this.image, this.metadata, this.creator, this.env,
this.buildpacks);
assertThat(b1.getName().toString()).startsWith("pack.local/builder/").endsWith(":latest");
assertThat(b1.getName().toString()).isNotEqualTo(b2.getName().toString());
}
@Test
void getArchiveHasCreatedByConfig() throws Exception {
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.metadata, this.creator, this.env);
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.metadata, this.creator, this.env,
this.buildpacks);
ImageConfig config = builder.getArchive().getImageConfig();
BuilderMetadata ephemeralMetadata = BuilderMetadata.fromImageConfig(config);
assertThat(ephemeralMetadata.getCreatedBy().getName()).isEqualTo("Spring Boot");
@@ -92,14 +103,16 @@ class EphemeralBuilderTests extends AbstractJsonTests {
@Test
void getArchiveHasTag() throws Exception {
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.metadata, this.creator, this.env);
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.metadata, this.creator, this.env,
this.buildpacks);
ImageReference tag = builder.getArchive().getTag();
assertThat(tag.toString()).startsWith("pack.local/builder/").endsWith(":latest");
}
@Test
void getArchiveHasFixedCreateDate() throws Exception {
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.metadata, this.creator, this.env);
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.metadata, this.creator, this.env,
this.buildpacks);
Instant createInstant = builder.getArchive().getCreateDate();
OffsetDateTime createDateTime = OffsetDateTime.ofInstant(createInstant, ZoneId.of("UTC"));
assertThat(createDateTime.getYear()).isEqualTo(1980);
@@ -112,12 +125,35 @@ class EphemeralBuilderTests extends AbstractJsonTests {
@Test
void getArchiveContainsEnvLayer() throws Exception {
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.metadata, this.creator, this.env);
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.metadata, this.creator, this.env,
this.buildpacks);
File directory = unpack(getLayer(builder.getArchive(), 0), "env");
assertThat(new File(directory, "platform/env/spring")).usingCharset(StandardCharsets.UTF_8).hasContent("boot");
assertThat(new File(directory, "platform/env/empty")).usingCharset(StandardCharsets.UTF_8).hasContent("");
}
@Test
void getArchiveContainsBuildpackLayers() throws Exception {
List<Buildpack> buildpackList = new ArrayList<>();
buildpackList.add(new TestBuildpack("example/buildpack1", "0.0.1"));
buildpackList.add(new TestBuildpack("example/buildpack2", "0.0.2"));
buildpackList.add(new TestBuildpack("example/buildpack3", "0.0.3"));
this.buildpacks = Buildpacks.of(buildpackList);
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.metadata, this.creator, null,
this.buildpacks);
assertBuildpackLayerContent(builder, 0, "/cnb/buildpacks/example_buildpack1/0.0.1/buildpack.toml");
assertBuildpackLayerContent(builder, 1, "/cnb/buildpacks/example_buildpack2/0.0.2/buildpack.toml");
assertBuildpackLayerContent(builder, 2, "/cnb/buildpacks/example_buildpack3/0.0.3/buildpack.toml");
File orderDirectory = unpack(getLayer(builder.getArchive(), 3), "order");
assertThat(new File(orderDirectory, "cnb/order.toml")).usingCharset(StandardCharsets.UTF_8)
.hasContent(content("order-versions.toml"));
}
private void assertBuildpackLayerContent(EphemeralBuilder builder, int index, String s) throws Exception {
File buildpackDirectory = unpack(getLayer(builder.getArchive(), index), "buildpack");
assertThat(new File(buildpackDirectory, s)).usingCharset(StandardCharsets.UTF_8).hasContent("[test]");
}
private TarArchiveInputStream getLayer(ImageArchive archive, int index) throws Exception {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
archive.writeTo(outputStream);
@@ -148,4 +184,9 @@ class EphemeralBuilderTests extends AbstractJsonTests {
return directory;
}
private String content(String fileName) throws IOException {
InputStream in = getClass().getResourceAsStream(fileName);
return FileCopyUtils.copyToString(new InputStreamReader(in, StandardCharsets.UTF_8));
}
}

View File

@@ -0,0 +1,138 @@
/*
* Copyright 2012-2021 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.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.junit.jupiter.api.Test;
import org.mockito.invocation.InvocationOnMock;
import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.io.IOBiConsumer;
import org.springframework.boot.buildpack.platform.io.TarArchive;
import org.springframework.boot.buildpack.platform.json.AbstractJsonTests;
import org.springframework.util.FileCopyUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ImageBuildpack}.
*
* @author Scott Frederick
* @author Phillip Webb
*/
class ImageBuildpackTests extends AbstractJsonTests {
@Test
void resolveWhenFullyQualifiedReferenceReturnsBuilder() throws Exception {
Image image = Image.of(getContent("buildpack-image.json"));
BuildpackResolverContext resolverContext = mock(BuildpackResolverContext.class);
given(resolverContext.fetchImage(any(), any())).willReturn(image);
willAnswer(this::withMockLayers).given(resolverContext).exportImageLayers(any(), any());
BuildpackReference reference = BuildpackReference.of("docker://example/buildpack1:latest");
Buildpack buildpack = ImageBuildpack.resolve(resolverContext, reference);
assertThat(buildpack.getCoordinates()).hasToString("example/hello-universe@0.0.1");
assertHasExpectedLayers(buildpack);
}
@Test
void resolveWhenUnqualifiedReferenceReturnsBuilder() throws Exception {
Image image = Image.of(getContent("buildpack-image.json"));
BuildpackResolverContext resolverContext = mock(BuildpackResolverContext.class);
given(resolverContext.fetchImage(any(), any())).willReturn(image);
willAnswer(this::withMockLayers).given(resolverContext).exportImageLayers(any(), any());
BuildpackReference reference = BuildpackReference.of("example/buildpack1:latest");
Buildpack buildpack = ImageBuildpack.resolve(resolverContext, reference);
assertThat(buildpack.getCoordinates()).hasToString("example/hello-universe@0.0.1");
assertHasExpectedLayers(buildpack);
}
@Test
void resolveWhenWhenImageNotPulledThrowsException() throws Exception {
BuildpackResolverContext resolverContext = mock(BuildpackResolverContext.class);
given(resolverContext.fetchImage(any(), any())).willThrow(IOException.class);
BuildpackReference reference = BuildpackReference.of("docker://example/buildpack1:latest");
assertThatIllegalArgumentException().isThrownBy(() -> ImageBuildpack.resolve(resolverContext, reference))
.withMessageContaining("Error pulling buildpack image")
.withMessageContaining("example/buildpack1:latest");
}
@Test
void resolveWhenMissingMetadataLabelThrowsException() throws Exception {
Image image = Image.of(getContent("image.json"));
BuildpackResolverContext resolverContext = mock(BuildpackResolverContext.class);
given(resolverContext.fetchImage(any(), any())).willReturn(image);
BuildpackReference reference = BuildpackReference.of("docker://example/buildpack1:latest");
assertThatIllegalArgumentException().isThrownBy(() -> ImageBuildpack.resolve(resolverContext, reference))
.withMessageContaining("No 'io.buildpacks.buildpackage.metadata' label found");
}
@Test
void resolveWhenFullyQualifiedReferenceWithInvalidImageReferenceThrowsException() throws Exception {
BuildpackReference reference = BuildpackReference.of("docker://buildpack@0.0.1");
BuildpackResolverContext resolverContext = mock(BuildpackResolverContext.class);
assertThatIllegalArgumentException().isThrownBy(() -> ImageBuildpack.resolve(resolverContext, reference))
.withMessageContaining("Unable to parse image reference \"buildpack@0.0.1\"");
}
@Test
void resolveWhenUnqualifiedReferenceWithInvalidImageReferenceReturnsNull() throws Exception {
BuildpackReference reference = BuildpackReference.of("buildpack@0.0.1");
BuildpackResolverContext resolverContext = mock(BuildpackResolverContext.class);
Buildpack buildpack = ImageBuildpack.resolve(resolverContext, reference);
assertThat(buildpack).isNull();
}
private Object withMockLayers(InvocationOnMock invocation) throws Exception {
IOBiConsumer<String, TarArchive> consumer = invocation.getArgument(1);
TarArchive archive = (out) -> FileCopyUtils.copy(getClass().getResourceAsStream("layer.tar"), out);
consumer.accept("test", archive);
return null;
}
private void assertHasExpectedLayers(Buildpack buildpack) throws IOException {
List<ByteArrayOutputStream> layers = new ArrayList<>();
buildpack.apply((layer) -> {
ByteArrayOutputStream out = new ByteArrayOutputStream();
layer.writeTo(out);
layers.add(out);
});
assertThat(layers).hasSize(1);
byte[] content = layers.get(0).toByteArray();
List<String> names = new ArrayList<>();
try (TarArchiveInputStream tar = new TarArchiveInputStream(new ByteArrayInputStream(content))) {
TarArchiveEntry entry = tar.getNextTarEntry();
while (entry != null) {
names.add(entry.getName());
entry = tar.getNextTarEntry();
}
}
assertThat(names).containsExactly("etc/apt/sources.list");
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2012-2021 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.io.File;
import java.nio.file.Path;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link TarGzipBuildpack}.
*
* @author Scott Frederick
*/
class TarGzipBuildpackTests {
private File buildpackDir;
private TestTarGzip testTarGzip;
private BuildpackResolverContext resolverContext;
@BeforeEach
void setUp(@TempDir File temp) {
this.buildpackDir = new File(temp, "buildpack");
this.buildpackDir.mkdirs();
this.testTarGzip = new TestTarGzip(this.buildpackDir);
this.resolverContext = mock(BuildpackResolverContext.class);
}
@Test
void resolveWhenFilePathReturnsBuildpack() throws Exception {
Path compressedArchive = this.testTarGzip.createArchive();
BuildpackReference reference = BuildpackReference.of(compressedArchive.toString());
Buildpack buildpack = TarGzipBuildpack.resolve(this.resolverContext, reference);
assertThat(buildpack).isNotNull();
assertThat(buildpack.getCoordinates()).hasToString("example/buildpack1@0.0.1");
this.testTarGzip.assertHasExpectedLayers(buildpack);
}
@Test
void resolveWhenFileUrlReturnsBuildpack() throws Exception {
Path compressedArchive = this.testTarGzip.createArchive();
BuildpackReference reference = BuildpackReference.of("file://" + compressedArchive.toString());
Buildpack buildpack = TarGzipBuildpack.resolve(this.resolverContext, reference);
assertThat(buildpack).isNotNull();
assertThat(buildpack.getCoordinates()).hasToString("example/buildpack1@0.0.1");
this.testTarGzip.assertHasExpectedLayers(buildpack);
}
@Test
void resolveWhenArchiveWithoutDescriptorThrowsException() throws Exception {
Path compressedArchive = this.testTarGzip.createEmptyArchive();
BuildpackReference reference = BuildpackReference.of(compressedArchive.toString());
assertThatIllegalArgumentException().isThrownBy(() -> TarGzipBuildpack.resolve(this.resolverContext, reference))
.withMessageContaining("Buildpack descriptor 'buildpack.toml' is required")
.withMessageContaining(compressedArchive.toString());
}
@Test
void resolveWhenArchiveWithDirectoryReturnsNull() {
BuildpackReference reference = BuildpackReference.of(this.buildpackDir.getAbsolutePath());
Buildpack buildpack = TarGzipBuildpack.resolve(this.resolverContext, reference);
assertThat(buildpack).isNull();
}
@Test
void resolveWhenArchiveThatDoesNotExistReturnsNull() {
BuildpackReference reference = BuildpackReference.of("/test/i/am/missing/buildpack.tar");
Buildpack buildpack = TarGzipBuildpack.resolve(this.resolverContext, reference);
assertThat(buildpack).isNull();
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2012-2021 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.io.IOException;
import org.springframework.boot.buildpack.platform.docker.type.Layer;
import org.springframework.boot.buildpack.platform.io.Content;
import org.springframework.boot.buildpack.platform.io.IOConsumer;
import org.springframework.boot.buildpack.platform.io.Layout;
import org.springframework.boot.buildpack.platform.io.Owner;
/**
* A test {@link Buildpack}.
*
* @author Scott Frederick
* @author Phillip Webb
*/
class TestBuildpack implements Buildpack {
private final BuildpackCoordinates coordinates;
TestBuildpack(String id, String version) {
this.coordinates = BuildpackCoordinates.of(id, version);
}
@Override
public BuildpackCoordinates getCoordinates() {
return this.coordinates;
}
@Override
public void apply(IOConsumer<Layer> layers) throws IOException {
layers.accept(Layer.of(this::getContent));
}
private void getContent(Layout layout) throws IOException {
String id = this.coordinates.getSanitizedId();
String dir = "/cnb/buildpacks/" + id + "/" + this.coordinates.getVersion();
layout.file(dir + "/buildpack.toml", Owner.ROOT, Content.of("[test]"));
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright 2012-2021 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.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.apache.commons.compress.utils.IOUtils;
import org.springframework.util.FileCopyUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Utility to create test tgz files.
*
* @author Scott Frederick
*/
class TestTarGzip {
private final File buildpackDir;
TestTarGzip(File buildpackDir) {
this.buildpackDir = buildpackDir;
}
Path createArchive() throws Exception {
return createArchive(true);
}
Path createEmptyArchive() throws Exception {
return createArchive(false);
}
private Path createArchive(boolean addContent) throws Exception {
Path path = Paths.get(this.buildpackDir.getAbsolutePath(), "buildpack.tar");
Path archive = Files.createFile(path);
if (addContent) {
writeBuildpackContentToArchive(archive);
}
return compressBuildpackArchive(archive);
}
private Path compressBuildpackArchive(Path archive) throws Exception {
Path tgzPath = Paths.get(this.buildpackDir.getAbsolutePath(), "buildpack.tgz");
FileCopyUtils.copy(Files.newInputStream(archive),
new GzipCompressorOutputStream(Files.newOutputStream(tgzPath)));
return tgzPath;
}
private void writeBuildpackContentToArchive(Path archive) throws Exception {
StringBuilder buildpackToml = new StringBuilder();
buildpackToml.append("[buildpack]\n");
buildpackToml.append("id = \"example/buildpack1\"\n");
buildpackToml.append("version = \"0.0.1\"\n");
buildpackToml.append("name = \"Example buildpack\"\n");
buildpackToml.append("homepage = \"https://github.com/example/example-buildpack\"\n");
buildpackToml.append("[[stacks]]\n");
buildpackToml.append("id = \"io.buildpacks.stacks.bionic\"\n");
String detectScript = "#!/usr/bin/env bash\n" + "echo \"---> detect\"\n";
String buildScript = "#!/usr/bin/env bash\n" + "echo \"---> build\"\n";
try (TarArchiveOutputStream tar = new TarArchiveOutputStream(Files.newOutputStream(archive))) {
writeEntry(tar, "buildpack.toml", buildpackToml.toString());
writeEntry(tar, "bin/detect", detectScript);
writeEntry(tar, "bin/build", buildScript);
tar.finish();
}
}
private void writeEntry(TarArchiveOutputStream tar, String entryName, String content) throws IOException {
TarArchiveEntry entry = new TarArchiveEntry(entryName);
entry.setSize(content.length());
tar.putArchiveEntry(entry);
IOUtils.copy(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)), tar);
tar.closeArchiveEntry();
}
void assertHasExpectedLayers(Buildpack buildpack) throws IOException {
List<ByteArrayOutputStream> layers = new ArrayList<>();
buildpack.apply((layer) -> {
ByteArrayOutputStream out = new ByteArrayOutputStream();
layer.writeTo(out);
layers.add(out);
});
assertThat(layers).hasSize(1);
byte[] content = layers.get(0).toByteArray();
try (TarArchiveInputStream tar = new TarArchiveInputStream(new ByteArrayInputStream(content))) {
assertThat(tar.getNextEntry().getName())
.isEqualTo("cnb/buildpacks/example_buildpack1/0.0.1/buildpack.toml");
assertThat(tar.getNextEntry().getName()).isEqualTo("cnb/buildpacks/example_buildpack1/0.0.1/bin/detect");
assertThat(tar.getNextEntry().getName()).isEqualTo("cnb/buildpacks/example_buildpack1/0.0.1/bin/build");
assertThat(tar.getNextEntry()).isNull();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
@@ -16,11 +16,14 @@
package org.springframework.boot.buildpack.platform.docker;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@@ -48,6 +51,8 @@ import org.springframework.boot.buildpack.platform.io.Content;
import org.springframework.boot.buildpack.platform.io.IOConsumer;
import org.springframework.boot.buildpack.platform.io.Owner;
import org.springframework.boot.buildpack.platform.io.TarArchive;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -304,6 +309,45 @@ class DockerApiTests {
assertThat(image.getLayers()).hasSize(46);
}
@Test
void exportLayersWhenReferenceIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.api.exportLayers(null, (name, archive) -> {
})).withMessage("Reference must not be null");
}
@Test
void exportLayersWhenExportsIsNullThrowsException() {
ImageReference reference = ImageReference.of("gcr.io/paketo-buildpacks/builder:base");
assertThatIllegalArgumentException().isThrownBy(() -> this.api.exportLayers(reference, null))
.withMessage("Exports must not be null");
}
@Test
void exportLayersExportsLayerTars() throws Exception {
ImageReference reference = ImageReference.of("gcr.io/paketo-buildpacks/builder:base");
URI exportUri = new URI(IMAGES_URL + "/gcr.io/paketo-buildpacks/builder:base/get");
given(DockerApiTests.this.http.get(exportUri)).willReturn(responseOf("export.tar"));
MultiValueMap<String, String> contents = new LinkedMultiValueMap<>();
this.api.exportLayers(reference, (name, archive) -> {
ByteArrayOutputStream out = new ByteArrayOutputStream();
archive.writeTo(out);
try (TarArchiveInputStream in = new TarArchiveInputStream(
new ByteArrayInputStream(out.toByteArray()))) {
TarArchiveEntry entry = in.getNextTarEntry();
while (entry != null) {
contents.add(name, entry.getName());
entry = in.getNextTarEntry();
}
}
});
assertThat(contents).hasSize(3).containsKeys(
"1bf6c63a1e9ed1dd7cb961273bf60b8e0f440361faf273baf866f408e4910601/layer.tar",
"8fdfb915302159a842cbfae6faec5311b00c071ebf14e12da7116ae7532e9319/layer.tar",
"93cd584bb189bfca4f51744bd19d836fd36da70710395af5a1523ee88f208c6a/layer.tar");
assertThat(contents.get("1bf6c63a1e9ed1dd7cb961273bf60b8e0f440361faf273baf866f408e4910601/layer.tar"))
.containsExactly("etc/", "etc/apt/", "etc/apt/sources.list");
}
}
@Nested

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
@@ -25,6 +25,7 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ProgressUpdateEvent}.
*
* @param <E> The event type
* @author Phillip Webb
* @author Scott Frederick
*/

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2012-2021 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.io;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.Collections;
import java.util.Set;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link FilePermissions}.
*
* @author Scott Frederick
*/
class FilePermissionsTests {
@Test
void posixPermissionsToUmask() {
Set<PosixFilePermission> permissions = PosixFilePermissions.fromString("rwxrw-r--");
assertThat(FilePermissions.posixPermissionsToUmask(permissions)).isEqualTo(0764);
}
@Test
void posixPermissionsToUmaskWithEmptyPermissions() {
Set<PosixFilePermission> permissions = Collections.emptySet();
assertThat(FilePermissions.posixPermissionsToUmask(permissions)).isEqualTo(0);
}
@Test
void posixPermissionsToUmaskWithNullPermissions() {
assertThatIllegalArgumentException().isThrownBy(() -> FilePermissions.posixPermissionsToUmask(null));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
@@ -31,6 +31,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link TarLayoutWriter}.
*
* @author Phillip Webb
* @author Scott Frederick
*/
class TarLayoutWriterTests {
@@ -39,7 +40,7 @@ class TarLayoutWriterTests {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (TarLayoutWriter writer = new TarLayoutWriter(outputStream)) {
writer.directory("/foo", Owner.ROOT);
writer.file("/foo/bar.txt", Owner.of(1, 1), Content.of("test"));
writer.file("/foo/bar.txt", Owner.of(1, 1), 0777, Content.of("test"));
}
try (TarArchiveInputStream tarInputStream = new TarArchiveInputStream(
new ByteArrayInputStream(outputStream.toByteArray()))) {
@@ -54,7 +55,7 @@ class TarLayoutWriterTests {
assertThat(directoryEntry.getLongGroupId()).isEqualTo(0);
assertThat(directoryEntry.getModTime()).isEqualTo(new Date(TarLayoutWriter.NORMALIZED_MOD_TIME));
assertThat(fileEntry.getName()).isEqualTo("/foo/bar.txt");
assertThat(fileEntry.getMode()).isEqualTo(0644);
assertThat(fileEntry.getMode()).isEqualTo(0777);
assertThat(fileEntry.getLongUserId()).isEqualTo(1);
assertThat(fileEntry.getLongGroupId()).isEqualTo(1);
assertThat(fileEntry.getModTime()).isEqualTo(new Date(TarLayoutWriter.NORMALIZED_MOD_TIME));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
@@ -16,7 +16,11 @@
package org.springframework.boot.buildpack.platform.json;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -26,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Abstract base class for JSON based tests.
*
* @author Phillip Webb
* @author Scott Frederick
*/
public abstract class AbstractJsonTests {
@@ -39,4 +44,9 @@ public abstract class AbstractJsonTests {
return result;
}
protected final String getContentAsString(String name) {
return new BufferedReader(new InputStreamReader(getContent(name), StandardCharsets.UTF_8)).lines()
.collect(Collectors.joining("\n"));
}
}

View File

@@ -2,124 +2,174 @@
"description": "Ubuntu bionic base image with buildpacks for Java, NodeJS and Golang",
"buildpacks": [
{
"id": "org.cloudfoundry.googlestackdriver",
"version": "v1.1.11"
"id": "paketo-buildpacks/dotnet-core",
"version": "0.0.9",
"homepage": "https://github.com/paketo-buildpacks/dotnet-core"
},
{
"id": "org.cloudfoundry.springboot",
"version": "v1.2.13"
"id": "paketo-buildpacks/dotnet-core-runtime",
"version": "0.0.201",
"homepage": "https://github.com/paketo-buildpacks/dotnet-core-runtime"
},
{
"id": "org.cloudfoundry.debug",
"version": "v1.2.11"
"id": "paketo-buildpacks/dotnet-core-sdk",
"version": "0.0.196",
"homepage": "https://github.com/paketo-buildpacks/dotnet-core-sdk"
},
{
"id": "org.cloudfoundry.tomcat",
"version": "v1.3.18"
"id": "paketo-buildpacks/dotnet-execute",
"version": "0.0.180",
"homepage": "https://github.com/paketo-buildpacks/dotnet-execute"
},
{
"id": "org.cloudfoundry.go",
"version": "v0.0.4"
"id": "paketo-buildpacks/dotnet-publish",
"version": "0.0.121",
"homepage": "https://github.com/paketo-buildpacks/dotnet-publish"
},
{
"id": "org.cloudfoundry.openjdk",
"version": "v1.2.14"
"id": "paketo-buildpacks/dotnet-core-aspnet",
"version": "0.0.196",
"homepage": "https://github.com/paketo-buildpacks/dotnet-core-aspnet"
},
{
"id": "org.cloudfoundry.buildsystem",
"version": "v1.2.15"
"id": "paketo-buildpacks/java-native-image",
"version": "4.7.0",
"homepage": "https://github.com/paketo-buildpacks/java-native-image"
},
{
"id": "org.cloudfoundry.jvmapplication",
"version": "v1.1.12"
"id": "paketo-buildpacks/spring-boot",
"version": "3.5.0",
"homepage": "https://github.com/paketo-buildpacks/spring-boot"
},
{
"id": "org.cloudfoundry.springautoreconfiguration",
"version": "v1.1.11"
"id": "paketo-buildpacks/executable-jar",
"version": "3.1.3",
"homepage": "https://github.com/paketo-buildpacks/executable-jar"
},
{
"id": "org.cloudfoundry.archiveexpanding",
"version": "v1.0.102"
"id": "paketo-buildpacks/graalvm",
"version": "4.1.0",
"homepage": "https://github.com/paketo-buildpacks/graalvm"
},
{
"id": "org.cloudfoundry.jmx",
"version": "v1.1.12"
"id": "paketo-buildpacks/gradle",
"version": "3.5.0",
"homepage": "https://github.com/paketo-buildpacks/gradle"
},
{
"id": "org.cloudfoundry.nodejs",
"version": "v2.0.8"
"id": "paketo-buildpacks/leiningen",
"version": "1.2.1",
"homepage": "https://github.com/paketo-buildpacks/leiningen"
},
{
"id": "org.cloudfoundry.jdbc",
"version": "v1.1.14"
"id": "paketo-buildpacks/procfile",
"version": "3.0.0",
"homepage": "https://github.com/paketo-buildpacks/procfile"
},
{
"id": "org.cloudfoundry.procfile",
"version": "v1.1.12"
"id": "paketo-buildpacks/sbt",
"version": "3.6.0",
"homepage": "https://github.com/paketo-buildpacks/sbt"
},
{
"id": "org.cloudfoundry.dotnet-core",
"version": "v0.0.6"
"id": "paketo-buildpacks/spring-boot-native-image",
"version": "2.0.1",
"homepage": "https://github.com/paketo-buildpacks/spring-boot-native-image"
},
{
"id": "org.cloudfoundry.azureapplicationinsights",
"version": "v1.1.12"
"id": "paketo-buildpacks/environment-variables",
"version": "2.1.2",
"homepage": "https://github.com/paketo-buildpacks/environment-variables"
},
{
"id": "org.cloudfoundry.distzip",
"version": "v1.1.12"
"id": "paketo-buildpacks/image-labels",
"version": "2.0.7",
"homepage": "https://github.com/paketo-buildpacks/image-labels"
},
{
"id": "org.cloudfoundry.dep",
"version": "0.0.101"
"id": "paketo-buildpacks/maven",
"version": "3.2.1",
"homepage": "https://github.com/paketo-buildpacks/maven"
},
{
"id": "org.cloudfoundry.go-compiler",
"version": "0.0.105"
"id": "paketo-buildpacks/java",
"version": "4.10.0",
"homepage": "https://github.com/paketo-buildpacks/java"
},
{
"id": "org.cloudfoundry.go-mod",
"version": "0.0.89"
"id": "paketo-buildpacks/ca-certificates",
"version": "1.0.1",
"homepage": "https://github.com/paketo-buildpacks/ca-certificates"
},
{
"id": "org.cloudfoundry.node-engine",
"version": "0.0.163"
"id": "paketo-buildpacks/environment-variables",
"version": "2.1.2",
"homepage": "https://github.com/paketo-buildpacks/environment-variables"
},
{
"id": "org.cloudfoundry.npm",
"version": "0.1.3"
"id": "paketo-buildpacks/executable-jar",
"version": "3.1.3",
"homepage": "https://github.com/paketo-buildpacks/executable-jar"
},
{
"id": "org.cloudfoundry.yarn-install",
"version": "0.1.10"
"id": "paketo-buildpacks/procfile",
"version": "3.0.0",
"homepage": "https://github.com/paketo-buildpacks/procfile"
},
{
"id": "org.cloudfoundry.dotnet-core-aspnet",
"version": "0.0.118"
"id": "paketo-buildpacks/apache-tomcat",
"version": "3.2.0",
"homepage": "https://github.com/paketo-buildpacks/apache-tomcat"
},
{
"id": "org.cloudfoundry.dotnet-core-build",
"version": "0.0.68"
"id": "paketo-buildpacks/gradle",
"version": "3.5.0",
"homepage": "https://github.com/paketo-buildpacks/gradle"
},
{
"id": "org.cloudfoundry.dotnet-core-conf",
"version": "0.0.115"
"id": "paketo-buildpacks/maven",
"version": "3.2.1",
"homepage": "https://github.com/paketo-buildpacks/maven"
},
{
"id": "org.cloudfoundry.dotnet-core-runtime",
"version": "0.0.127"
"id": "paketo-buildpacks/sbt",
"version": "3.6.0",
"homepage": "https://github.com/paketo-buildpacks/sbt"
},
{
"id": "org.cloudfoundry.dotnet-core-sdk",
"version": "0.0.122"
"id": "paketo-buildpacks/bellsoft-liberica",
"version": "6.2.0",
"homepage": "https://github.com/paketo-buildpacks/bellsoft-liberica"
},
{
"id": "org.cloudfoundry.icu",
"version": "0.0.43"
"id": "paketo-buildpacks/image-labels",
"version": "2.0.7",
"homepage": "https://github.com/paketo-buildpacks/image-labels"
},
{
"id": "org.cloudfoundry.node-engine",
"version": "0.0.158"
"id": "paketo-buildpacks/debug",
"version": "2.1.4",
"homepage": "https://github.com/paketo-buildpacks/debug"
},
{
"id": "paketo-buildpacks/dist-zip",
"version": "2.2.2",
"homepage": "https://github.com/paketo-buildpacks/dist-zip"
},
{
"id": "paketo-buildpacks/spring-boot",
"version": "3.5.0",
"homepage": "https://github.com/paketo-buildpacks/spring-boot"
},
{
"id": "paketo-buildpacks/jmx",
"version": "2.1.4",
"homepage": "https://github.com/paketo-buildpacks/jmx"
},
{
"id": "paketo-buildpacks/leiningen",
"version": "1.2.1",
"homepage": "https://github.com/paketo-buildpacks/leiningen"
}
],
"stack": {
@@ -139,4 +189,4 @@
"name": "Pack CLI",
"version": "v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)"
}
}
}

View File

@@ -0,0 +1,78 @@
{
"Id": "sha256:a266647e285b52403b556adc963f1809556aa999f2f694e8dc54098c570ee55a",
"RepoTags": [
"example/hello-universe:latest"
],
"RepoDigests": [],
"Parent": "",
"Comment": "",
"Created": "1980-01-01T00:00:01Z",
"Container": "",
"ContainerConfig": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": null,
"Cmd": null,
"Image": "",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"DockerVersion": "",
"Author": "",
"Config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": null,
"Cmd": null,
"Image": "",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": {
"io.buildpacks.buildpackage.metadata": "{\"id\":\"example/hello-universe\",\"version\":\"0.0.1\",\"homepage\":\"https://github.com/buildpacks/example/tree/main/buildpacks/hello-universe\",\"stacks\":[{\"id\":\"io.buildpacks.example.stacks.alpine\"},{\"id\":\"io.buildpacks.stacks.bionic\"}]}",
"io.buildpacks.buildpack.layers": "{\"example/hello-moon\":{\"0.0.3\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.alpine\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:4bfdc8714aee68da6662c43bc28d3b41202c88e915641c356523dabe729814c2\",\"homepage\":\"https://github.com/example/tree/main/buildpacks/hello-moon\"}},\"example/hello-universe\":{\"0.0.1\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"example/hello-world\",\"version\":\"0.0.2\"},{\"id\":\"example/hello-moon\",\"version\":\"0.0.2\"}]}],\"layerDiffID\":\"sha256:739b4e8f3caae7237584a1bfe029ebdb05403752b1a60a4f9be991b1d51dbb69\",\"homepage\":\"https://github.com/example/tree/main/buildpacks/hello-universe\"}},\"example/hello-world\":{\"0.0.2\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.alpine\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:f752fe099c846e501bdc991d1a22f98c055ddc62f01cfc0495fff2c69f8eb940\",\"homepage\":\"https://github.com/example/tree/main/buildpacks/hello-world\"}}}"
}
},
"Architecture": "amd64",
"Os": "linux",
"Size": 4654,
"VirtualSize": 4654,
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/cbf39b4508463beeb1d0a553c3e2baa84b8cd8dbc95681aaecc243e3ca77bcf4/diff:/var/lib/docker/overlay2/15e3d01b65c962b50a3da1b6663b8196284fb3c7e7f8497f2c1a0a736d0ec237/diff",
"MergedDir": "/var/lib/docker/overlay2/1425ea68b0daff01bcc32e55e09eeeada2318d7dd1dc4e184711359da8425bb7/merged",
"UpperDir": "/var/lib/docker/overlay2/1425ea68b0daff01bcc32e55e09eeeada2318d7dd1dc4e184711359da8425bb7/diff",
"WorkDir": "/var/lib/docker/overlay2/1425ea68b0daff01bcc32e55e09eeeada2318d7dd1dc4e184711359da8425bb7/work"
},
"Name": "overlay2"
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:4bfdc8714aee68da6662c43bc28d3b41202c88e915641c356523dabe729814c2",
"sha256:f752fe099c846e501bdc991d1a22f98c055ddc62f01cfc0495fff2c69f8eb940",
"sha256:739b4e8f3caae7237584a1bfe029ebdb05403752b1a60a4f9be991b1d51dbb69"
]
},
"Metadata": {
"LastTagTime": "2021-01-27T22:56:06.4599859Z"
}
}

View File

@@ -0,0 +1,13 @@
{
"id": "example/hello-universe",
"version": "0.0.1",
"homepage": "https://github.com/example/tree/main/buildpacks/hello-universe",
"stacks": [
{
"id": "io.buildpacks.stacks.alpine"
},
{
"id": "io.buildpacks.stacks.bionic"
}
]
}

View File

@@ -0,0 +1,8 @@
[buildpack]
id = "test";
version = "1.0.0"
name = "Example buildpack"
homepage = "https://github.com/example/example-buildpack"
[[stacks]]
id = "io.buildpacks.stacks.bionic"

View File

@@ -0,0 +1,15 @@
[[order]]
group = [
{ id = "example/buildpack1", version = "0.0.1" }
]
[[order]]
group = [
{ id = "example/buildpack2", version = "0.0.2" }
]
[[order]]
group = [
{ id = "example/buildpack3", version = "0.0.3" }
]

View File

@@ -0,0 +1,15 @@
[[order]]
group = [
{ id = "example/buildpack1" }
]
[[order]]
group = [
{ id = "example/buildpack2" }
]
[[order]]
group = [
{ id = "example/buildpack3" }
]