commit
e24c49a09a
@ -0,0 +1,194 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2022 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.lang.invoke.MethodHandles;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
|
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.MappedObject;
|
||||||
|
import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Buildpack layers metadata information.
|
||||||
|
*
|
||||||
|
* @author Scott Frederick
|
||||||
|
*/
|
||||||
|
final class BuildpackLayersMetadata extends MappedObject {
|
||||||
|
|
||||||
|
private static final String LABEL_NAME = "io.buildpacks.buildpack.layers";
|
||||||
|
|
||||||
|
private final Buildpacks buildpacks;
|
||||||
|
|
||||||
|
private BuildpackLayersMetadata(JsonNode node) {
|
||||||
|
super(node, MethodHandles.lookup());
|
||||||
|
this.buildpacks = Buildpacks.fromJson(getNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the metadata details of a buildpack with the given ID and version.
|
||||||
|
* @param id the buildpack ID
|
||||||
|
* @param version the buildpack version
|
||||||
|
* @return the buildpack details or {@code null} if a buildpack with the given ID and
|
||||||
|
* version does not exist in the metadata
|
||||||
|
*/
|
||||||
|
BuildpackLayerDetails getBuildpack(String id, String version) {
|
||||||
|
return this.buildpacks.getBuildpack(id, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link BuildpackLayersMetadata} from an image.
|
||||||
|
* @param image the source image
|
||||||
|
* @return the buildpack layers metadata
|
||||||
|
* @throws IOException on IO error
|
||||||
|
*/
|
||||||
|
static BuildpackLayersMetadata fromImage(Image image) throws IOException {
|
||||||
|
Assert.notNull(image, "Image must not be null");
|
||||||
|
return fromImageConfig(image.getConfig());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link BuildpackLayersMetadata} from image config.
|
||||||
|
* @param imageConfig the source image config
|
||||||
|
* @return the buildpack layers metadata
|
||||||
|
* @throws IOException on IO error
|
||||||
|
*/
|
||||||
|
static BuildpackLayersMetadata fromImageConfig(ImageConfig imageConfig) throws IOException {
|
||||||
|
Assert.notNull(imageConfig, "ImageConfig must not be null");
|
||||||
|
String json = imageConfig.getLabels().get(LABEL_NAME);
|
||||||
|
Assert.notNull(json, () -> "No '" + LABEL_NAME + "' label found in image config labels '"
|
||||||
|
+ StringUtils.collectionToCommaDelimitedString(imageConfig.getLabels().keySet()) + "'");
|
||||||
|
return fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link BuildpackLayersMetadata} from JSON.
|
||||||
|
* @param json the source JSON
|
||||||
|
* @return the buildpack layers metadata
|
||||||
|
* @throws IOException on IO error
|
||||||
|
*/
|
||||||
|
static BuildpackLayersMetadata fromJson(String json) throws IOException {
|
||||||
|
return fromJson(SharedObjectMapper.get().readTree(json));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link BuildpackLayersMetadata} from JSON.
|
||||||
|
* @param node the source JSON
|
||||||
|
* @return the buildpack layers metadata
|
||||||
|
*/
|
||||||
|
static BuildpackLayersMetadata fromJson(JsonNode node) {
|
||||||
|
return new BuildpackLayersMetadata(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Buildpacks {
|
||||||
|
|
||||||
|
private final Map<String, BuildpackVersions> buildpacks = new HashMap<>();
|
||||||
|
|
||||||
|
private BuildpackLayerDetails getBuildpack(String id, String version) {
|
||||||
|
if (this.buildpacks.containsKey(id)) {
|
||||||
|
return this.buildpacks.get(id).getBuildpack(version);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addBuildpackVersions(String id, BuildpackVersions versions) {
|
||||||
|
this.buildpacks.put(id, versions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Buildpacks fromJson(JsonNode node) {
|
||||||
|
Buildpacks buildpacks = new Buildpacks();
|
||||||
|
node.fields().forEachRemaining((field) -> buildpacks.addBuildpackVersions(field.getKey(),
|
||||||
|
BuildpackVersions.fromJson(field.getValue())));
|
||||||
|
return buildpacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class BuildpackVersions {
|
||||||
|
|
||||||
|
private final Map<String, BuildpackLayerDetails> versions = new HashMap<>();
|
||||||
|
|
||||||
|
private BuildpackLayerDetails getBuildpack(String version) {
|
||||||
|
return this.versions.get(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addBuildpackVersion(String version, BuildpackLayerDetails details) {
|
||||||
|
this.versions.put(version, details);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BuildpackVersions fromJson(JsonNode node) {
|
||||||
|
BuildpackVersions versions = new BuildpackVersions();
|
||||||
|
node.fields().forEachRemaining((field) -> versions.addBuildpackVersion(field.getKey(),
|
||||||
|
BuildpackLayerDetails.fromJson(field.getValue())));
|
||||||
|
return versions;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class BuildpackLayerDetails extends MappedObject {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
private final String homepage;
|
||||||
|
|
||||||
|
private final String layerDiffId;
|
||||||
|
|
||||||
|
private BuildpackLayerDetails(JsonNode node) {
|
||||||
|
super(node, MethodHandles.lookup());
|
||||||
|
this.name = valueAt("/name", String.class);
|
||||||
|
this.homepage = valueAt("/homepage", String.class);
|
||||||
|
this.layerDiffId = valueAt("/layerDiffID", String.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the buildpack name.
|
||||||
|
* @return the name
|
||||||
|
*/
|
||||||
|
String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the buildpack homepage address.
|
||||||
|
* @return the homepage address
|
||||||
|
*/
|
||||||
|
String getHomepage() {
|
||||||
|
return this.homepage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the buildpack layer {@code diffID}.
|
||||||
|
* @return the layer {@code diffID}
|
||||||
|
*/
|
||||||
|
String getLayerDiffId() {
|
||||||
|
return this.layerDiffId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BuildpackLayerDetails fromJson(JsonNode node) {
|
||||||
|
return new BuildpackLayerDetails(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2022 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 BuildpackLayersMetadata}.
|
||||||
|
*
|
||||||
|
* @author Scott Frederick
|
||||||
|
*/
|
||||||
|
class BuildpackLayersMetadataTests extends AbstractJsonTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void fromImageLoadsMetadata() throws IOException {
|
||||||
|
Image image = Image.of(getContent("buildpack-image.json"));
|
||||||
|
BuildpackLayersMetadata metadata = BuildpackLayersMetadata.fromImage(image);
|
||||||
|
assertThat(metadata.getBuildpack("example/hello-moon", "0.0.3")).extracting("homepage", "layerDiffId")
|
||||||
|
.containsExactly("https://github.com/example/tree/main/buildpacks/hello-moon",
|
||||||
|
"sha256:4bfdc8714aee68da6662c43bc28d3b41202c88e915641c356523dabe729814c2");
|
||||||
|
assertThat(metadata.getBuildpack("example/hello-world", "0.0.2")).extracting("homepage", "layerDiffId")
|
||||||
|
.containsExactly("https://github.com/example/tree/main/buildpacks/hello-world",
|
||||||
|
"sha256:f752fe099c846e501bdc991d1a22f98c055ddc62f01cfc0495fff2c69f8eb940");
|
||||||
|
assertThat(metadata.getBuildpack("example/hello-world", "version-does-not-exist")).isNull();
|
||||||
|
assertThat(metadata.getBuildpack("id-does-not-exist", "9.9.9")).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void fromImageWhenImageIsNullThrowsException() {
|
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> BuildpackLayersMetadata.fromImage(null))
|
||||||
|
.withMessage("Image must not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void fromImageWhenImageConfigIsNullThrowsException() {
|
||||||
|
Image image = mock(Image.class);
|
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> BuildpackLayersMetadata.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(() -> BuildpackLayersMetadata.fromImage(image))
|
||||||
|
.withMessage("No 'io.buildpacks.buildpack.layers' label found in image config labels 'alpha'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void fromJsonLoadsMetadata() throws IOException {
|
||||||
|
BuildpackLayersMetadata metadata = BuildpackLayersMetadata
|
||||||
|
.fromJson(getContentAsString("buildpack-layers-metadata.json"));
|
||||||
|
assertThat(metadata.getBuildpack("example/hello-moon", "0.0.3")).extracting("name", "homepage", "layerDiffId")
|
||||||
|
.containsExactly("Example hello-moon buildpack",
|
||||||
|
"https://github.com/example/tree/main/buildpacks/hello-moon",
|
||||||
|
"sha256:4bfdc8714aee68da6662c43bc28d3b41202c88e915641c356523dabe729814c2");
|
||||||
|
assertThat(metadata.getBuildpack("example/hello-world", "0.0.1")).extracting("name", "homepage", "layerDiffId")
|
||||||
|
.containsExactly("Example hello-world buildpack",
|
||||||
|
"https://github.com/example/tree/main/buildpacks/hello-world",
|
||||||
|
"sha256:1c90e0b80d92555a0523c9ee6500845328fc39ba9dca9d30a877ff759ffbff28");
|
||||||
|
assertThat(metadata.getBuildpack("example/hello-world", "0.0.2")).extracting("name", "homepage", "layerDiffId")
|
||||||
|
.containsExactly("Example hello-world buildpack",
|
||||||
|
"https://github.com/example/tree/main/buildpacks/hello-world",
|
||||||
|
"sha256:f752fe099c846e501bdc991d1a22f98c055ddc62f01cfc0495fff2c69f8eb940");
|
||||||
|
assertThat(metadata.getBuildpack("example/hello-world", "version-does-not-exist")).isNull();
|
||||||
|
assertThat(metadata.getBuildpack("id-does-not-exist", "9.9.9")).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"example/hello-moon": {
|
||||||
|
"0.0.3": {
|
||||||
|
"api": "0.2",
|
||||||
|
"stacks": [
|
||||||
|
{
|
||||||
|
"id": "io.buildpacks.stacks.alpine"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "io.buildpacks.stacks.bionic"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Example hello-moon buildpack",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Example hello-universe buildpack",
|
||||||
|
"layerDiffID": "sha256:739b4e8f3caae7237584a1bfe029ebdb05403752b1a60a4f9be991b1d51dbb69",
|
||||||
|
"homepage": "https://github.com/example/tree/main/buildpacks/hello-universe"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example/hello-world": {
|
||||||
|
"0.0.1": {
|
||||||
|
"api": "0.2",
|
||||||
|
"stacks": [
|
||||||
|
{
|
||||||
|
"id": "io.buildpacks.stacks.alpine"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "io.buildpacks.stacks.bionic"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Example hello-world buildpack",
|
||||||
|
"layerDiffID": "sha256:1c90e0b80d92555a0523c9ee6500845328fc39ba9dca9d30a877ff759ffbff28",
|
||||||
|
"homepage": "https://github.com/example/tree/main/buildpacks/hello-world"
|
||||||
|
},
|
||||||
|
"0.0.2": {
|
||||||
|
"api": "0.2",
|
||||||
|
"stacks": [
|
||||||
|
{
|
||||||
|
"id": "io.buildpacks.stacks.alpine"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "io.buildpacks.stacks.bionic"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "Example hello-world buildpack",
|
||||||
|
"layerDiffID": "sha256:f752fe099c846e501bdc991d1a22f98c055ddc62f01cfc0495fff2c69f8eb940",
|
||||||
|
"homepage": "https://github.com/example/tree/main/buildpacks/hello-world"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue