Skip to content

Commit 079357d

Browse files
Merge pull request #27613 from ceccone
* pr/27613: Polish "Add option to create tags for a built image" Add option to create tags for a built image Closes gh-27613
2 parents ca69c8b + 64c4900 commit 079357d

File tree

22 files changed

+438
-21
lines changed

22 files changed

+438
-21
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
* @author Phillip Webb
3232
* @author Scott Frederick
3333
* @author Andrey Shlykov
34+
* @author Rafael Ceccone
3435
* @since 2.3.0
3536
*/
3637
public abstract class AbstractBuildLog implements BuildLog {
@@ -89,6 +90,12 @@ public void executedLifecycle(BuildRequest request) {
8990
log();
9091
}
9192

93+
@Override
94+
public void taggedImage(ImageReference tag) {
95+
log("Successfully created image tag '" + tag + "'");
96+
log();
97+
}
98+
9299
private String getDigest(Image image) {
93100
List<String> digests = image.getDigests();
94101
return (digests.isEmpty() ? "" : digests.get(0));

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
* @author Phillip Webb
3232
* @author Scott Frederick
3333
* @author Andrey Shlykov
34+
* @author Rafael Ceccone
3435
* @since 2.3.0
3536
* @see #toSystemOut()
3637
*/
@@ -99,6 +100,12 @@ public interface BuildLog {
99100
*/
100101
void executedLifecycle(BuildRequest request);
101102

103+
/**
104+
* Log that a tag has been created.
105+
* @param tag the tag reference
106+
*/
107+
void taggedImage(ImageReference tag);
108+
102109
/**
103110
* Factory method that returns a {@link BuildLog} the outputs to {@link System#out}.
104111
* @return a build log instance that logs to system out

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

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
* @author Scott Frederick
3838
* @author Andrey Shlykov
3939
* @author Jeroen Meijer
40+
* @author Rafael Ceccone
4041
* @since 2.3.0
4142
*/
4243
public class BuildRequest {
@@ -71,6 +72,8 @@ public class BuildRequest {
7172

7273
private final String network;
7374

75+
private final List<ImageReference> tags;
76+
7477
BuildRequest(ImageReference name, Function<Owner, TarArchive> applicationContent) {
7578
Assert.notNull(name, "Name must not be null");
7679
Assert.notNull(applicationContent, "ApplicationContent must not be null");
@@ -87,12 +90,13 @@ public class BuildRequest {
8790
this.buildpacks = Collections.emptyList();
8891
this.bindings = Collections.emptyList();
8992
this.network = null;
93+
this.tags = Collections.emptyList();
9094
}
9195

9296
BuildRequest(ImageReference name, Function<Owner, TarArchive> applicationContent, ImageReference builder,
9397
ImageReference runImage, Creator creator, Map<String, String> env, boolean cleanCache,
9498
boolean verboseLogging, PullPolicy pullPolicy, boolean publish, List<BuildpackReference> buildpacks,
95-
List<Binding> bindings, String network) {
99+
List<Binding> bindings, String network, List<ImageReference> tags) {
96100
this.name = name;
97101
this.applicationContent = applicationContent;
98102
this.builder = builder;
@@ -106,6 +110,7 @@ public class BuildRequest {
106110
this.buildpacks = buildpacks;
107111
this.bindings = bindings;
108112
this.network = network;
113+
this.tags = tags;
109114
}
110115

111116
/**
@@ -117,7 +122,7 @@ public BuildRequest withBuilder(ImageReference builder) {
117122
Assert.notNull(builder, "Builder must not be null");
118123
return new BuildRequest(this.name, this.applicationContent, builder.inTaggedOrDigestForm(), this.runImage,
119124
this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish,
120-
this.buildpacks, this.bindings, this.network);
125+
this.buildpacks, this.bindings, this.network, this.tags);
121126
}
122127

123128
/**
@@ -128,7 +133,7 @@ public BuildRequest withBuilder(ImageReference builder) {
128133
public BuildRequest withRunImage(ImageReference runImageName) {
129134
return new BuildRequest(this.name, this.applicationContent, this.builder, runImageName.inTaggedOrDigestForm(),
130135
this.creator, this.env, this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish,
131-
this.buildpacks, this.bindings, this.network);
136+
this.buildpacks, this.bindings, this.network, this.tags);
132137
}
133138

134139
/**
@@ -140,7 +145,7 @@ public BuildRequest withCreator(Creator creator) {
140145
Assert.notNull(creator, "Creator must not be null");
141146
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, creator, this.env,
142147
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
143-
this.network);
148+
this.network, this.tags);
144149
}
145150

146151
/**
@@ -156,7 +161,7 @@ public BuildRequest withEnv(String name, String value) {
156161
env.put(name, value);
157162
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator,
158163
Collections.unmodifiableMap(env), this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish,
159-
this.buildpacks, this.bindings, this.network);
164+
this.buildpacks, this.bindings, this.network, this.tags);
160165
}
161166

162167
/**
@@ -170,7 +175,7 @@ public BuildRequest withEnv(Map<String, String> env) {
170175
updatedEnv.putAll(env);
171176
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator,
172177
Collections.unmodifiableMap(updatedEnv), this.cleanCache, this.verboseLogging, this.pullPolicy,
173-
this.publish, this.buildpacks, this.bindings, this.network);
178+
this.publish, this.buildpacks, this.bindings, this.network, this.tags);
174179
}
175180

176181
/**
@@ -181,7 +186,7 @@ public BuildRequest withEnv(Map<String, String> env) {
181186
public BuildRequest withCleanCache(boolean cleanCache) {
182187
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
183188
cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
184-
this.network);
189+
this.network, this.tags);
185190
}
186191

187192
/**
@@ -192,7 +197,7 @@ public BuildRequest withCleanCache(boolean cleanCache) {
192197
public BuildRequest withVerboseLogging(boolean verboseLogging) {
193198
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
194199
this.cleanCache, verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
195-
this.network);
200+
this.network, this.tags);
196201
}
197202

198203
/**
@@ -203,7 +208,7 @@ public BuildRequest withVerboseLogging(boolean verboseLogging) {
203208
public BuildRequest withPullPolicy(PullPolicy pullPolicy) {
204209
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
205210
this.cleanCache, this.verboseLogging, pullPolicy, this.publish, this.buildpacks, this.bindings,
206-
this.network);
211+
this.network, this.tags);
207212
}
208213

209214
/**
@@ -214,7 +219,7 @@ public BuildRequest withPullPolicy(PullPolicy pullPolicy) {
214219
public BuildRequest withPublish(boolean publish) {
215220
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
216221
this.cleanCache, this.verboseLogging, this.pullPolicy, publish, this.buildpacks, this.bindings,
217-
this.network);
222+
this.network, this.tags);
218223
}
219224

220225
/**
@@ -238,7 +243,7 @@ public BuildRequest withBuildpacks(List<BuildpackReference> buildpacks) {
238243
Assert.notNull(buildpacks, "Buildpacks must not be null");
239244
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
240245
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, buildpacks, this.bindings,
241-
this.network);
246+
this.network, this.tags);
242247
}
243248

244249
/**
@@ -262,7 +267,7 @@ public BuildRequest withBindings(List<Binding> bindings) {
262267
Assert.notNull(bindings, "Bindings must not be null");
263268
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
264269
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, bindings,
265-
this.network);
270+
this.network, this.tags);
266271
}
267272

268273
/**
@@ -274,7 +279,29 @@ public BuildRequest withBindings(List<Binding> bindings) {
274279
public BuildRequest withNetwork(String network) {
275280
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
276281
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
277-
network);
282+
network, this.tags);
283+
}
284+
285+
/**
286+
* Return a new {@link BuildRequest} with updated tags.
287+
* @param tags a collection of tags to be created for the built image
288+
* @return an updated build request
289+
*/
290+
public BuildRequest withTags(ImageReference... tags) {
291+
Assert.notEmpty(tags, "Tags must not be empty");
292+
return withTags(Arrays.asList(tags));
293+
}
294+
295+
/**
296+
* Return a new {@link BuildRequest} with updated tags.
297+
* @param tags a collection of tags to be created for the built image
298+
* @return an updated build request
299+
*/
300+
public BuildRequest withTags(List<ImageReference> tags) {
301+
Assert.notNull(tags, "Tags must not be null");
302+
return new BuildRequest(this.name, this.applicationContent, this.builder, this.runImage, this.creator, this.env,
303+
this.cleanCache, this.verboseLogging, this.pullPolicy, this.publish, this.buildpacks, this.bindings,
304+
this.network, tags);
278305
}
279306

280307
/**
@@ -386,6 +413,14 @@ public String getNetwork() {
386413
return this.network;
387414
}
388415

416+
/**
417+
* Return the collection of tags that should be created.
418+
* @return the tags
419+
*/
420+
public List<ImageReference> getTags() {
421+
return this.tags;
422+
}
423+
389424
/**
390425
* Factory method to create a new {@link BuildRequest} from a JAR file.
391426
* @param jarFile the source jar file

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
* @author Phillip Webb
4242
* @author Scott Frederick
4343
* @author Andrey Shlykov
44+
* @author Rafael Ceccone
4445
* @since 2.3.0
4546
*/
4647
public class Builder {
@@ -110,8 +111,9 @@ public void build(BuildRequest request) throws DockerEngineException, IOExceptio
110111
this.docker.image().load(ephemeralBuilder.getArchive(), UpdateListener.none());
111112
try {
112113
executeLifecycle(request, ephemeralBuilder);
114+
tagImage(request.getName(), request.getTags());
113115
if (request.isPublish()) {
114-
pushImage(request.getName());
116+
pushImages(request.getName(), request.getTags());
115117
}
116118
}
117119
finally {
@@ -150,6 +152,20 @@ private void executeLifecycle(BuildRequest request, EphemeralBuilder builder) th
150152
}
151153
}
152154

155+
private void tagImage(ImageReference sourceReference, List<ImageReference> tags) throws IOException {
156+
for (ImageReference tag : tags) {
157+
this.docker.image().tag(sourceReference, tag);
158+
this.log.taggedImage(tag);
159+
}
160+
}
161+
162+
private void pushImages(ImageReference name, List<ImageReference> tags) throws IOException {
163+
pushImage(name);
164+
for (ImageReference tag : tags) {
165+
pushImage(tag);
166+
}
167+
}
168+
153169
private void pushImage(ImageReference reference) throws IOException {
154170
Consumer<TotalProgressEvent> progressConsumer = this.log.pushingImage(reference);
155171
TotalProgressPushListener listener = new TotalProgressPushListener(progressConsumer);

spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
*
5353
* @author Phillip Webb
5454
* @author Scott Frederick
55+
* @author Rafael Ceccone
5556
* @since 2.3.0
5657
*/
5758
public class DockerApi {
@@ -283,7 +284,7 @@ public void remove(ImageReference reference, boolean force) throws IOException {
283284
Assert.notNull(reference, "Reference must not be null");
284285
Collection<String> params = force ? FORCE_PARAMS : Collections.emptySet();
285286
URI uri = buildUrl("/images/" + reference, params);
286-
http().delete(uri);
287+
http().delete(uri).close();
287288
}
288289

289290
/**
@@ -300,6 +301,13 @@ public Image inspect(ImageReference reference) throws IOException {
300301
}
301302
}
302303

304+
public void tag(ImageReference sourceReference, ImageReference targetReference) throws IOException {
305+
Assert.notNull(sourceReference, "SourceReference must not be null");
306+
Assert.notNull(targetReference, "TargetReference must not be null");
307+
URI uri = buildUrl("/images/" + sourceReference + "/tag", "repo", targetReference.toString());
308+
http().post(uri).close();
309+
}
310+
303311
}
304312

305313
/**
@@ -348,7 +356,7 @@ private void uploadContainerContent(ContainerReference reference, ContainerConte
348356
public void start(ContainerReference reference) throws IOException {
349357
Assert.notNull(reference, "Reference must not be null");
350358
URI uri = buildUrl("/containers/" + reference + "/start");
351-
http().post(uri);
359+
http().post(uri).close();
352360
}
353361

354362
/**
@@ -396,7 +404,7 @@ public void remove(ContainerReference reference, boolean force) throws IOExcepti
396404
Assert.notNull(reference, "Reference must not be null");
397405
Collection<String> params = force ? FORCE_PARAMS : Collections.emptySet();
398406
URI uri = buildUrl("/containers/" + reference, params);
399-
http().delete(uri);
407+
http().delete(uri).close();
400408
}
401409

402410
}
@@ -419,7 +427,7 @@ public void delete(VolumeName name, boolean force) throws IOException {
419427
Assert.notNull(name, "Name must not be null");
420428
Collection<String> params = force ? FORCE_PARAMS : Collections.emptySet();
421429
URI uri = buildUrl("/volumes/" + name, params);
422-
http().delete(uri);
430+
http().delete(uri).close();
423431
}
424432

425433
}

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
* @author Phillip Webb
4747
* @author Scott Frederick
4848
* @author Jeroen Meijer
49+
* @author Rafael Ceccone
4950
*/
5051
class BuildRequestTests {
5152

@@ -206,6 +207,25 @@ void withNetworkUpdatesNetwork() throws IOException {
206207
assertThat(request.getNetwork()).isEqualTo("test");
207208
}
208209

210+
@Test
211+
void withTagsAddsTags() throws IOException {
212+
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar"));
213+
BuildRequest witTags = request.withTags(ImageReference.of("docker.io/library/my-app:latest"),
214+
ImageReference.of("example.com/custom/my-app:0.0.1"),
215+
ImageReference.of("example.com/custom/my-app:latest"));
216+
assertThat(request.getTags()).isEmpty();
217+
assertThat(witTags.getTags()).containsExactly(ImageReference.of("docker.io/library/my-app:latest"),
218+
ImageReference.of("example.com/custom/my-app:0.0.1"),
219+
ImageReference.of("example.com/custom/my-app:latest"));
220+
}
221+
222+
@Test
223+
void withTagsWhenTagsIsNullThrowsException() throws IOException {
224+
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar"));
225+
assertThatIllegalArgumentException().isThrownBy(() -> request.withTags((List<ImageReference>) null))
226+
.withMessage("Tags must not be null");
227+
}
228+
209229
private void hasExpectedJarContent(TarArchive archive) {
210230
try {
211231
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

0 commit comments

Comments
 (0)