Set Spring Boot version in ephemeral builder
This commit adds a `createdBy` structure to the metadata of the ephemeral builder container image that identifies Spring Boot as the creator of the image, along with the Spring Boot version. See gh-20126
This commit is contained in:
@@ -44,6 +44,8 @@ public class BuildRequest {
|
||||
|
||||
private final ImageReference builder;
|
||||
|
||||
private final Creator creator;
|
||||
|
||||
private final Map<String, String> env;
|
||||
|
||||
private final boolean cleanCache;
|
||||
@@ -59,13 +61,15 @@ public class BuildRequest {
|
||||
this.env = Collections.emptyMap();
|
||||
this.cleanCache = false;
|
||||
this.verboseLogging = false;
|
||||
this.creator = Creator.withVersion("");
|
||||
}
|
||||
|
||||
BuildRequest(ImageReference name, Function<Owner, TarArchive> applicationContent, ImageReference builder,
|
||||
Map<String, String> env, boolean cleanCache, boolean verboseLogging) {
|
||||
Creator creator, Map<String, String> env, boolean cleanCache, boolean verboseLogging) {
|
||||
this.name = name;
|
||||
this.applicationContent = applicationContent;
|
||||
this.builder = builder;
|
||||
this.creator = creator;
|
||||
this.env = env;
|
||||
this.cleanCache = cleanCache;
|
||||
this.verboseLogging = verboseLogging;
|
||||
@@ -78,7 +82,18 @@ public class BuildRequest {
|
||||
*/
|
||||
public BuildRequest withBuilder(ImageReference builder) {
|
||||
Assert.notNull(builder, "Builder must not be null");
|
||||
return new BuildRequest(this.name, this.applicationContent, builder.inTaggedForm(), this.env, this.cleanCache,
|
||||
return new BuildRequest(this.name, this.applicationContent, builder.inTaggedForm(), this.creator, this.env,
|
||||
this.cleanCache, this.verboseLogging);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link BuildRequest} with an updated builder.
|
||||
* @param creator the new {@code Creator} to use
|
||||
* @return an updated build request
|
||||
*/
|
||||
public BuildRequest withCreator(Creator creator) {
|
||||
Assert.notNull(creator, "Creator must not be null");
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, creator, this.env, this.cleanCache,
|
||||
this.verboseLogging);
|
||||
}
|
||||
|
||||
@@ -93,8 +108,8 @@ public class BuildRequest {
|
||||
Assert.hasText(value, "Value must not be empty");
|
||||
Map<String, String> env = new LinkedHashMap<>(this.env);
|
||||
env.put(name, value);
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, Collections.unmodifiableMap(env),
|
||||
this.cleanCache, this.verboseLogging);
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.creator,
|
||||
Collections.unmodifiableMap(env), this.cleanCache, this.verboseLogging);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,7 +121,7 @@ public class BuildRequest {
|
||||
Assert.notNull(env, "Env must not be null");
|
||||
Map<String, String> updatedEnv = new LinkedHashMap<>(this.env);
|
||||
updatedEnv.putAll(env);
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder,
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.creator,
|
||||
Collections.unmodifiableMap(updatedEnv), this.cleanCache, this.verboseLogging);
|
||||
}
|
||||
|
||||
@@ -116,7 +131,7 @@ public class BuildRequest {
|
||||
* @return an updated build request
|
||||
*/
|
||||
public BuildRequest withCleanCache(boolean cleanCache) {
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.env, cleanCache,
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.creator, this.env, cleanCache,
|
||||
this.verboseLogging);
|
||||
}
|
||||
|
||||
@@ -126,8 +141,8 @@ public class BuildRequest {
|
||||
* @return an updated build request
|
||||
*/
|
||||
public BuildRequest withVerboseLogging(boolean verboseLogging) {
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.env, this.cleanCache,
|
||||
verboseLogging);
|
||||
return new BuildRequest(this.name, this.applicationContent, this.builder, this.creator, this.env,
|
||||
this.cleanCache, verboseLogging);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,6 +172,14 @@ public class BuildRequest {
|
||||
return this.builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link Creator} the builder should use.
|
||||
* @return the {@code Creator}
|
||||
*/
|
||||
public Creator getCreator() {
|
||||
return this.creator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return any env variable that should be passed to the builder.
|
||||
* @return the builder env
|
||||
|
||||
@@ -66,7 +66,8 @@ public class Builder {
|
||||
ImageReference runImageReference = getRunImageReference(builderMetadata.getStack());
|
||||
Image runImage = pullRunImage(request, runImageReference);
|
||||
assertHasExpectedStackId(runImage, stackId);
|
||||
EphemeralBuilder builder = new EphemeralBuilder(buildOwner, builderImage, builderMetadata, request.getEnv());
|
||||
EphemeralBuilder builder = new EphemeralBuilder(buildOwner, builderImage, builderMetadata, request.getCreator(),
|
||||
request.getEnv());
|
||||
this.docker.image().load(builder.getArchive(), UpdateListener.none());
|
||||
try {
|
||||
executeLifecycle(request, runImageReference, builder);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.buildpack.platform.build;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Identifying information about the tooling that created a builder.
|
||||
*
|
||||
* @author Scott Frederick
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class Creator {
|
||||
|
||||
private final String version;
|
||||
|
||||
Creator(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the builder creator.
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return "Spring Boot";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the version of the builder creator.
|
||||
* @return the version
|
||||
*/
|
||||
public String getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code Creator} using the provided version.
|
||||
* @param version the creator version
|
||||
* @return a new creator instance
|
||||
*/
|
||||
public static Creator withVersion(String version) {
|
||||
Assert.notNull(version, "Version must not be null");
|
||||
return new Creator(version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName() + " version " + getVersion();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,17 +42,20 @@ class EphemeralBuilder {
|
||||
|
||||
private final ImageArchive archive;
|
||||
|
||||
private final Creator creator;
|
||||
|
||||
/**
|
||||
* Create a new {@link EphemeralBuilder} instance.
|
||||
* @param buildOwner the build owner
|
||||
* @param builderImage the image
|
||||
* @param builderMetadata the builder metadata
|
||||
* @param creator the builder creator
|
||||
* @param env the builder env
|
||||
* @throws IOException on IO error
|
||||
*/
|
||||
EphemeralBuilder(BuildOwner buildOwner, Image builderImage, BuilderMetadata builderMetadata,
|
||||
EphemeralBuilder(BuildOwner buildOwner, Image builderImage, BuilderMetadata builderMetadata, Creator creator,
|
||||
Map<String, String> env) throws IOException {
|
||||
this(Clock.systemUTC(), buildOwner, builderImage, builderMetadata, env);
|
||||
this(Clock.systemUTC(), buildOwner, builderImage, builderMetadata, creator, env);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,13 +64,15 @@ class EphemeralBuilder {
|
||||
* @param buildOwner the build owner
|
||||
* @param builderImage the image
|
||||
* @param builderMetadata the builder metadata
|
||||
* @param creator the builder creator
|
||||
* @param env the builder env
|
||||
* @throws IOException on IO error
|
||||
*/
|
||||
EphemeralBuilder(Clock clock, BuildOwner buildOwner, Image builderImage, BuilderMetadata builderMetadata,
|
||||
Map<String, String> env) throws IOException {
|
||||
Creator creator, Map<String, String> env) throws IOException {
|
||||
ImageReference name = ImageReference.random("pack.local/builder/").inTaggedForm();
|
||||
this.buildOwner = buildOwner;
|
||||
this.creator = creator;
|
||||
this.builderMetadata = builderMetadata.copy(this::updateMetadata);
|
||||
this.archive = ImageArchive.from(builderImage, (update) -> {
|
||||
update.withUpdatedConfig(this.builderMetadata::attachTo);
|
||||
@@ -80,7 +85,7 @@ class EphemeralBuilder {
|
||||
}
|
||||
|
||||
private void updateMetadata(BuilderMetadata.Update update) {
|
||||
update.withCreatedBy("Spring Boot", "dev");
|
||||
update.withCreatedBy(this.creator.getName(), this.creator.getVersion());
|
||||
}
|
||||
|
||||
private Layer getEnvLayer(Map<String, String> env) throws IOException {
|
||||
|
||||
@@ -42,6 +42,7 @@ import static org.assertj.core.api.Assertions.entry;
|
||||
* Tests for {@link BuildRequest}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
public class BuildRequestTests {
|
||||
|
||||
@@ -97,6 +98,16 @@ public class BuildRequestTests {
|
||||
assertThat(request.getBuilder().toString()).isEqualTo("docker.io/spring/builder:latest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withCreatorUpdatesCreator() throws IOException {
|
||||
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar"));
|
||||
BuildRequest withCreator = request.withCreator(Creator.withVersion("1.0.0"));
|
||||
assertThat(request.getCreator().getName()).isEqualTo("Spring Boot");
|
||||
assertThat(request.getCreator().getVersion()).isEqualTo("");
|
||||
assertThat(withCreator.getCreator().getName()).isEqualTo("Spring Boot");
|
||||
assertThat(withCreator.getCreator().getVersion()).isEqualTo("1.0.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withEnvAddsEnvEntry() throws IOException {
|
||||
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar"));
|
||||
|
||||
@@ -62,6 +62,8 @@ class EphemeralBuilderTests extends AbstractJsonTests {
|
||||
|
||||
private Map<String, String> env;
|
||||
|
||||
private Creator creator = Creator.withVersion("dev");
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
this.image = Image.of(getContent("image.json"));
|
||||
@@ -71,23 +73,24 @@ class EphemeralBuilderTests extends AbstractJsonTests {
|
||||
|
||||
@Test
|
||||
void getNameHasRandomName() throws Exception {
|
||||
EphemeralBuilder b1 = new EphemeralBuilder(this.owner, this.image, this.metadata, this.env);
|
||||
EphemeralBuilder b2 = new EphemeralBuilder(this.owner, this.image, this.metadata, this.env);
|
||||
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);
|
||||
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.env);
|
||||
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.metadata, this.creator, this.env);
|
||||
ImageConfig config = builder.getArchive().getImageConfig();
|
||||
BuilderMetadata ephemeralMetadata = BuilderMetadata.fromImageConfig(config);
|
||||
assertThat(ephemeralMetadata.getCreatedBy().getName()).isEqualTo("Spring Boot");
|
||||
assertThat(ephemeralMetadata.getCreatedBy().getVersion()).isEqualTo("dev");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getArchiveHasTag() throws Exception {
|
||||
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.metadata, this.env);
|
||||
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.metadata, this.creator, this.env);
|
||||
ImageReference tag = builder.getArchive().getTag();
|
||||
assertThat(tag.toString()).startsWith("pack.local/builder/").endsWith(":latest");
|
||||
}
|
||||
@@ -95,13 +98,14 @@ class EphemeralBuilderTests extends AbstractJsonTests {
|
||||
@Test
|
||||
void getArchiveHasCreateDate() throws Exception {
|
||||
Clock clock = Clock.fixed(Instant.now(), ZoneOffset.UTC);
|
||||
EphemeralBuilder builder = new EphemeralBuilder(clock, this.owner, this.image, this.metadata, this.env);
|
||||
EphemeralBuilder builder = new EphemeralBuilder(clock, this.owner, this.image, this.metadata, this.creator,
|
||||
this.env);
|
||||
assertThat(builder.getArchive().getCreateDate()).isEqualTo(Instant.now(clock));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getArchiveContainsEnvLayer() throws Exception {
|
||||
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.metadata, this.env);
|
||||
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.metadata, this.creator, this.env);
|
||||
File folder = unpack(getLayer(builder.getArchive(), 0), "env");
|
||||
assertThat(new File(folder, "platform/env/spring")).usingCharset(StandardCharsets.UTF_8).hasContent("boot");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user