diff --git a/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/bintray/BintrayService.java b/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/bintray/BintrayService.java index 80ca5fa41f..7a224f3262 100644 --- a/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/bintray/BintrayService.java +++ b/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/bintray/BintrayService.java @@ -101,8 +101,13 @@ public class BintrayService { try { waitAtMost(timeout).with().pollDelay(Duration.ZERO).pollInterval(pollInterval).until(() -> { logger.debug("Checking bintray"); - PackageFile[] published = this.restTemplate.exchange(request, PackageFile[].class).getBody(); - return hasPublishedAll(published, requiredDigests); + try { + PackageFile[] published = this.restTemplate.exchange(request, PackageFile[].class).getBody(); + return hasPublishedAll(published, requiredDigests); + } + catch (HttpClientErrorException.NotFound ex) { + return false; + } }); } catch (ConditionTimeoutException ex) { diff --git a/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/command/DistributeCommand.java b/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/command/DistributeCommand.java index cbd828710e..f8f8ea11a1 100644 --- a/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/command/DistributeCommand.java +++ b/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/command/DistributeCommand.java @@ -20,6 +20,8 @@ import java.io.File; import java.nio.file.Files; import java.util.List; import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; import com.fasterxml.jackson.databind.ObjectMapper; import io.spring.concourse.releasescripts.ReleaseInfo; @@ -40,6 +42,7 @@ import org.springframework.util.Assert; * Command used to deploy builds from Artifactory to Bintray. * * @author Madhura Bhave + * @author Phillip Webb */ @Component public class DistributeCommand implements Command { @@ -50,9 +53,14 @@ public class DistributeCommand implements Command { private final ObjectMapper objectMapper; - public DistributeCommand(ArtifactoryService artifactoryService, ObjectMapper objectMapper) { + private final List optionalDeployments; + + public DistributeCommand(ArtifactoryService artifactoryService, ObjectMapper objectMapper, + DistributeProperties distributeProperties) { this.artifactoryService = artifactoryService; this.objectMapper = objectMapper; + this.optionalDeployments = distributeProperties.getOptionalDeployments().stream().map(Pattern::compile) + .collect(Collectors.toList()); } @Override @@ -80,8 +88,18 @@ public class DistributeCommand implements Command { } } ReleaseInfo releaseInfo = ReleaseInfo.from(buildInfo); - Set artifactDigests = buildInfo.getArtifactDigests((artifact) -> !artifact.getName().endsWith(".zip")); + Set artifactDigests = buildInfo.getArtifactDigests(this::isIncluded); this.artifactoryService.distribute(type.getRepo(), releaseInfo, artifactDigests); } + private boolean isIncluded(Artifact artifact) { + String path = artifact.getName(); + for (Pattern optionalDeployment : this.optionalDeployments) { + if (optionalDeployment.matcher(path).matches()) { + return false; + } + } + return true; + } + } diff --git a/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/command/DistributeProperties.java b/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/command/DistributeProperties.java new file mode 100644 index 0000000000..6e56b0763a --- /dev/null +++ b/ci/images/releasescripts/src/main/java/io/spring/concourse/releasescripts/command/DistributeProperties.java @@ -0,0 +1,42 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.concourse.releasescripts.command; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Distribution properties. + * + * @author Phillip Webb + */ +@ConfigurationProperties(prefix = "distribute") +public class DistributeProperties { + + private List optionalDeployments = new ArrayList<>(); + + public List getOptionalDeployments() { + return this.optionalDeployments; + } + + public void setOptionalDeployments(List optionalDeployments) { + this.optionalDeployments = optionalDeployments; + } + +} diff --git a/ci/images/releasescripts/src/main/resources/application.properties b/ci/images/releasescripts/src/main/resources/application.properties index 5bc7bb6054..44dc307221 100644 --- a/ci/images/releasescripts/src/main/resources/application.properties +++ b/ci/images/releasescripts/src/main/resources/application.properties @@ -1,2 +1,4 @@ spring.main.banner-mode=off -# logging.level.io.spring.concourse=DEBUG \ No newline at end of file +distribute.optional-deployments[0]=.*\\.zip +distribute.optional-deployments[1]=spring-boot-project-\\d+\\.\\d+\\.\\d+(?:\\.RELEASE)?\\.pom +logging.level.io.spring.concourse=DEBUG \ No newline at end of file diff --git a/ci/images/releasescripts/src/test/java/io/spring/concourse/releasescripts/artifactory/ArtifactoryServiceTests.java b/ci/images/releasescripts/src/test/java/io/spring/concourse/releasescripts/artifactory/ArtifactoryServiceTests.java index 6ce710ebdd..7cf1e7f067 100644 --- a/ci/images/releasescripts/src/test/java/io/spring/concourse/releasescripts/artifactory/ArtifactoryServiceTests.java +++ b/ci/images/releasescripts/src/test/java/io/spring/concourse/releasescripts/artifactory/ArtifactoryServiceTests.java @@ -84,9 +84,7 @@ class ArtifactoryServiceTests { @Test void promoteWhenSuccessful() { - this.server - .expect(requestTo( - "https://repo.spring.io/api/build/promote/" + "example-build" + "/" + "example-build-1")) + this.server.expect(requestTo("https://repo.spring.io/api/build/promote/example-build/example-build-1")) .andExpect(method(HttpMethod.POST)) .andExpect(content().json( "{\"status\": \"staged\", \"sourceRepo\": \"libs-staging-local\", \"targetRepo\": \"libs-milestone-local\"}")) @@ -99,11 +97,9 @@ class ArtifactoryServiceTests { @Test void promoteWhenArtifactsAlreadyPromoted() { - this.server - .expect(requestTo( - "https://repo.spring.io/api/build/promote/" + "example-build" + "/" + "example-build-1")) + this.server.expect(requestTo("https://repo.spring.io/api/build/promote/example-build/example-build-1")) .andRespond(withStatus(HttpStatus.CONFLICT)); - this.server.expect(requestTo("https://repo.spring.io/api/build/" + "example-build" + "/" + "example-build-1")) + this.server.expect(requestTo("https://repo.spring.io/api/build/example-build/example-build-1")) .andRespond(withJsonFrom("build-info-response.json")); this.service.promote("libs-release-local", getReleaseInfo()); this.server.verify(); @@ -111,11 +107,9 @@ class ArtifactoryServiceTests { @Test void promoteWhenCheckForArtifactsAlreadyPromotedFails() { - this.server - .expect(requestTo( - "https://repo.spring.io/api/build/promote/" + "example-build" + "/" + "example-build-1")) + this.server.expect(requestTo("https://repo.spring.io/api/build/promote/example-build/example-build-1")) .andRespond(withStatus(HttpStatus.CONFLICT)); - this.server.expect(requestTo("https://repo.spring.io/api/build/" + "example-build" + "/" + "example-build-1")) + this.server.expect(requestTo("https://repo.spring.io/api/build/example-build/example-build-1")) .andRespond(withStatus(HttpStatus.FORBIDDEN)); assertThatExceptionOfType(HttpClientErrorException.class) .isThrownBy(() -> this.service.promote("libs-release-local", getReleaseInfo())); @@ -124,11 +118,9 @@ class ArtifactoryServiceTests { @Test void promoteWhenPromotionFails() { - this.server - .expect(requestTo( - "https://repo.spring.io/api/build/promote/" + "example-build" + "/" + "example-build-1")) + this.server.expect(requestTo("https://repo.spring.io/api/build/promote/example-build/example-build-1")) .andRespond(withStatus(HttpStatus.CONFLICT)); - this.server.expect(requestTo("https://repo.spring.io/api/build/" + "example-build" + "/" + "example-build-1")) + this.server.expect(requestTo("https://repo.spring.io/api/build/example-build/example-build-1")) .andRespond(withJsonFrom("staged-build-info-response.json")); assertThatExceptionOfType(HttpClientErrorException.class) .isThrownBy(() -> this.service.promote("libs-release-local", getReleaseInfo())); @@ -158,9 +150,7 @@ class ArtifactoryServiceTests { @Test void distributeWhenFailure() throws Exception { ReleaseInfo releaseInfo = getReleaseInfo(); - this.server - .expect(requestTo( - "https://repo.spring.io/api/build/distribute/" + "example-build" + "/" + "example-build-1")) + this.server.expect(requestTo("https://repo.spring.io/api/build/distribute/example-build/example-build-1")) .andExpect(method(HttpMethod.POST)) .andExpect(content().json( "{\"sourceRepos\": [\"libs-release-local\"], \"targetRepo\" : \"spring-distributions\", \"async\":\"true\"}")) diff --git a/ci/images/releasescripts/src/test/java/io/spring/concourse/releasescripts/command/DistributeCommandTests.java b/ci/images/releasescripts/src/test/java/io/spring/concourse/releasescripts/command/DistributeCommandTests.java index ef949b0ada..080c10013a 100644 --- a/ci/images/releasescripts/src/test/java/io/spring/concourse/releasescripts/command/DistributeCommandTests.java +++ b/ci/images/releasescripts/src/test/java/io/spring/concourse/releasescripts/command/DistributeCommandTests.java @@ -16,6 +16,7 @@ package io.spring.concourse.releasescripts.command; +import java.util.Arrays; import java.util.Set; import com.fasterxml.jackson.databind.DeserializationFeature; @@ -42,6 +43,7 @@ import static org.mockito.Mockito.verifyNoInteractions; * Tests for {@link DistributeCommand}. * * @author Madhura Bhave + * @author Phillip Webb */ class DistributeCommandTests { @@ -55,8 +57,10 @@ class DistributeCommandTests { @BeforeEach void setup() { MockitoAnnotations.initMocks(this); + DistributeProperties distributeProperties = new DistributeProperties(); + distributeProperties.setOptionalDeployments(Arrays.asList(".*\\.zip", "demo-\\d\\.\\d\\.\\d\\.doc")); this.objectMapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - this.command = new DistributeCommand(this.service, this.objectMapper); + this.command = new DistributeCommand(this.service, this.objectMapper, distributeProperties); } @Test @@ -94,8 +98,30 @@ class DistributeCommandTests { assertThat(artifactDigests).containsExactly("aaaaaaaaa85f5c5093721f3ed0edda8ff8290yyyyyyyyyy"); } + @Test + @SuppressWarnings("unchecked") + void distributeWhenReleaseTypeReleaseAndFilteredShouldCallService() throws Exception { + ArgumentCaptor releaseInfoCaptor = ArgumentCaptor.forClass(ReleaseInfo.class); + ArgumentCaptor> artifactDigestCaptor = ArgumentCaptor.forClass(Set.class); + this.command.run(new DefaultApplicationArguments("distribute", "RELEASE", + getBuildInfoLocation("filtered-build-info-response.json"))); + verify(this.service).distribute(eq(ReleaseType.RELEASE.getRepo()), releaseInfoCaptor.capture(), + artifactDigestCaptor.capture()); + ReleaseInfo releaseInfo = releaseInfoCaptor.getValue(); + assertThat(releaseInfo.getBuildName()).isEqualTo("example"); + assertThat(releaseInfo.getBuildNumber()).isEqualTo("example-build-1"); + assertThat(releaseInfo.getGroupId()).isEqualTo("org.example.demo"); + assertThat(releaseInfo.getVersion()).isEqualTo("2.2.0"); + Set artifactDigests = artifactDigestCaptor.getValue(); + assertThat(artifactDigests).containsExactly("aaaaaaaaa85f5c5093721f3ed0edda8ff8290yyyyyyyyyy"); + } + private String getBuildInfoLocation() throws Exception { - return new ClassPathResource("build-info-response.json", ArtifactoryService.class).getFile().getAbsolutePath(); + return getBuildInfoLocation("build-info-response.json"); + } + + private String getBuildInfoLocation(String file) throws Exception { + return new ClassPathResource(file, ArtifactoryService.class).getFile().getAbsolutePath(); } } \ No newline at end of file diff --git a/ci/images/releasescripts/src/test/resources/io/spring/concourse/releasescripts/artifactory/filtered-build-info-response.json b/ci/images/releasescripts/src/test/resources/io/spring/concourse/releasescripts/artifactory/filtered-build-info-response.json new file mode 100644 index 0000000000..9f9935114d --- /dev/null +++ b/ci/images/releasescripts/src/test/resources/io/spring/concourse/releasescripts/artifactory/filtered-build-info-response.json @@ -0,0 +1,59 @@ +{ + "buildInfo": { + "version": "1.0.1", + "name": "example", + "number": "example-build-1", + "started": "2019-09-10T12:18:05.430+0000", + "durationMillis": 0, + "artifactoryPrincipal": "user", + "url": "https://my-ci.com", + "modules": [ + { + "id": "org.example.demo:demo:2.2.0", + "artifacts": [ + { + "type": "jar", + "sha1": "ayyyya9151a22cb3145538e523dbbaaaaaaaa", + "sha256": "aaaaaaaaa85f5c5093721f3ed0edda8ff8290yyyyyyyyyy", + "md5": "aaaaaacddea1724b0b69d8yyyyyyy", + "name": "demo-2.2.0.jar" + } + ] + }, + { + "id": "org.example.demo:demo:2.2.0:zip", + "artifacts": [ + { + "type": "zip", + "sha1": "ayyyya9151a22cb3145538e523dbbaaaaaaab", + "sha256": "aaaaaaaaa85f5c5093721f3ed0edda8ff8290yyyyyyyyyz", + "md5": "aaaaaacddea1724b0b69d8yyyyyyz", + "name": "demo-2.2.0.zip" + } + ] + }, + { + "id": "org.example.demo:demo:2.2.0:doc", + "artifacts": [ + { + "type": "jar", + "sha1": "ayyyya9151a22cb3145538e523dbbaaaaaaba", + "sha256": "aaaaaaaaa85f5c5093721f3ed0edda8ff8290yyyyyyyyzy", + "md5": "aaaaaacddea1724b0b69d8yyyyyzy", + "name": "demo-2.2.0.doc" + } + ] + } + ], + "statuses": [ + { + "status": "staged", + "repository": "libs-release-local", + "timestamp": "2019-09-10T12:42:24.716+0000", + "user": "user", + "timestampDate": 1568119344716 + } + ] + }, + "uri": "https://my-artifactory-repo.com/api/build/example/example-build-1" +} \ No newline at end of file