Skip to content

Commit 6d93d73

Browse files
committed
Cherry-pick release script updates
Cherry-pick release script updates from 2.3.x See gh-21474
1 parent 5abca71 commit 6d93d73

File tree

20 files changed

+265
-100
lines changed

20 files changed

+265
-100
lines changed

ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/artifactory/ArtifactoryService.java

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,16 @@
1717
package io.spring.concourse.releasescripts.artifactory;
1818

1919
import java.net.URI;
20+
import java.time.Duration;
21+
import java.util.Set;
2022

2123
import io.spring.concourse.releasescripts.ReleaseInfo;
2224
import io.spring.concourse.releasescripts.artifactory.payload.BuildInfoResponse;
2325
import io.spring.concourse.releasescripts.artifactory.payload.DistributionRequest;
2426
import io.spring.concourse.releasescripts.artifactory.payload.PromotionRequest;
2527
import io.spring.concourse.releasescripts.bintray.BintrayService;
26-
import io.spring.concourse.releasescripts.system.ConsoleLogger;
28+
import org.slf4j.Logger;
29+
import org.slf4j.LoggerFactory;
2730

2831
import org.springframework.boot.web.client.RestTemplateBuilder;
2932
import org.springframework.http.MediaType;
@@ -42,6 +45,8 @@
4245
@Component
4346
public class ArtifactoryService {
4447

48+
private static final Logger logger = LoggerFactory.getLogger(ArtifactoryService.class);
49+
4550
private static final String ARTIFACTORY_URL = "https://repo.spring.io";
4651

4752
private static final String PROMOTION_URL = ARTIFACTORY_URL + "/api/build/promote/";
@@ -56,8 +61,6 @@ public class ArtifactoryService {
5661

5762
private final BintrayService bintrayService;
5863

59-
private static final ConsoleLogger console = new ConsoleLogger();
60-
6164
public ArtifactoryService(RestTemplateBuilder builder, ArtifactoryProperties artifactoryProperties,
6265
BintrayService bintrayService) {
6366
this.bintrayService = bintrayService;
@@ -78,54 +81,66 @@ public void promote(String targetRepo, ReleaseInfo releaseInfo) {
7881
PromotionRequest request = getPromotionRequest(targetRepo);
7982
String buildName = releaseInfo.getBuildName();
8083
String buildNumber = releaseInfo.getBuildNumber();
81-
console.log("Promoting " + buildName + "/" + buildNumber + " to " + request.getTargetRepo());
84+
logger.info("Promoting " + buildName + "/" + buildNumber + " to " + request.getTargetRepo());
8285
RequestEntity<PromotionRequest> requestEntity = RequestEntity
8386
.post(URI.create(PROMOTION_URL + buildName + "/" + buildNumber)).contentType(MediaType.APPLICATION_JSON)
8487
.body(request);
8588
try {
8689
this.restTemplate.exchange(requestEntity, String.class);
90+
logger.debug("Promotion complete");
8791
}
8892
catch (HttpClientErrorException ex) {
8993
boolean isAlreadyPromoted = isAlreadyPromoted(buildName, buildNumber, request.getTargetRepo());
9094
if (isAlreadyPromoted) {
91-
console.log("Already promoted.");
95+
logger.info("Already promoted.");
9296
}
9397
else {
94-
console.log("Promotion failed.");
98+
logger.info("Promotion failed.");
9599
throw ex;
96100
}
97101
}
98102
}
99103

100104
private boolean isAlreadyPromoted(String buildName, String buildNumber, String targetRepo) {
101105
try {
106+
logger.debug("Checking if alreay promoted");
102107
ResponseEntity<BuildInfoResponse> entity = this.restTemplate
103108
.getForEntity(BUILD_INFO_URL + buildName + "/" + buildNumber, BuildInfoResponse.class);
104109
BuildInfoResponse.Status status = entity.getBody().getBuildInfo().getStatuses()[0];
110+
logger.debug("Reutned repository " + status.getRepository() + " expecting " + targetRepo);
105111
return status.getRepository().equals(targetRepo);
106112
}
107113
catch (HttpClientErrorException ex) {
114+
logger.debug("Client error, assuming not promoted");
108115
return false;
109116
}
110117
}
111118

112119
/**
113120
* Deploy builds from Artifactory to Bintray.
114121
* @param sourceRepo the source repo in Artifactory.
122+
* @param releaseInfo the resease info
123+
* @param artifactDigests the artifact digests
115124
*/
116-
public void distribute(String sourceRepo, ReleaseInfo releaseInfo) {
125+
public void distribute(String sourceRepo, ReleaseInfo releaseInfo, Set<String> artifactDigests) {
126+
logger.debug("Attempting distribute via Artifactory");
127+
if (this.bintrayService.isDistributionComplete(releaseInfo, artifactDigests, Duration.ofMinutes(2))) {
128+
logger.info("Distribution already complete");
129+
return;
130+
}
117131
DistributionRequest request = new DistributionRequest(new String[] { sourceRepo });
118132
RequestEntity<DistributionRequest> requestEntity = RequestEntity
119133
.post(URI.create(DISTRIBUTION_URL + releaseInfo.getBuildName() + "/" + releaseInfo.getBuildNumber()))
120134
.contentType(MediaType.APPLICATION_JSON).body(request);
121135
try {
122136
this.restTemplate.exchange(requestEntity, Object.class);
137+
logger.debug("Distribution call completed");
123138
}
124139
catch (HttpClientErrorException ex) {
125-
console.log("Failed to distribute.");
140+
logger.info("Failed to distribute.");
126141
throw ex;
127142
}
128-
if (!this.bintrayService.isDistributionComplete(releaseInfo)) {
143+
if (!this.bintrayService.isDistributionComplete(releaseInfo, artifactDigests, Duration.ofMinutes(60))) {
129144
throw new DistributionTimeoutException("Distribution timed out.");
130145
}
131146

ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/artifactory/payload/BuildInfoResponse.java

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616

1717
package io.spring.concourse.releasescripts.artifactory.payload;
1818

19+
import java.util.Arrays;
20+
import java.util.Set;
21+
import java.util.function.Predicate;
22+
import java.util.stream.Collectors;
23+
import java.util.stream.Stream;
24+
1925
/**
2026
* Represents the response from Artifactory's buildInfo endpoint.
2127
*
@@ -54,7 +60,7 @@ public void setStatuses(Status[] statuses) {
5460
}
5561

5662
public String getName() {
57-
return name;
63+
return this.name;
5864
}
5965

6066
public void setName(String name) {
@@ -83,6 +89,14 @@ public String getVersion() {
8389

8490
public void setVersion(String version) {
8591
this.version = version;
92+
93+
}
94+
95+
public Set<String> getArtifactDigests(Predicate<Artifact> predicate) {
96+
return Arrays.stream(this.modules).flatMap((module) -> {
97+
Artifact[] artifacts = module.getArtifacts();
98+
return (artifacts != null) ? Arrays.stream(artifacts) : Stream.empty();
99+
}).filter(predicate).map(Artifact::getSha256).collect(Collectors.toSet());
86100
}
87101

88102
}
@@ -105,6 +119,8 @@ public static class Module {
105119

106120
private String id;
107121

122+
private Artifact[] artifacts;
123+
108124
public String getId() {
109125
return this.id;
110126
}
@@ -113,6 +129,38 @@ public void setId(String id) {
113129
this.id = id;
114130
}
115131

132+
public Artifact[] getArtifacts() {
133+
return this.artifacts;
134+
}
135+
136+
public void setArtifacts(Artifact[] artifacts) {
137+
this.artifacts = artifacts;
138+
}
139+
140+
}
141+
142+
public static class Artifact {
143+
144+
private String name;
145+
146+
private String sha256;
147+
148+
public String getName() {
149+
return this.name;
150+
}
151+
152+
public void setName(String name) {
153+
this.name = name;
154+
}
155+
156+
public String getSha256() {
157+
return this.sha256;
158+
}
159+
160+
public void setSha256(String sha256) {
161+
this.sha256 = sha256;
162+
}
163+
116164
}
117165

118166
}

ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/bintray/BintrayService.java

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,18 @@
1717
package io.spring.concourse.releasescripts.bintray;
1818

1919
import java.net.URI;
20-
import java.util.Objects;
21-
import java.util.concurrent.TimeUnit;
20+
import java.time.Duration;
21+
import java.util.HashSet;
22+
import java.util.Set;
2223

2324
import io.spring.concourse.releasescripts.ReleaseInfo;
2425
import io.spring.concourse.releasescripts.sonatype.SonatypeProperties;
2526
import io.spring.concourse.releasescripts.sonatype.SonatypeService;
26-
import io.spring.concourse.releasescripts.system.ConsoleLogger;
2727
import org.awaitility.core.ConditionTimeoutException;
28+
import org.slf4j.Logger;
29+
import org.slf4j.LoggerFactory;
2830

2931
import org.springframework.boot.web.client.RestTemplateBuilder;
30-
import org.springframework.http.HttpStatus;
3132
import org.springframework.http.MediaType;
3233
import org.springframework.http.RequestEntity;
3334
import org.springframework.stereotype.Component;
@@ -45,6 +46,8 @@
4546
@Component
4647
public class BintrayService {
4748

49+
private static final Logger logger = LoggerFactory.getLogger(BintrayService.class);
50+
4851
private static final String BINTRAY_URL = "https://api.bintray.com/";
4952

5053
private static final String GRADLE_PLUGIN_REQUEST = "[ { \"name\": \"gradle-plugin\", \"values\": [\"org.springframework.boot:org.springframework.boot:spring-boot-gradle-plugin\"] } ]";
@@ -57,8 +60,6 @@ public class BintrayService {
5760

5861
private final SonatypeService sonatypeService;
5962

60-
private static final ConsoleLogger console = new ConsoleLogger();
61-
6263
public BintrayService(RestTemplateBuilder builder, BintrayProperties bintrayProperties,
6364
SonatypeProperties sonatypeProperties, SonatypeService sonatypeService) {
6465
this.bintrayProperties = bintrayProperties;
@@ -72,32 +73,48 @@ public BintrayService(RestTemplateBuilder builder, BintrayProperties bintrayProp
7273
this.restTemplate = builder.build();
7374
}
7475

75-
public boolean isDistributionComplete(ReleaseInfo releaseInfo) {
76-
RequestEntity<Void> allFilesRequest = getRequest(releaseInfo, 1);
77-
Object[] allFiles = waitAtMost(5, TimeUnit.MINUTES).with().pollDelay(20, TimeUnit.SECONDS).until(() -> {
78-
try {
79-
return this.restTemplate.exchange(allFilesRequest, Object[].class).getBody();
80-
}
81-
catch (HttpClientErrorException ex) {
82-
if (ex.getStatusCode() != HttpStatus.NOT_FOUND) {
83-
throw ex;
84-
}
85-
return null;
86-
}
87-
}, Objects::nonNull);
88-
RequestEntity<Void> publishedFilesRequest = getRequest(releaseInfo, 0);
76+
public boolean isDistributionComplete(ReleaseInfo releaseInfo, Set<String> requiredDigets, Duration timeout) {
77+
return isDistributionComplete(releaseInfo, requiredDigets, timeout, Duration.ofSeconds(20));
78+
}
79+
80+
public boolean isDistributionComplete(ReleaseInfo releaseInfo, Set<String> requiredDigets, Duration timeout,
81+
Duration pollInterval) {
82+
logger.debug("Checking if distribution is complete");
83+
RequestEntity<Void> request = getRequest(releaseInfo, 0);
8984
try {
90-
waitAtMost(40, TimeUnit.MINUTES).with().pollDelay(20, TimeUnit.SECONDS).until(() -> {
91-
Object[] publishedFiles = this.restTemplate.exchange(publishedFilesRequest, Object[].class).getBody();
92-
return allFiles.length == publishedFiles.length;
85+
waitAtMost(timeout).with().pollDelay(Duration.ZERO).pollInterval(pollInterval).until(() -> {
86+
logger.debug("Checking bintray");
87+
PackageFile[] published = this.restTemplate.exchange(request, PackageFile[].class).getBody();
88+
return hasPublishedAll(published, requiredDigets);
9389
});
9490
}
9591
catch (ConditionTimeoutException ex) {
92+
logger.debug("Timeout checking bintray");
9693
return false;
9794
}
9895
return true;
9996
}
10097

98+
private boolean hasPublishedAll(PackageFile[] published, Set<String> requiredDigets) {
99+
if (published == null || published.length == 0) {
100+
logger.debug("Bintray returned no published files");
101+
return false;
102+
}
103+
Set<String> remaining = new HashSet<>(requiredDigets);
104+
for (PackageFile publishedFile : published) {
105+
logger.debug(
106+
"Found published file " + publishedFile.getName() + " with digest " + publishedFile.getSha256());
107+
remaining.remove(publishedFile.getSha256());
108+
}
109+
if (remaining.isEmpty()) {
110+
logger.debug("Found all required digests");
111+
return true;
112+
}
113+
logger.debug("Some digests have not been published:");
114+
remaining.forEach(logger::debug);
115+
return false;
116+
}
117+
101118
private RequestEntity<Void> getRequest(ReleaseInfo releaseInfo, int includeUnpublished) {
102119
return RequestEntity.get(URI.create(BINTRAY_URL + "packages/" + this.bintrayProperties.getSubject() + "/"
103120
+ this.bintrayProperties.getRepo() + "/" + releaseInfo.getGroupId() + "/versions/"
@@ -109,16 +126,18 @@ private RequestEntity<Void> getRequest(ReleaseInfo releaseInfo, int includeUnpub
109126
* @param releaseInfo the release information
110127
*/
111128
public void publishGradlePlugin(ReleaseInfo releaseInfo) {
129+
logger.debug("Publishing Gradle Pluging");
112130
RequestEntity<String> requestEntity = RequestEntity
113131
.post(URI.create(BINTRAY_URL + "packages/" + this.bintrayProperties.getSubject() + "/"
114132
+ this.bintrayProperties.getRepo() + "/" + releaseInfo.getGroupId() + "/versions/"
115133
+ releaseInfo.getVersion() + "/attributes"))
116134
.contentType(MediaType.APPLICATION_JSON).body(GRADLE_PLUGIN_REQUEST);
117135
try {
118136
this.restTemplate.exchange(requestEntity, Object.class);
137+
logger.debug("Publishing Gradle Pluging complete");
119138
}
120139
catch (HttpClientErrorException ex) {
121-
console.log("Failed to add attribute to gradle plugin.");
140+
logger.info("Failed to add attribute to gradle plugin.");
122141
throw ex;
123142
}
124143
}
@@ -128,8 +147,9 @@ public void publishGradlePlugin(ReleaseInfo releaseInfo) {
128147
* @param releaseInfo the release information
129148
*/
130149
public void syncToMavenCentral(ReleaseInfo releaseInfo) {
131-
console.log("Calling Bintray to sync to Sonatype");
150+
logger.info("Calling Bintray to sync to Sonatype");
132151
if (this.sonatypeService.artifactsPublished(releaseInfo)) {
152+
logger.info("Artifacts already published");
133153
return;
134154
}
135155
RequestEntity<SonatypeProperties> requestEntity = RequestEntity
@@ -139,9 +159,10 @@ public void syncToMavenCentral(ReleaseInfo releaseInfo) {
139159
.contentType(MediaType.APPLICATION_JSON).body(this.sonatypeProperties);
140160
try {
141161
this.restTemplate.exchange(requestEntity, Object.class);
162+
logger.debug("Sync complete");
142163
}
143164
catch (HttpClientErrorException ex) {
144-
console.log("Failed to sync.");
165+
logger.info("Failed to sync.");
145166
throw ex;
146167
}
147168
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,19 +14,25 @@
1414
* limitations under the License.
1515
*/
1616

17-
package io.spring.concourse.releasescripts.system;
18-
19-
import org.slf4j.helpers.MessageFormatter;
17+
package io.spring.concourse.releasescripts.bintray;
2018

2119
/**
22-
* Simple console logger used to output progress messages.
20+
* Details for a single packaged file.
2321
*
24-
* @author Madhura Bhave
22+
* @author Phillip Webb
2523
*/
26-
public class ConsoleLogger {
24+
public class PackageFile {
25+
26+
private String name;
27+
28+
private String sha256;
29+
30+
public String getName() {
31+
return this.name;
32+
}
2733

28-
public void log(String message, Object... args) {
29-
System.err.println(MessageFormatter.arrayFormat(message, args).getMessage());
34+
public String getSha256() {
35+
return this.sha256;
3036
}
3137

3238
}

0 commit comments

Comments
 (0)