Commit 6d93d732 authored by Phillip Webb's avatar Phillip Webb

Cherry-pick release script updates

Cherry-pick release script updates from 2.3.x

See gh-21474
parent 5abca710
...@@ -17,13 +17,16 @@ ...@@ -17,13 +17,16 @@
package io.spring.concourse.releasescripts.artifactory; package io.spring.concourse.releasescripts.artifactory;
import java.net.URI; import java.net.URI;
import java.time.Duration;
import java.util.Set;
import io.spring.concourse.releasescripts.ReleaseInfo; import io.spring.concourse.releasescripts.ReleaseInfo;
import io.spring.concourse.releasescripts.artifactory.payload.BuildInfoResponse; import io.spring.concourse.releasescripts.artifactory.payload.BuildInfoResponse;
import io.spring.concourse.releasescripts.artifactory.payload.DistributionRequest; import io.spring.concourse.releasescripts.artifactory.payload.DistributionRequest;
import io.spring.concourse.releasescripts.artifactory.payload.PromotionRequest; import io.spring.concourse.releasescripts.artifactory.payload.PromotionRequest;
import io.spring.concourse.releasescripts.bintray.BintrayService; import io.spring.concourse.releasescripts.bintray.BintrayService;
import io.spring.concourse.releasescripts.system.ConsoleLogger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
...@@ -42,6 +45,8 @@ import org.springframework.web.client.RestTemplate; ...@@ -42,6 +45,8 @@ import org.springframework.web.client.RestTemplate;
@Component @Component
public class ArtifactoryService { public class ArtifactoryService {
private static final Logger logger = LoggerFactory.getLogger(ArtifactoryService.class);
private static final String ARTIFACTORY_URL = "https://repo.spring.io"; private static final String ARTIFACTORY_URL = "https://repo.spring.io";
private static final String PROMOTION_URL = ARTIFACTORY_URL + "/api/build/promote/"; private static final String PROMOTION_URL = ARTIFACTORY_URL + "/api/build/promote/";
...@@ -56,8 +61,6 @@ public class ArtifactoryService { ...@@ -56,8 +61,6 @@ public class ArtifactoryService {
private final BintrayService bintrayService; private final BintrayService bintrayService;
private static final ConsoleLogger console = new ConsoleLogger();
public ArtifactoryService(RestTemplateBuilder builder, ArtifactoryProperties artifactoryProperties, public ArtifactoryService(RestTemplateBuilder builder, ArtifactoryProperties artifactoryProperties,
BintrayService bintrayService) { BintrayService bintrayService) {
this.bintrayService = bintrayService; this.bintrayService = bintrayService;
...@@ -78,20 +81,21 @@ public class ArtifactoryService { ...@@ -78,20 +81,21 @@ public class ArtifactoryService {
PromotionRequest request = getPromotionRequest(targetRepo); PromotionRequest request = getPromotionRequest(targetRepo);
String buildName = releaseInfo.getBuildName(); String buildName = releaseInfo.getBuildName();
String buildNumber = releaseInfo.getBuildNumber(); String buildNumber = releaseInfo.getBuildNumber();
console.log("Promoting " + buildName + "/" + buildNumber + " to " + request.getTargetRepo()); logger.info("Promoting " + buildName + "/" + buildNumber + " to " + request.getTargetRepo());
RequestEntity<PromotionRequest> requestEntity = RequestEntity RequestEntity<PromotionRequest> requestEntity = RequestEntity
.post(URI.create(PROMOTION_URL + buildName + "/" + buildNumber)).contentType(MediaType.APPLICATION_JSON) .post(URI.create(PROMOTION_URL + buildName + "/" + buildNumber)).contentType(MediaType.APPLICATION_JSON)
.body(request); .body(request);
try { try {
this.restTemplate.exchange(requestEntity, String.class); this.restTemplate.exchange(requestEntity, String.class);
logger.debug("Promotion complete");
} }
catch (HttpClientErrorException ex) { catch (HttpClientErrorException ex) {
boolean isAlreadyPromoted = isAlreadyPromoted(buildName, buildNumber, request.getTargetRepo()); boolean isAlreadyPromoted = isAlreadyPromoted(buildName, buildNumber, request.getTargetRepo());
if (isAlreadyPromoted) { if (isAlreadyPromoted) {
console.log("Already promoted."); logger.info("Already promoted.");
} }
else { else {
console.log("Promotion failed."); logger.info("Promotion failed.");
throw ex; throw ex;
} }
} }
...@@ -99,12 +103,15 @@ public class ArtifactoryService { ...@@ -99,12 +103,15 @@ public class ArtifactoryService {
private boolean isAlreadyPromoted(String buildName, String buildNumber, String targetRepo) { private boolean isAlreadyPromoted(String buildName, String buildNumber, String targetRepo) {
try { try {
logger.debug("Checking if alreay promoted");
ResponseEntity<BuildInfoResponse> entity = this.restTemplate ResponseEntity<BuildInfoResponse> entity = this.restTemplate
.getForEntity(BUILD_INFO_URL + buildName + "/" + buildNumber, BuildInfoResponse.class); .getForEntity(BUILD_INFO_URL + buildName + "/" + buildNumber, BuildInfoResponse.class);
BuildInfoResponse.Status status = entity.getBody().getBuildInfo().getStatuses()[0]; BuildInfoResponse.Status status = entity.getBody().getBuildInfo().getStatuses()[0];
logger.debug("Reutned repository " + status.getRepository() + " expecting " + targetRepo);
return status.getRepository().equals(targetRepo); return status.getRepository().equals(targetRepo);
} }
catch (HttpClientErrorException ex) { catch (HttpClientErrorException ex) {
logger.debug("Client error, assuming not promoted");
return false; return false;
} }
} }
...@@ -112,20 +119,28 @@ public class ArtifactoryService { ...@@ -112,20 +119,28 @@ public class ArtifactoryService {
/** /**
* Deploy builds from Artifactory to Bintray. * Deploy builds from Artifactory to Bintray.
* @param sourceRepo the source repo in Artifactory. * @param sourceRepo the source repo in Artifactory.
* @param releaseInfo the resease info
* @param artifactDigests the artifact digests
*/ */
public void distribute(String sourceRepo, ReleaseInfo releaseInfo) { public void distribute(String sourceRepo, ReleaseInfo releaseInfo, Set<String> artifactDigests) {
logger.debug("Attempting distribute via Artifactory");
if (this.bintrayService.isDistributionComplete(releaseInfo, artifactDigests, Duration.ofMinutes(2))) {
logger.info("Distribution already complete");
return;
}
DistributionRequest request = new DistributionRequest(new String[] { sourceRepo }); DistributionRequest request = new DistributionRequest(new String[] { sourceRepo });
RequestEntity<DistributionRequest> requestEntity = RequestEntity RequestEntity<DistributionRequest> requestEntity = RequestEntity
.post(URI.create(DISTRIBUTION_URL + releaseInfo.getBuildName() + "/" + releaseInfo.getBuildNumber())) .post(URI.create(DISTRIBUTION_URL + releaseInfo.getBuildName() + "/" + releaseInfo.getBuildNumber()))
.contentType(MediaType.APPLICATION_JSON).body(request); .contentType(MediaType.APPLICATION_JSON).body(request);
try { try {
this.restTemplate.exchange(requestEntity, Object.class); this.restTemplate.exchange(requestEntity, Object.class);
logger.debug("Distribution call completed");
} }
catch (HttpClientErrorException ex) { catch (HttpClientErrorException ex) {
console.log("Failed to distribute."); logger.info("Failed to distribute.");
throw ex; throw ex;
} }
if (!this.bintrayService.isDistributionComplete(releaseInfo)) { if (!this.bintrayService.isDistributionComplete(releaseInfo, artifactDigests, Duration.ofMinutes(60))) {
throw new DistributionTimeoutException("Distribution timed out."); throw new DistributionTimeoutException("Distribution timed out.");
} }
......
...@@ -16,6 +16,12 @@ ...@@ -16,6 +16,12 @@
package io.spring.concourse.releasescripts.artifactory.payload; package io.spring.concourse.releasescripts.artifactory.payload;
import java.util.Arrays;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/** /**
* Represents the response from Artifactory's buildInfo endpoint. * Represents the response from Artifactory's buildInfo endpoint.
* *
...@@ -54,7 +60,7 @@ public class BuildInfoResponse { ...@@ -54,7 +60,7 @@ public class BuildInfoResponse {
} }
public String getName() { public String getName() {
return name; return this.name;
} }
public void setName(String name) { public void setName(String name) {
...@@ -83,6 +89,14 @@ public class BuildInfoResponse { ...@@ -83,6 +89,14 @@ public class BuildInfoResponse {
public void setVersion(String version) { public void setVersion(String version) {
this.version = version; this.version = version;
}
public Set<String> getArtifactDigests(Predicate<Artifact> predicate) {
return Arrays.stream(this.modules).flatMap((module) -> {
Artifact[] artifacts = module.getArtifacts();
return (artifacts != null) ? Arrays.stream(artifacts) : Stream.empty();
}).filter(predicate).map(Artifact::getSha256).collect(Collectors.toSet());
} }
} }
...@@ -105,6 +119,8 @@ public class BuildInfoResponse { ...@@ -105,6 +119,8 @@ public class BuildInfoResponse {
private String id; private String id;
private Artifact[] artifacts;
public String getId() { public String getId() {
return this.id; return this.id;
} }
...@@ -113,6 +129,38 @@ public class BuildInfoResponse { ...@@ -113,6 +129,38 @@ public class BuildInfoResponse {
this.id = id; this.id = id;
} }
public Artifact[] getArtifacts() {
return this.artifacts;
}
public void setArtifacts(Artifact[] artifacts) {
this.artifacts = artifacts;
}
}
public static class Artifact {
private String name;
private String sha256;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getSha256() {
return this.sha256;
}
public void setSha256(String sha256) {
this.sha256 = sha256;
}
} }
} }
...@@ -17,17 +17,18 @@ ...@@ -17,17 +17,18 @@
package io.spring.concourse.releasescripts.bintray; package io.spring.concourse.releasescripts.bintray;
import java.net.URI; import java.net.URI;
import java.util.Objects; import java.time.Duration;
import java.util.concurrent.TimeUnit; import java.util.HashSet;
import java.util.Set;
import io.spring.concourse.releasescripts.ReleaseInfo; import io.spring.concourse.releasescripts.ReleaseInfo;
import io.spring.concourse.releasescripts.sonatype.SonatypeProperties; import io.spring.concourse.releasescripts.sonatype.SonatypeProperties;
import io.spring.concourse.releasescripts.sonatype.SonatypeService; import io.spring.concourse.releasescripts.sonatype.SonatypeService;
import io.spring.concourse.releasescripts.system.ConsoleLogger;
import org.awaitility.core.ConditionTimeoutException; import org.awaitility.core.ConditionTimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity; import org.springframework.http.RequestEntity;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
...@@ -45,6 +46,8 @@ import static org.awaitility.Awaitility.waitAtMost; ...@@ -45,6 +46,8 @@ import static org.awaitility.Awaitility.waitAtMost;
@Component @Component
public class BintrayService { public class BintrayService {
private static final Logger logger = LoggerFactory.getLogger(BintrayService.class);
private static final String BINTRAY_URL = "https://api.bintray.com/"; private static final String BINTRAY_URL = "https://api.bintray.com/";
private static final String GRADLE_PLUGIN_REQUEST = "[ { \"name\": \"gradle-plugin\", \"values\": [\"org.springframework.boot:org.springframework.boot:spring-boot-gradle-plugin\"] } ]"; 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 { ...@@ -57,8 +60,6 @@ public class BintrayService {
private final SonatypeService sonatypeService; private final SonatypeService sonatypeService;
private static final ConsoleLogger console = new ConsoleLogger();
public BintrayService(RestTemplateBuilder builder, BintrayProperties bintrayProperties, public BintrayService(RestTemplateBuilder builder, BintrayProperties bintrayProperties,
SonatypeProperties sonatypeProperties, SonatypeService sonatypeService) { SonatypeProperties sonatypeProperties, SonatypeService sonatypeService) {
this.bintrayProperties = bintrayProperties; this.bintrayProperties = bintrayProperties;
...@@ -72,32 +73,48 @@ public class BintrayService { ...@@ -72,32 +73,48 @@ public class BintrayService {
this.restTemplate = builder.build(); this.restTemplate = builder.build();
} }
public boolean isDistributionComplete(ReleaseInfo releaseInfo) { public boolean isDistributionComplete(ReleaseInfo releaseInfo, Set<String> requiredDigets, Duration timeout) {
RequestEntity<Void> allFilesRequest = getRequest(releaseInfo, 1); return isDistributionComplete(releaseInfo, requiredDigets, timeout, Duration.ofSeconds(20));
Object[] allFiles = waitAtMost(5, TimeUnit.MINUTES).with().pollDelay(20, TimeUnit.SECONDS).until(() -> { }
try {
return this.restTemplate.exchange(allFilesRequest, Object[].class).getBody(); public boolean isDistributionComplete(ReleaseInfo releaseInfo, Set<String> requiredDigets, Duration timeout,
} Duration pollInterval) {
catch (HttpClientErrorException ex) { logger.debug("Checking if distribution is complete");
if (ex.getStatusCode() != HttpStatus.NOT_FOUND) { RequestEntity<Void> request = getRequest(releaseInfo, 0);
throw ex;
}
return null;
}
}, Objects::nonNull);
RequestEntity<Void> publishedFilesRequest = getRequest(releaseInfo, 0);
try { try {
waitAtMost(40, TimeUnit.MINUTES).with().pollDelay(20, TimeUnit.SECONDS).until(() -> { waitAtMost(timeout).with().pollDelay(Duration.ZERO).pollInterval(pollInterval).until(() -> {
Object[] publishedFiles = this.restTemplate.exchange(publishedFilesRequest, Object[].class).getBody(); logger.debug("Checking bintray");
return allFiles.length == publishedFiles.length; PackageFile[] published = this.restTemplate.exchange(request, PackageFile[].class).getBody();
return hasPublishedAll(published, requiredDigets);
}); });
} }
catch (ConditionTimeoutException ex) { catch (ConditionTimeoutException ex) {
logger.debug("Timeout checking bintray");
return false; return false;
} }
return true; return true;
} }
private boolean hasPublishedAll(PackageFile[] published, Set<String> requiredDigets) {
if (published == null || published.length == 0) {
logger.debug("Bintray returned no published files");
return false;
}
Set<String> remaining = new HashSet<>(requiredDigets);
for (PackageFile publishedFile : published) {
logger.debug(
"Found published file " + publishedFile.getName() + " with digest " + publishedFile.getSha256());
remaining.remove(publishedFile.getSha256());
}
if (remaining.isEmpty()) {
logger.debug("Found all required digests");
return true;
}
logger.debug("Some digests have not been published:");
remaining.forEach(logger::debug);
return false;
}
private RequestEntity<Void> getRequest(ReleaseInfo releaseInfo, int includeUnpublished) { private RequestEntity<Void> getRequest(ReleaseInfo releaseInfo, int includeUnpublished) {
return RequestEntity.get(URI.create(BINTRAY_URL + "packages/" + this.bintrayProperties.getSubject() + "/" return RequestEntity.get(URI.create(BINTRAY_URL + "packages/" + this.bintrayProperties.getSubject() + "/"
+ this.bintrayProperties.getRepo() + "/" + releaseInfo.getGroupId() + "/versions/" + this.bintrayProperties.getRepo() + "/" + releaseInfo.getGroupId() + "/versions/"
...@@ -109,6 +126,7 @@ public class BintrayService { ...@@ -109,6 +126,7 @@ public class BintrayService {
* @param releaseInfo the release information * @param releaseInfo the release information
*/ */
public void publishGradlePlugin(ReleaseInfo releaseInfo) { public void publishGradlePlugin(ReleaseInfo releaseInfo) {
logger.debug("Publishing Gradle Pluging");
RequestEntity<String> requestEntity = RequestEntity RequestEntity<String> requestEntity = RequestEntity
.post(URI.create(BINTRAY_URL + "packages/" + this.bintrayProperties.getSubject() + "/" .post(URI.create(BINTRAY_URL + "packages/" + this.bintrayProperties.getSubject() + "/"
+ this.bintrayProperties.getRepo() + "/" + releaseInfo.getGroupId() + "/versions/" + this.bintrayProperties.getRepo() + "/" + releaseInfo.getGroupId() + "/versions/"
...@@ -116,9 +134,10 @@ public class BintrayService { ...@@ -116,9 +134,10 @@ public class BintrayService {
.contentType(MediaType.APPLICATION_JSON).body(GRADLE_PLUGIN_REQUEST); .contentType(MediaType.APPLICATION_JSON).body(GRADLE_PLUGIN_REQUEST);
try { try {
this.restTemplate.exchange(requestEntity, Object.class); this.restTemplate.exchange(requestEntity, Object.class);
logger.debug("Publishing Gradle Pluging complete");
} }
catch (HttpClientErrorException ex) { catch (HttpClientErrorException ex) {
console.log("Failed to add attribute to gradle plugin."); logger.info("Failed to add attribute to gradle plugin.");
throw ex; throw ex;
} }
} }
...@@ -128,8 +147,9 @@ public class BintrayService { ...@@ -128,8 +147,9 @@ public class BintrayService {
* @param releaseInfo the release information * @param releaseInfo the release information
*/ */
public void syncToMavenCentral(ReleaseInfo releaseInfo) { public void syncToMavenCentral(ReleaseInfo releaseInfo) {
console.log("Calling Bintray to sync to Sonatype"); logger.info("Calling Bintray to sync to Sonatype");
if (this.sonatypeService.artifactsPublished(releaseInfo)) { if (this.sonatypeService.artifactsPublished(releaseInfo)) {
logger.info("Artifacts already published");
return; return;
} }
RequestEntity<SonatypeProperties> requestEntity = RequestEntity RequestEntity<SonatypeProperties> requestEntity = RequestEntity
...@@ -139,9 +159,10 @@ public class BintrayService { ...@@ -139,9 +159,10 @@ public class BintrayService {
.contentType(MediaType.APPLICATION_JSON).body(this.sonatypeProperties); .contentType(MediaType.APPLICATION_JSON).body(this.sonatypeProperties);
try { try {
this.restTemplate.exchange(requestEntity, Object.class); this.restTemplate.exchange(requestEntity, Object.class);
logger.debug("Sync complete");
} }
catch (HttpClientErrorException ex) { catch (HttpClientErrorException ex) {
console.log("Failed to sync."); logger.info("Failed to sync.");
throw ex; throw ex;
} }
} }
......
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -14,19 +14,25 @@ ...@@ -14,19 +14,25 @@
* limitations under the License. * limitations under the License.
*/ */
package io.spring.concourse.releasescripts.system; package io.spring.concourse.releasescripts.bintray;
import org.slf4j.helpers.MessageFormatter;
/** /**
* Simple console logger used to output progress messages. * Details for a single packaged file.
* *
* @author Madhura Bhave * @author Phillip Webb
*/ */
public class ConsoleLogger { public class PackageFile {
private String name;
private String sha256;
public String getName() {
return this.name;
}
public void log(String message, Object... args) { public String getSha256() {
System.err.println(MessageFormatter.arrayFormat(message, args).getMessage()); return this.sha256;
} }
} }
...@@ -19,6 +19,9 @@ package io.spring.concourse.releasescripts.command; ...@@ -19,6 +19,9 @@ package io.spring.concourse.releasescripts.command;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner; import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
...@@ -32,6 +35,8 @@ import org.springframework.util.Assert; ...@@ -32,6 +35,8 @@ import org.springframework.util.Assert;
@Component @Component
public class CommandProcessor implements ApplicationRunner { public class CommandProcessor implements ApplicationRunner {
private static final Logger logger = LoggerFactory.getLogger(CommandProcessor.class);
private final List<Command> commands; private final List<Command> commands;
public CommandProcessor(List<Command> commands) { public CommandProcessor(List<Command> commands) {
...@@ -40,11 +45,14 @@ public class CommandProcessor implements ApplicationRunner { ...@@ -40,11 +45,14 @@ public class CommandProcessor implements ApplicationRunner {
@Override @Override
public void run(ApplicationArguments args) throws Exception { public void run(ApplicationArguments args) throws Exception {
logger.debug("Running command processor");
List<String> nonOptionArgs = args.getNonOptionArgs(); List<String> nonOptionArgs = args.getNonOptionArgs();
Assert.state(!nonOptionArgs.isEmpty(), "No command argument specified"); Assert.state(!nonOptionArgs.isEmpty(), "No command argument specified");
String request = nonOptionArgs.get(0); String request = nonOptionArgs.get(0);
this.commands.stream().filter((c) -> c.getName().equals(request)).findFirst() Command command = this.commands.stream().filter((candidate) -> candidate.getName().equals(request)).findFirst()
.orElseThrow(() -> new IllegalStateException("Unknown command '" + request + "'")).run(args); .orElseThrow(() -> new IllegalStateException("Unknown command '" + request + "'"));
logger.debug("Found command " + command.getClass().getName());
command.run(args);
} }
} }
...@@ -19,12 +19,18 @@ package io.spring.concourse.releasescripts.command; ...@@ -19,12 +19,18 @@ package io.spring.concourse.releasescripts.command;
import java.io.File; import java.io.File;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.List; import java.util.List;
import java.util.Set;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import io.spring.concourse.releasescripts.ReleaseInfo; import io.spring.concourse.releasescripts.ReleaseInfo;
import io.spring.concourse.releasescripts.ReleaseType; import io.spring.concourse.releasescripts.ReleaseType;
import io.spring.concourse.releasescripts.artifactory.ArtifactoryService; import io.spring.concourse.releasescripts.artifactory.ArtifactoryService;
import io.spring.concourse.releasescripts.artifactory.payload.BuildInfoResponse; import io.spring.concourse.releasescripts.artifactory.payload.BuildInfoResponse;
import io.spring.concourse.releasescripts.artifactory.payload.BuildInfoResponse.Artifact;
import io.spring.concourse.releasescripts.artifactory.payload.BuildInfoResponse.BuildInfo;
import io.spring.concourse.releasescripts.artifactory.payload.BuildInfoResponse.Module;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationArguments;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
...@@ -38,30 +44,44 @@ import org.springframework.util.Assert; ...@@ -38,30 +44,44 @@ import org.springframework.util.Assert;
@Component @Component
public class DistributeCommand implements Command { public class DistributeCommand implements Command {
private final ArtifactoryService service; private static final Logger logger = LoggerFactory.getLogger(DistributeCommand.class);
private final ArtifactoryService artifactoryService;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
public DistributeCommand(ArtifactoryService service, ObjectMapper objectMapper) { public DistributeCommand(ArtifactoryService artifactoryService, ObjectMapper objectMapper) {
this.service = service; this.artifactoryService = artifactoryService;
this.objectMapper = objectMapper; this.objectMapper = objectMapper;
} }
@Override @Override
public void run(ApplicationArguments args) throws Exception { public void run(ApplicationArguments args) throws Exception {
logger.debug("Running 'distribute' command");
List<String> nonOptionArgs = args.getNonOptionArgs(); List<String> nonOptionArgs = args.getNonOptionArgs();
Assert.state(!nonOptionArgs.isEmpty(), "No command argument specified"); Assert.state(!nonOptionArgs.isEmpty(), "No command argument specified");
Assert.state(nonOptionArgs.size() == 3, "Release type or build info not specified"); Assert.state(nonOptionArgs.size() == 3, "Release type or build info not specified");
String releaseType = nonOptionArgs.get(1); String releaseType = nonOptionArgs.get(1);
ReleaseType type = ReleaseType.from(releaseType); ReleaseType type = ReleaseType.from(releaseType);
if (!ReleaseType.RELEASE.equals(type)) { if (!ReleaseType.RELEASE.equals(type)) {
logger.info("Skipping distribution of " + type + " type");
return; return;
} }
String buildInfoLocation = nonOptionArgs.get(2); String buildInfoLocation = nonOptionArgs.get(2);
logger.debug("Loading build-info from " + buildInfoLocation);
byte[] content = Files.readAllBytes(new File(buildInfoLocation).toPath()); byte[] content = Files.readAllBytes(new File(buildInfoLocation).toPath());
BuildInfoResponse buildInfoResponse = this.objectMapper.readValue(content, BuildInfoResponse.class); BuildInfoResponse buildInfoResponse = this.objectMapper.readValue(content, BuildInfoResponse.class);
ReleaseInfo releaseInfo = ReleaseInfo.from(buildInfoResponse.getBuildInfo()); BuildInfo buildInfo = buildInfoResponse.getBuildInfo();
this.service.distribute(type.getRepo(), releaseInfo); logger.debug("Loading build info:");
for (Module module : buildInfo.getModules()) {
logger.debug(module.getId());
for (Artifact artifact : module.getArtifacts()) {
logger.debug(artifact.getSha256() + " " + artifact.getName());
}
}
ReleaseInfo releaseInfo = ReleaseInfo.from(buildInfo);
Set<String> artifactDigests = buildInfo.getArtifactDigests((artifact) -> !artifact.getName().endsWith(".zip"));
this.artifactoryService.distribute(type.getRepo(), releaseInfo, artifactDigests);
} }
} }
...@@ -25,6 +25,8 @@ import io.spring.concourse.releasescripts.ReleaseInfo; ...@@ -25,6 +25,8 @@ import io.spring.concourse.releasescripts.ReleaseInfo;
import io.spring.concourse.releasescripts.ReleaseType; import io.spring.concourse.releasescripts.ReleaseType;
import io.spring.concourse.releasescripts.artifactory.ArtifactoryService; import io.spring.concourse.releasescripts.artifactory.ArtifactoryService;
import io.spring.concourse.releasescripts.artifactory.payload.BuildInfoResponse; import io.spring.concourse.releasescripts.artifactory.payload.BuildInfoResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationArguments;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
...@@ -38,6 +40,8 @@ import org.springframework.util.Assert; ...@@ -38,6 +40,8 @@ import org.springframework.util.Assert;
@Component @Component
public class PromoteCommand implements Command { public class PromoteCommand implements Command {
private static final Logger logger = LoggerFactory.getLogger(PromoteCommand.class);
private final ArtifactoryService service; private final ArtifactoryService service;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
...@@ -49,6 +53,7 @@ public class PromoteCommand implements Command { ...@@ -49,6 +53,7 @@ public class PromoteCommand implements Command {
@Override @Override
public void run(ApplicationArguments args) throws Exception { public void run(ApplicationArguments args) throws Exception {
logger.debug("Running 'promote' command");
List<String> nonOptionArgs = args.getNonOptionArgs(); List<String> nonOptionArgs = args.getNonOptionArgs();
Assert.state(!nonOptionArgs.isEmpty(), "No command argument specified"); Assert.state(!nonOptionArgs.isEmpty(), "No command argument specified");
Assert.state(nonOptionArgs.size() == 3, "Release type or build info location not specified"); Assert.state(nonOptionArgs.size() == 3, "Release type or build info location not specified");
......
...@@ -25,6 +25,8 @@ import io.spring.concourse.releasescripts.ReleaseInfo; ...@@ -25,6 +25,8 @@ import io.spring.concourse.releasescripts.ReleaseInfo;
import io.spring.concourse.releasescripts.ReleaseType; import io.spring.concourse.releasescripts.ReleaseType;
import io.spring.concourse.releasescripts.artifactory.payload.BuildInfoResponse; import io.spring.concourse.releasescripts.artifactory.payload.BuildInfoResponse;
import io.spring.concourse.releasescripts.bintray.BintrayService; import io.spring.concourse.releasescripts.bintray.BintrayService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationArguments;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
...@@ -38,6 +40,8 @@ import org.springframework.util.Assert; ...@@ -38,6 +40,8 @@ import org.springframework.util.Assert;
@Component @Component
public class PublishGradlePlugin implements Command { public class PublishGradlePlugin implements Command {
private static final Logger logger = LoggerFactory.getLogger(PublishGradlePlugin.class);
private static final String PUBLISH_GRADLE_PLUGIN_COMMAND = "publishGradlePlugin"; private static final String PUBLISH_GRADLE_PLUGIN_COMMAND = "publishGradlePlugin";
private final BintrayService service; private final BintrayService service;
...@@ -56,6 +60,7 @@ public class PublishGradlePlugin implements Command { ...@@ -56,6 +60,7 @@ public class PublishGradlePlugin implements Command {
@Override @Override
public void run(ApplicationArguments args) throws Exception { public void run(ApplicationArguments args) throws Exception {
logger.debug("Running 'publish gradle' command");
List<String> nonOptionArgs = args.getNonOptionArgs(); List<String> nonOptionArgs = args.getNonOptionArgs();
Assert.state(!nonOptionArgs.isEmpty(), "No command argument specified"); Assert.state(!nonOptionArgs.isEmpty(), "No command argument specified");
Assert.state(nonOptionArgs.size() == 3, "Release type or build info not specified"); Assert.state(nonOptionArgs.size() == 3, "Release type or build info not specified");
......
...@@ -25,6 +25,8 @@ import io.spring.concourse.releasescripts.ReleaseInfo; ...@@ -25,6 +25,8 @@ import io.spring.concourse.releasescripts.ReleaseInfo;
import io.spring.concourse.releasescripts.ReleaseType; import io.spring.concourse.releasescripts.ReleaseType;
import io.spring.concourse.releasescripts.artifactory.payload.BuildInfoResponse; import io.spring.concourse.releasescripts.artifactory.payload.BuildInfoResponse;
import io.spring.concourse.releasescripts.bintray.BintrayService; import io.spring.concourse.releasescripts.bintray.BintrayService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationArguments;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
...@@ -38,6 +40,8 @@ import org.springframework.util.Assert; ...@@ -38,6 +40,8 @@ import org.springframework.util.Assert;
@Component @Component
public class SyncToCentralCommand implements Command { public class SyncToCentralCommand implements Command {
private static final Logger logger = LoggerFactory.getLogger(SyncToCentralCommand.class);
private static final String SYNC_TO_CENTRAL_COMMAND = "syncToCentral"; private static final String SYNC_TO_CENTRAL_COMMAND = "syncToCentral";
private final BintrayService service; private final BintrayService service;
...@@ -56,6 +60,7 @@ public class SyncToCentralCommand implements Command { ...@@ -56,6 +60,7 @@ public class SyncToCentralCommand implements Command {
@Override @Override
public void run(ApplicationArguments args) throws Exception { public void run(ApplicationArguments args) throws Exception {
logger.debug("Running 'sync to central' command");
List<String> nonOptionArgs = args.getNonOptionArgs(); List<String> nonOptionArgs = args.getNonOptionArgs();
Assert.state(!nonOptionArgs.isEmpty(), "No command argument specified"); Assert.state(!nonOptionArgs.isEmpty(), "No command argument specified");
Assert.state(nonOptionArgs.size() == 3, "Release type or build info not specified"); Assert.state(nonOptionArgs.size() == 3, "Release type or build info not specified");
......
...@@ -17,7 +17,8 @@ ...@@ -17,7 +17,8 @@
package io.spring.concourse.releasescripts.sonatype; package io.spring.concourse.releasescripts.sonatype;
import io.spring.concourse.releasescripts.ReleaseInfo; import io.spring.concourse.releasescripts.ReleaseInfo;
import io.spring.concourse.releasescripts.system.ConsoleLogger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
...@@ -35,12 +36,12 @@ import org.springframework.web.client.RestTemplate; ...@@ -35,12 +36,12 @@ import org.springframework.web.client.RestTemplate;
@Component @Component
public class SonatypeService { public class SonatypeService {
private static final Logger logger = LoggerFactory.getLogger(SonatypeService.class);
private static final String SONATYPE_REPOSITORY_URI = "https://oss.sonatype.org/service/local/repositories/releases/content/org/springframework/boot/spring-boot/"; private static final String SONATYPE_REPOSITORY_URI = "https://oss.sonatype.org/service/local/repositories/releases/content/org/springframework/boot/spring-boot/";
private final RestTemplate restTemplate; private final RestTemplate restTemplate;
private static final ConsoleLogger console = new ConsoleLogger();
public SonatypeService(RestTemplateBuilder builder, SonatypeProperties sonatypeProperties) { public SonatypeService(RestTemplateBuilder builder, SonatypeProperties sonatypeProperties) {
String username = sonatypeProperties.getUserToken(); String username = sonatypeProperties.getUserToken();
String password = sonatypeProperties.getPasswordToken(); String password = sonatypeProperties.getPasswordToken();
...@@ -57,11 +58,11 @@ public class SonatypeService { ...@@ -57,11 +58,11 @@ public class SonatypeService {
*/ */
public boolean artifactsPublished(ReleaseInfo releaseInfo) { public boolean artifactsPublished(ReleaseInfo releaseInfo) {
try { try {
ResponseEntity<Object> entity = this.restTemplate ResponseEntity<?> entity = this.restTemplate
.getForEntity(String.format(SONATYPE_REPOSITORY_URI + "%s/spring-boot-%s.jar.sha1", .getForEntity(String.format(SONATYPE_REPOSITORY_URI + "%s/spring-boot-%s.jar.sha1",
releaseInfo.getVersion(), releaseInfo.getVersion()), Object.class); releaseInfo.getVersion(), releaseInfo.getVersion()), byte[].class);
if (HttpStatus.OK.equals(entity.getStatusCode())) { if (HttpStatus.OK.equals(entity.getStatusCode())) {
console.log("Already published to Sonatype."); logger.info("Already published to Sonatype.");
return true; return true;
} }
} }
......
spring.main.banner-mode=off
# logging.level.io.spring.concourse=DEBUG
\ No newline at end of file
...@@ -16,10 +16,15 @@ ...@@ -16,10 +16,15 @@
package io.spring.concourse.releasescripts.artifactory; package io.spring.concourse.releasescripts.artifactory;
import java.time.Duration;
import java.util.Collections;
import java.util.Set;
import io.spring.concourse.releasescripts.ReleaseInfo; import io.spring.concourse.releasescripts.ReleaseInfo;
import io.spring.concourse.releasescripts.bintray.BintrayService; import io.spring.concourse.releasescripts.bintray.BintrayService;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
...@@ -35,10 +40,13 @@ import org.springframework.util.Base64Utils; ...@@ -35,10 +40,13 @@ import org.springframework.util.Base64Utils;
import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpClientErrorException;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.content; import static org.springframework.test.web.client.match.MockRestRequestMatchers.content;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.header; import static org.springframework.test.web.client.match.MockRestRequestMatchers.header;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
...@@ -55,6 +63,10 @@ import static org.springframework.test.web.client.response.MockRestResponseCreat ...@@ -55,6 +63,10 @@ import static org.springframework.test.web.client.response.MockRestResponseCreat
@EnableConfigurationProperties(ArtifactoryProperties.class) @EnableConfigurationProperties(ArtifactoryProperties.class)
class ArtifactoryServiceTests { class ArtifactoryServiceTests {
private static final Duration SHORT_TIMEOUT = Duration.ofMinutes(2);
private static final Duration LONG_TIMEOUT = Duration.ofMinutes(60);
@Autowired @Autowired
private ArtifactoryService service; private ArtifactoryService service;
...@@ -126,21 +138,24 @@ class ArtifactoryServiceTests { ...@@ -126,21 +138,24 @@ class ArtifactoryServiceTests {
} }
@Test @Test
@SuppressWarnings("unchecked")
void distributeWhenSuccessful() throws Exception { void distributeWhenSuccessful() throws Exception {
ReleaseInfo releaseInfo = getReleaseInfo(); ReleaseInfo releaseInfo = getReleaseInfo();
given(this.bintrayService.isDistributionComplete(releaseInfo)).willReturn(true); given(this.bintrayService.isDistributionComplete(eq(releaseInfo), (Set<String>) any(), any())).willReturn(false,
this.server true);
.expect(requestTo( this.server.expect(requestTo("https://repo.spring.io/api/build/distribute/example-build/example-build-1"))
"https://repo.spring.io/api/build/distribute/" + "example-build" + "/" + "example-build-1"))
.andExpect(method(HttpMethod.POST)) .andExpect(method(HttpMethod.POST))
.andExpect(content().json( .andExpect(content().json(
"{\"sourceRepos\": [\"libs-release-local\"], \"targetRepo\" : \"spring-distributions\", \"async\":\"true\"}")) "{\"sourceRepos\": [\"libs-release-local\"], \"targetRepo\" : \"spring-distributions\", \"async\":\"true\"}"))
.andExpect(header("Authorization", "Basic " + Base64Utils.encodeToString(String .andExpect(header("Authorization", "Basic " + Base64Utils.encodeToString(String
.format("%s:%s", this.properties.getUsername(), this.properties.getPassword()).getBytes()))) .format("%s:%s", this.properties.getUsername(), this.properties.getPassword()).getBytes())))
.andExpect(header("Content-Type", MediaType.APPLICATION_JSON.toString())).andRespond(withSuccess()); .andExpect(header("Content-Type", MediaType.APPLICATION_JSON.toString())).andRespond(withSuccess());
this.service.distribute("libs-release-local", releaseInfo); Set<String> artifactDigests = Collections.singleton("602e20176706d3cc7535f01ffdbe91b270ae5014");
this.service.distribute("libs-release-local", releaseInfo, artifactDigests);
this.server.verify(); this.server.verify();
verify(this.bintrayService, times(1)).isDistributionComplete(releaseInfo); InOrder ordered = inOrder(this.bintrayService);
ordered.verify(this.bintrayService).isDistributionComplete(releaseInfo, artifactDigests, SHORT_TIMEOUT);
ordered.verify(this.bintrayService).isDistributionComplete(releaseInfo, artifactDigests, LONG_TIMEOUT);
} }
@Test @Test
...@@ -156,29 +171,36 @@ class ArtifactoryServiceTests { ...@@ -156,29 +171,36 @@ class ArtifactoryServiceTests {
.format("%s:%s", this.properties.getUsername(), this.properties.getPassword()).getBytes()))) .format("%s:%s", this.properties.getUsername(), this.properties.getPassword()).getBytes())))
.andExpect(header("Content-Type", MediaType.APPLICATION_JSON.toString())) .andExpect(header("Content-Type", MediaType.APPLICATION_JSON.toString()))
.andRespond(withStatus(HttpStatus.FORBIDDEN)); .andRespond(withStatus(HttpStatus.FORBIDDEN));
Set<String> artifactDigests = Collections.singleton("602e20176706d3cc7535f01ffdbe91b270ae5014");
assertThatExceptionOfType(HttpClientErrorException.class) assertThatExceptionOfType(HttpClientErrorException.class)
.isThrownBy(() -> this.service.distribute("libs-release-local", releaseInfo)); .isThrownBy(() -> this.service.distribute("libs-release-local", releaseInfo, artifactDigests));
this.server.verify(); this.server.verify();
verifyNoInteractions(this.bintrayService); verify(this.bintrayService, times(1)).isDistributionComplete(releaseInfo, artifactDigests, SHORT_TIMEOUT);
verifyNoMoreInteractions(this.bintrayService);
} }
@Test @Test
@SuppressWarnings("unchecked")
void distributeWhenGettingPackagesTimesOut() throws Exception { void distributeWhenGettingPackagesTimesOut() throws Exception {
ReleaseInfo releaseInfo = getReleaseInfo(); ReleaseInfo releaseInfo = getReleaseInfo();
given(this.bintrayService.isDistributionComplete(releaseInfo)).willReturn(false); given(this.bintrayService.isDistributionComplete(eq(releaseInfo), (Set<String>) any(), any()))
this.server .willReturn(false);
.expect(requestTo( given(this.bintrayService.isDistributionComplete(eq(releaseInfo), (Set<String>) any(), any()))
"https://repo.spring.io/api/build/distribute/" + "example-build" + "/" + "example-build-1")) .willReturn(false);
this.server.expect(requestTo("https://repo.spring.io/api/build/distribute/example-build/example-build-1"))
.andExpect(method(HttpMethod.POST)) .andExpect(method(HttpMethod.POST))
.andExpect(content().json( .andExpect(content().json(
"{\"sourceRepos\": [\"libs-release-local\"], \"targetRepo\" : \"spring-distributions\", \"async\":\"true\"}")) "{\"sourceRepos\": [\"libs-release-local\"], \"targetRepo\" : \"spring-distributions\", \"async\":\"true\"}"))
.andExpect(header("Authorization", "Basic " + Base64Utils.encodeToString(String .andExpect(header("Authorization", "Basic " + Base64Utils.encodeToString(String
.format("%s:%s", this.properties.getUsername(), this.properties.getPassword()).getBytes()))) .format("%s:%s", this.properties.getUsername(), this.properties.getPassword()).getBytes())))
.andExpect(header("Content-Type", MediaType.APPLICATION_JSON.toString())).andRespond(withSuccess()); .andExpect(header("Content-Type", MediaType.APPLICATION_JSON.toString())).andRespond(withSuccess());
Set<String> artifactDigests = Collections.singleton("602e20176706d3cc7535f01ffdbe91b270ae5014");
assertThatExceptionOfType(DistributionTimeoutException.class) assertThatExceptionOfType(DistributionTimeoutException.class)
.isThrownBy(() -> this.service.distribute("libs-release-local", releaseInfo)); .isThrownBy(() -> this.service.distribute("libs-release-local", releaseInfo, artifactDigests));
this.server.verify(); this.server.verify();
verify(this.bintrayService, times(1)).isDistributionComplete(releaseInfo); InOrder ordered = inOrder(this.bintrayService);
ordered.verify(this.bintrayService).isDistributionComplete(releaseInfo, artifactDigests, SHORT_TIMEOUT);
ordered.verify(this.bintrayService).isDistributionComplete(releaseInfo, artifactDigests, LONG_TIMEOUT);
} }
private ReleaseInfo getReleaseInfo() { private ReleaseInfo getReleaseInfo() {
......
...@@ -16,6 +16,10 @@ ...@@ -16,6 +16,10 @@
package io.spring.concourse.releasescripts.bintray; package io.spring.concourse.releasescripts.bintray;
import java.time.Duration;
import java.util.LinkedHashSet;
import java.util.Set;
import io.spring.concourse.releasescripts.ReleaseInfo; import io.spring.concourse.releasescripts.ReleaseInfo;
import io.spring.concourse.releasescripts.sonatype.SonatypeProperties; import io.spring.concourse.releasescripts.sonatype.SonatypeProperties;
import io.spring.concourse.releasescripts.sonatype.SonatypeService; import io.spring.concourse.releasescripts.sonatype.SonatypeService;
...@@ -28,7 +32,6 @@ import org.springframework.boot.test.autoconfigure.web.client.RestClientTest; ...@@ -28,7 +32,6 @@ import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.web.client.ExpectedCount; import org.springframework.test.web.client.ExpectedCount;
import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.test.web.client.MockRestServiceServer;
...@@ -41,7 +44,6 @@ import static org.springframework.test.web.client.match.MockRestRequestMatchers. ...@@ -41,7 +44,6 @@ import static org.springframework.test.web.client.match.MockRestRequestMatchers.
import static org.springframework.test.web.client.match.MockRestRequestMatchers.header; import static org.springframework.test.web.client.match.MockRestRequestMatchers.header;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
/** /**
...@@ -75,15 +77,15 @@ class BintrayServiceTests { ...@@ -75,15 +77,15 @@ class BintrayServiceTests {
@Test @Test
void isDistributionComplete() throws Exception { void isDistributionComplete() throws Exception {
this.server setupGetPackageFiles(0, "no-package-files.json");
.expect(requestTo(String.format( setupGetPackageFiles(0, "some-package-files.json");
"https://api.bintray.com/packages/%s/%s/%s/versions/%s/files?include_unpublished=%s",
this.properties.getSubject(), this.properties.getRepo(), "example", "1.1.0.RELEASE", 1)))
.andRespond(withStatus(HttpStatus.NOT_FOUND));
setupGetPackageFiles(1, "all-package-files.json");
setupGetPackageFiles(0, "published-files.json");
setupGetPackageFiles(0, "all-package-files.json"); setupGetPackageFiles(0, "all-package-files.json");
assertThat(this.service.isDistributionComplete(getReleaseInfo())).isTrue(); Set<String> digests = new LinkedHashSet<>();
digests.add("602e20176706d3cc7535f01ffdbe91b270ae5012");
digests.add("602e20176706d3cc7535f01ffdbe91b270ae5013");
digests.add("602e20176706d3cc7535f01ffdbe91b270ae5014");
assertThat(this.service.isDistributionComplete(getReleaseInfo(), digests, Duration.ofMinutes(1), Duration.ZERO))
.isTrue();
this.server.verify(); this.server.verify();
} }
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package io.spring.concourse.releasescripts.command; package io.spring.concourse.releasescripts.command;
import java.util.Set;
import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import io.spring.concourse.releasescripts.ReleaseInfo; import io.spring.concourse.releasescripts.ReleaseInfo;
...@@ -54,7 +56,7 @@ class DistributeCommandTests { ...@@ -54,7 +56,7 @@ class DistributeCommandTests {
void setup() { void setup() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
this.objectMapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); this.objectMapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
this.command = new DistributeCommand(this.service, objectMapper); this.command = new DistributeCommand(this.service, this.objectMapper);
} }
@Test @Test
...@@ -76,15 +78,20 @@ class DistributeCommandTests { ...@@ -76,15 +78,20 @@ class DistributeCommandTests {
} }
@Test @Test
@SuppressWarnings("unchecked")
void distributeWhenReleaseTypeReleaseShouldCallService() throws Exception { void distributeWhenReleaseTypeReleaseShouldCallService() throws Exception {
ArgumentCaptor<ReleaseInfo> captor = ArgumentCaptor.forClass(ReleaseInfo.class); ArgumentCaptor<ReleaseInfo> releaseInfoCaptor = ArgumentCaptor.forClass(ReleaseInfo.class);
ArgumentCaptor<Set<String>> artifactDigestCaptor = ArgumentCaptor.forClass(Set.class);
this.command.run(new DefaultApplicationArguments("distribute", "RELEASE", getBuildInfoLocation())); this.command.run(new DefaultApplicationArguments("distribute", "RELEASE", getBuildInfoLocation()));
verify(this.service).distribute(eq(ReleaseType.RELEASE.getRepo()), captor.capture()); verify(this.service).distribute(eq(ReleaseType.RELEASE.getRepo()), releaseInfoCaptor.capture(),
ReleaseInfo releaseInfo = captor.getValue(); artifactDigestCaptor.capture());
ReleaseInfo releaseInfo = releaseInfoCaptor.getValue();
assertThat(releaseInfo.getBuildName()).isEqualTo("example"); assertThat(releaseInfo.getBuildName()).isEqualTo("example");
assertThat(releaseInfo.getBuildNumber()).isEqualTo("example-build-1"); assertThat(releaseInfo.getBuildNumber()).isEqualTo("example-build-1");
assertThat(releaseInfo.getGroupId()).isEqualTo("org.example.demo"); assertThat(releaseInfo.getGroupId()).isEqualTo("org.example.demo");
assertThat(releaseInfo.getVersion()).isEqualTo("2.2.0"); assertThat(releaseInfo.getVersion()).isEqualTo("2.2.0");
Set<String> artifactDigests = artifactDigestCaptor.getValue();
assertThat(artifactDigests).containsExactly("aaaaaaaaa85f5c5093721f3ed0edda8ff8290yyyyyyyyyy");
} }
private String getBuildInfoLocation() throws Exception { private String getBuildInfoLocation() throws Exception {
......
...@@ -45,9 +45,6 @@ class SonatypeServiceTests { ...@@ -45,9 +45,6 @@ class SonatypeServiceTests {
@Autowired @Autowired
private SonatypeService service; private SonatypeService service;
@Autowired
private SonatypeProperties properties;
@Autowired @Autowired
private MockRestServiceServer server; private MockRestServiceServer server;
...@@ -60,7 +57,8 @@ class SonatypeServiceTests { ...@@ -60,7 +57,8 @@ class SonatypeServiceTests {
void artifactsPublishedWhenPublishedShouldReturnTrue() { void artifactsPublishedWhenPublishedShouldReturnTrue() {
this.server.expect(requestTo(String.format( this.server.expect(requestTo(String.format(
"https://oss.sonatype.org/service/local/repositories/releases/content/org/springframework/boot/spring-boot/%s/spring-boot-%s.jar.sha1", "https://oss.sonatype.org/service/local/repositories/releases/content/org/springframework/boot/spring-boot/%s/spring-boot-%s.jar.sha1",
"1.1.0.RELEASE", "1.1.0.RELEASE"))).andExpect(method(HttpMethod.GET)).andRespond(withSuccess()); "1.1.0.RELEASE", "1.1.0.RELEASE"))).andExpect(method(HttpMethod.GET))
.andRespond(withSuccess().body("ce8d8b6838ecceb68962b975b18682f4237ccf71".getBytes()));
boolean published = this.service.artifactsPublished(getReleaseInfo()); boolean published = this.service.artifactsPublished(getReleaseInfo());
assertThat(published).isTrue(); assertThat(published).isTrue();
this.server.verify(); this.server.verify();
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
"owner": "jfrog", "owner": "jfrog",
"created": "ISO8601 (yyyy-MM-dd'T'HH:mm:ss.SSSZ)", "created": "ISO8601 (yyyy-MM-dd'T'HH:mm:ss.SSSZ)",
"size": 1234, "size": 1234,
"sha1": "602e20176706d3cc7535f01ffdbe91b270ae5012" "sha256": "602e20176706d3cc7535f01ffdbe91b270ae5012"
}, },
{ {
"name": "nutcracker-1.1.pom", "name": "nutcracker-1.1.pom",
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
"owner": "jfrog", "owner": "jfrog",
"created": "ISO8601 (yyyy-MM-dd'T'HH:mm:ss.SSSZ)", "created": "ISO8601 (yyyy-MM-dd'T'HH:mm:ss.SSSZ)",
"size": 1234, "size": 1234,
"sha1": "602e20176706d3cc7535f01ffdbe91b270ae5012" "sha256": "602e20176706d3cc7535f01ffdbe91b270ae5013"
}, },
{ {
"name": "nutcracker-1.1.jar", "name": "nutcracker-1.1.jar",
...@@ -30,6 +30,6 @@ ...@@ -30,6 +30,6 @@
"owner": "jfrog", "owner": "jfrog",
"created": "ISO8601 (yyyy-MM-dd'T'HH:mm:ss.SSSZ)", "created": "ISO8601 (yyyy-MM-dd'T'HH:mm:ss.SSSZ)",
"size": 1234, "size": 1234,
"sha1": "602e20176706d3cc7535f01ffdbe91b270ae5012" "sha256": "602e20176706d3cc7535f01ffdbe91b270ae5014"
} }
] ]
\ No newline at end of file
...@@ -8,6 +8,6 @@ ...@@ -8,6 +8,6 @@
"owner": "jfrog", "owner": "jfrog",
"created": "ISO8601 (yyyy-MM-dd'T'HH:mm:ss.SSSZ)", "created": "ISO8601 (yyyy-MM-dd'T'HH:mm:ss.SSSZ)",
"size": 1234, "size": 1234,
"sha1": "602e20176706d3cc7535f01ffdbe91b270ae5012" "sha256": "602e20176706d3cc7535f01ffdbe91b270ae5012"
} }
] ]
\ No newline at end of file
...@@ -5,11 +5,11 @@ source $(dirname $0)/common.sh ...@@ -5,11 +5,11 @@ source $(dirname $0)/common.sh
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' ) version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
export BUILD_INFO_LOCATION=$(pwd)/artifactory-repo/build-info.json export BUILD_INFO_LOCATION=$(pwd)/artifactory-repo/build-info.json
java -jar /spring-boot-release-scripts.jar promote $RELEASE_TYPE $BUILD_INFO_LOCATION > /dev/null || { exit 1; } java -jar /spring-boot-release-scripts.jar promote $RELEASE_TYPE $BUILD_INFO_LOCATION || { exit 1; }
java -jar /spring-boot-release-scripts.jar distribute $RELEASE_TYPE $BUILD_INFO_LOCATION > /dev/null || { exit 1; } java -jar /spring-boot-release-scripts.jar distribute $RELEASE_TYPE $BUILD_INFO_LOCATION || { exit 1; }
java -jar /spring-boot-release-scripts.jar publishGradlePlugin $RELEASE_TYPE $BUILD_INFO_LOCATION > /dev/null || { exit 1; } java -jar /spring-boot-release-scripts.jar publishGradlePlugin $RELEASE_TYPE $BUILD_INFO_LOCATION || { exit 1; }
echo "Promotion complete" echo "Promotion complete"
echo $version > version/version echo $version > version/version
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
export BUILD_INFO_LOCATION=$(pwd)/artifactory-repo/build-info.json export BUILD_INFO_LOCATION=$(pwd)/artifactory-repo/build-info.json
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' ) version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
java -jar /spring-boot-release-scripts.jar syncToCentral "RELEASE" $BUILD_INFO_LOCATION > /dev/null || { exit 1; } java -jar /spring-boot-release-scripts.jar syncToCentral "RELEASE" $BUILD_INFO_LOCATION || { exit 1; }
echo "Sync complete" echo "Sync complete"
echo $version > version/version echo $version > version/version
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment