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