Skip to content

Commit 191dce3

Browse files
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
1 parent 97af0b2 commit 191dce3

File tree

12 files changed

+297
-57
lines changed

12 files changed

+297
-57
lines changed

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildRequest.java

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ public class BuildRequest {
4444

4545
private final ImageReference builder;
4646

47+
private final Creator creator;
48+
4749
private final Map<String, String> env;
4850

4951
private final boolean cleanCache;
@@ -59,13 +61,15 @@ public class BuildRequest {
5961
this.env = Collections.emptyMap();
6062
this.cleanCache = false;
6163
this.verboseLogging = false;
64+
this.creator = Creator.withVersion("");
6265
}
6366

6467
BuildRequest(ImageReference name, Function<Owner, TarArchive> applicationContent, ImageReference builder,
65-
Map<String, String> env, boolean cleanCache, boolean verboseLogging) {
68+
Creator creator, Map<String, String> env, boolean cleanCache, boolean verboseLogging) {
6669
this.name = name;
6770
this.applicationContent = applicationContent;
6871
this.builder = builder;
72+
this.creator = creator;
6973
this.env = env;
7074
this.cleanCache = cleanCache;
7175
this.verboseLogging = verboseLogging;
@@ -78,7 +82,18 @@ public class BuildRequest {
7882
*/
7983
public BuildRequest withBuilder(ImageReference builder) {
8084
Assert.notNull(builder, "Builder must not be null");
81-
return new BuildRequest(this.name, this.applicationContent, builder.inTaggedForm(), this.env, this.cleanCache,
85+
return new BuildRequest(this.name, this.applicationContent, builder.inTaggedForm(), this.creator, this.env,
86+
this.cleanCache, this.verboseLogging);
87+
}
88+
89+
/**
90+
* Return a new {@link BuildRequest} with an updated builder.
91+
* @param creator the new {@code Creator} to use
92+
* @return an updated build request
93+
*/
94+
public BuildRequest withCreator(Creator creator) {
95+
Assert.notNull(creator, "Creator must not be null");
96+
return new BuildRequest(this.name, this.applicationContent, this.builder, creator, this.env, this.cleanCache,
8297
this.verboseLogging);
8398
}
8499

@@ -93,8 +108,8 @@ public BuildRequest withEnv(String name, String value) {
93108
Assert.hasText(value, "Value must not be empty");
94109
Map<String, String> env = new LinkedHashMap<>(this.env);
95110
env.put(name, value);
96-
return new BuildRequest(this.name, this.applicationContent, this.builder, Collections.unmodifiableMap(env),
97-
this.cleanCache, this.verboseLogging);
111+
return new BuildRequest(this.name, this.applicationContent, this.builder, this.creator,
112+
Collections.unmodifiableMap(env), this.cleanCache, this.verboseLogging);
98113
}
99114

100115
/**
@@ -106,7 +121,7 @@ public BuildRequest withEnv(Map<String, String> env) {
106121
Assert.notNull(env, "Env must not be null");
107122
Map<String, String> updatedEnv = new LinkedHashMap<>(this.env);
108123
updatedEnv.putAll(env);
109-
return new BuildRequest(this.name, this.applicationContent, this.builder,
124+
return new BuildRequest(this.name, this.applicationContent, this.builder, this.creator,
110125
Collections.unmodifiableMap(updatedEnv), this.cleanCache, this.verboseLogging);
111126
}
112127

@@ -116,7 +131,7 @@ public BuildRequest withEnv(Map<String, String> env) {
116131
* @return an updated build request
117132
*/
118133
public BuildRequest withCleanCache(boolean cleanCache) {
119-
return new BuildRequest(this.name, this.applicationContent, this.builder, this.env, cleanCache,
134+
return new BuildRequest(this.name, this.applicationContent, this.builder, this.creator, this.env, cleanCache,
120135
this.verboseLogging);
121136
}
122137

@@ -126,8 +141,8 @@ public BuildRequest withCleanCache(boolean cleanCache) {
126141
* @return an updated build request
127142
*/
128143
public BuildRequest withVerboseLogging(boolean verboseLogging) {
129-
return new BuildRequest(this.name, this.applicationContent, this.builder, this.env, this.cleanCache,
130-
verboseLogging);
144+
return new BuildRequest(this.name, this.applicationContent, this.builder, this.creator, this.env,
145+
this.cleanCache, verboseLogging);
131146
}
132147

133148
/**
@@ -157,6 +172,14 @@ public ImageReference getBuilder() {
157172
return this.builder;
158173
}
159174

175+
/**
176+
* Return the {@link Creator} the builder should use.
177+
* @return the {@code Creator}
178+
*/
179+
public Creator getCreator() {
180+
return this.creator;
181+
}
182+
160183
/**
161184
* Return any env variable that should be passed to the builder.
162185
* @return the builder env

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/Builder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ public void build(BuildRequest request) throws DockerException, IOException {
6666
ImageReference runImageReference = getRunImageReference(builderMetadata.getStack());
6767
Image runImage = pullRunImage(request, runImageReference);
6868
assertHasExpectedStackId(runImage, stackId);
69-
EphemeralBuilder builder = new EphemeralBuilder(buildOwner, builderImage, builderMetadata, request.getEnv());
69+
EphemeralBuilder builder = new EphemeralBuilder(buildOwner, builderImage, builderMetadata, request.getCreator(),
70+
request.getEnv());
7071
this.docker.image().load(builder.getArchive(), UpdateListener.none());
7172
try {
7273
executeLifecycle(request, runImageReference, builder);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.buildpack.platform.build;
18+
19+
import org.springframework.util.Assert;
20+
21+
/**
22+
* Identifying information about the tooling that created a builder.
23+
*
24+
* @author Scott Frederick
25+
* @since 2.3.0
26+
*/
27+
public class Creator {
28+
29+
private final String version;
30+
31+
Creator(String version) {
32+
this.version = version;
33+
}
34+
35+
/**
36+
* Return the name of the builder creator.
37+
* @return the name
38+
*/
39+
public String getName() {
40+
return "Spring Boot";
41+
}
42+
43+
/**
44+
* Return the version of the builder creator.
45+
* @return the version
46+
*/
47+
public String getVersion() {
48+
return this.version;
49+
}
50+
51+
/**
52+
* Create a new {@code Creator} using the provided version.
53+
* @param version the creator version
54+
* @return a new creator instance
55+
*/
56+
public static Creator withVersion(String version) {
57+
Assert.notNull(version, "Version must not be null");
58+
return new Creator(version);
59+
}
60+
61+
@Override
62+
public String toString() {
63+
return getName() + " version " + getVersion();
64+
}
65+
66+
}

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/EphemeralBuilder.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,20 @@ class EphemeralBuilder {
4242

4343
private final ImageArchive archive;
4444

45+
private final Creator creator;
46+
4547
/**
4648
* Create a new {@link EphemeralBuilder} instance.
4749
* @param buildOwner the build owner
4850
* @param builderImage the image
4951
* @param builderMetadata the builder metadata
52+
* @param creator the builder creator
5053
* @param env the builder env
5154
* @throws IOException on IO error
5255
*/
53-
EphemeralBuilder(BuildOwner buildOwner, Image builderImage, BuilderMetadata builderMetadata,
56+
EphemeralBuilder(BuildOwner buildOwner, Image builderImage, BuilderMetadata builderMetadata, Creator creator,
5457
Map<String, String> env) throws IOException {
55-
this(Clock.systemUTC(), buildOwner, builderImage, builderMetadata, env);
58+
this(Clock.systemUTC(), buildOwner, builderImage, builderMetadata, creator, env);
5659
}
5760

5861
/**
@@ -61,13 +64,15 @@ class EphemeralBuilder {
6164
* @param buildOwner the build owner
6265
* @param builderImage the image
6366
* @param builderMetadata the builder metadata
67+
* @param creator the builder creator
6468
* @param env the builder env
6569
* @throws IOException on IO error
6670
*/
6771
EphemeralBuilder(Clock clock, BuildOwner buildOwner, Image builderImage, BuilderMetadata builderMetadata,
68-
Map<String, String> env) throws IOException {
72+
Creator creator, Map<String, String> env) throws IOException {
6973
ImageReference name = ImageReference.random("pack.local/builder/").inTaggedForm();
7074
this.buildOwner = buildOwner;
75+
this.creator = creator;
7176
this.builderMetadata = builderMetadata.copy(this::updateMetadata);
7277
this.archive = ImageArchive.from(builderImage, (update) -> {
7378
update.withUpdatedConfig(this.builderMetadata::attachTo);
@@ -80,7 +85,7 @@ class EphemeralBuilder {
8085
}
8186

8287
private void updateMetadata(BuilderMetadata.Update update) {
83-
update.withCreatedBy("Spring Boot", "dev");
88+
update.withCreatedBy(this.creator.getName(), this.creator.getVersion());
8489
}
8590

8691
private Layer getEnvLayer(Map<String, String> env) throws IOException {

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildRequestTests.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
* Tests for {@link BuildRequest}.
4343
*
4444
* @author Phillip Webb
45+
* @author Scott Frederick
4546
*/
4647
public class BuildRequestTests {
4748

@@ -97,6 +98,16 @@ void withBuilderUpdatesBuilder() throws IOException {
9798
assertThat(request.getBuilder().toString()).isEqualTo("docker.io/spring/builder:latest");
9899
}
99100

101+
@Test
102+
void withCreatorUpdatesCreator() throws IOException {
103+
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar"));
104+
BuildRequest withCreator = request.withCreator(Creator.withVersion("1.0.0"));
105+
assertThat(request.getCreator().getName()).isEqualTo("Spring Boot");
106+
assertThat(request.getCreator().getVersion()).isEqualTo("");
107+
assertThat(withCreator.getCreator().getName()).isEqualTo("Spring Boot");
108+
assertThat(withCreator.getCreator().getVersion()).isEqualTo("1.0.0");
109+
}
110+
100111
@Test
101112
void withEnvAddsEnvEntry() throws IOException {
102113
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar"));

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/EphemeralBuilderTests.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ class EphemeralBuilderTests extends AbstractJsonTests {
6262

6363
private Map<String, String> env;
6464

65+
private Creator creator = Creator.withVersion("dev");
66+
6567
@BeforeEach
6668
void setup() throws Exception {
6769
this.image = Image.of(getContent("image.json"));
@@ -71,37 +73,39 @@ void setup() throws Exception {
7173

7274
@Test
7375
void getNameHasRandomName() throws Exception {
74-
EphemeralBuilder b1 = new EphemeralBuilder(this.owner, this.image, this.metadata, this.env);
75-
EphemeralBuilder b2 = new EphemeralBuilder(this.owner, this.image, this.metadata, this.env);
76+
EphemeralBuilder b1 = new EphemeralBuilder(this.owner, this.image, this.metadata, this.creator, this.env);
77+
EphemeralBuilder b2 = new EphemeralBuilder(this.owner, this.image, this.metadata, this.creator, this.env);
7678
assertThat(b1.getName().toString()).startsWith("pack.local/builder/").endsWith(":latest");
7779
assertThat(b1.getName().toString()).isNotEqualTo(b2.getName().toString());
7880
}
7981

8082
@Test
8183
void getArchiveHasCreatedByConfig() throws Exception {
82-
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.metadata, this.env);
84+
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.metadata, this.creator, this.env);
8385
ImageConfig config = builder.getArchive().getImageConfig();
8486
BuilderMetadata ephemeralMetadata = BuilderMetadata.fromImageConfig(config);
8587
assertThat(ephemeralMetadata.getCreatedBy().getName()).isEqualTo("Spring Boot");
88+
assertThat(ephemeralMetadata.getCreatedBy().getVersion()).isEqualTo("dev");
8689
}
8790

8891
@Test
8992
void getArchiveHasTag() throws Exception {
90-
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.metadata, this.env);
93+
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.metadata, this.creator, this.env);
9194
ImageReference tag = builder.getArchive().getTag();
9295
assertThat(tag.toString()).startsWith("pack.local/builder/").endsWith(":latest");
9396
}
9497

9598
@Test
9699
void getArchiveHasCreateDate() throws Exception {
97100
Clock clock = Clock.fixed(Instant.now(), ZoneOffset.UTC);
98-
EphemeralBuilder builder = new EphemeralBuilder(clock, this.owner, this.image, this.metadata, this.env);
101+
EphemeralBuilder builder = new EphemeralBuilder(clock, this.owner, this.image, this.metadata, this.creator,
102+
this.env);
99103
assertThat(builder.getArchive().getCreateDate()).isEqualTo(Instant.now(clock));
100104
}
101105

102106
@Test
103107
void getArchiveContainsEnvLayer() throws Exception {
104-
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.metadata, this.env);
108+
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.metadata, this.creator, this.env);
105109
File folder = unpack(getLayer(builder.getArchive(), 0), "env");
106110
assertThat(new File(folder, "platform/env/spring")).usingCharset(StandardCharsets.UTF_8).hasContent("boot");
107111
}

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,8 @@
1616

1717
package org.springframework.boot.gradle.plugin;
1818

19-
import java.io.File;
20-
import java.io.IOException;
21-
import java.net.JarURLConnection;
22-
import java.net.URL;
23-
import java.net.URLConnection;
2419
import java.util.Arrays;
2520
import java.util.List;
26-
import java.util.jar.Attributes;
27-
import java.util.jar.JarFile;
2821

2922
import org.gradle.api.GradleException;
3023
import org.gradle.api.Plugin;
@@ -45,11 +38,12 @@
4538
* @author Dave Syer
4639
* @author Andy Wilkinson
4740
* @author Danny Hyun
41+
* @author Scott Frederick
4842
* @since 1.2.7
4943
*/
5044
public class SpringBootPlugin implements Plugin<Project> {
5145

52-
private static final String SPRING_BOOT_VERSION = determineSpringBootVersion();
46+
private static final String SPRING_BOOT_VERSION = VersionExtractor.forClass(DependencyManagementPluginAction.class);
5347

5448
/**
5549
* The name of the {@link Configuration} that contains Spring Boot archives.
@@ -135,29 +129,4 @@ private void unregisterUnresolvedDependenciesAnalyzer(Project project) {
135129
project.getGradle().buildFinished((buildResult) -> unresolvedDependenciesAnalyzer.buildFinished(project));
136130
}
137131

138-
private static String determineSpringBootVersion() {
139-
String implementationVersion = DependencyManagementPluginAction.class.getPackage().getImplementationVersion();
140-
if (implementationVersion != null) {
141-
return implementationVersion;
142-
}
143-
URL codeSourceLocation = DependencyManagementPluginAction.class.getProtectionDomain().getCodeSource()
144-
.getLocation();
145-
try {
146-
URLConnection connection = codeSourceLocation.openConnection();
147-
if (connection instanceof JarURLConnection) {
148-
return getImplementationVersion(((JarURLConnection) connection).getJarFile());
149-
}
150-
try (JarFile jarFile = new JarFile(new File(codeSourceLocation.toURI()))) {
151-
return getImplementationVersion(jarFile);
152-
}
153-
}
154-
catch (Exception ex) {
155-
return null;
156-
}
157-
}
158-
159-
private static String getImplementationVersion(JarFile jarFile) throws IOException {
160-
return jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION);
161-
}
162-
163132
}

0 commit comments

Comments
 (0)