From 2deb04af1627066379e3776db588f46fb10dd809 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Thu, 24 Oct 2019 13:54:36 +0200 Subject: [PATCH] Added dry-run option; fixes gh-163 --- .../release/internal/ReleaserProperties.java | 6 +- .../release/internal/options/Options.java | 14 +++- .../internal/options/OptionsBuilder.java | 9 ++- .../internal/spring/OptionsParser.java | 7 +- .../spring/ReleaserPropertiesUpdater.java | 2 +- .../internal/spring/SpringReleaser.java | 12 +-- .../cloud/release/internal/spring/Tasks.java | 18 ++++- .../internal/spring/AcceptanceTests.java | 78 ++++++++++++++++++- .../release/internal/spring/TaskUtils.java | 33 ++++++++ 9 files changed, 160 insertions(+), 19 deletions(-) create mode 100644 spring-cloud-release-tools-spring/src/test/java/org/springframework/cloud/release/internal/spring/TaskUtils.java diff --git a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/ReleaserProperties.java b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/ReleaserProperties.java index 0a65a952..a8265026 100644 --- a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/ReleaserProperties.java +++ b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/ReleaserProperties.java @@ -679,8 +679,7 @@ public class ReleaserProperties implements Serializable { private List ignoredPomRegex = Arrays.asList("^.*\\.git/.*$", "^.*spring-cloud-contract-maven-plugin/src/test/projects/.*$", "^.*spring-cloud-contract-maven-plugin/target/.*$", - "^.*src/test/bats/.*$", - "^.*samples/standalone/[a-z]+/.*$"); + "^.*src/test/bats/.*$", "^.*samples/standalone/[a-z]+/.*$"); public String getBranch() { return this.branch; @@ -1002,8 +1001,7 @@ public class ReleaserProperties implements Serializable { private List ignoredGradleRegex = Arrays.asList( "^.*spring-cloud-contract-maven-plugin/src/test/projects/.*$", "^.*spring-cloud-contract-maven-plugin/target/.*$", - "^.*src/test/bats/.*$", - "^.*samples/standalone/[a-z]+/.*$"); + "^.*src/test/bats/.*$", "^.*samples/standalone/[a-z]+/.*$"); /** * Command to be executed to build the project. diff --git a/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/options/Options.java b/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/options/Options.java index 4eec8573..4092be6a 100644 --- a/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/options/Options.java +++ b/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/options/Options.java @@ -39,6 +39,11 @@ public class Options { */ public Boolean interactive; + /** + * Is dry run set. + */ + public Boolean dryRun; + /** * List of task names to release. */ @@ -54,11 +59,12 @@ public class Options { */ public String range = ""; - Options(Boolean metaRelease, Boolean fullRelease, Boolean interactive, + Options(Boolean metaRelease, Boolean fullRelease, Boolean interactive, Boolean dryRun, List taskNames, String startFrom, String range) { this.metaRelease = metaRelease; this.fullRelease = fullRelease; this.interactive = interactive; + this.dryRun = dryRun; this.taskNames = taskNames.stream().map(this::removeQuotingChars) .collect(Collectors.toList()); this.startFrom = removeQuotingChars(startFrom); @@ -75,9 +81,9 @@ public class Options { @Override public String toString() { return "Options{" + "metaRelease=" + this.metaRelease + ", fullRelease=" - + this.fullRelease + ", interactive=" + this.interactive + ", taskNames=" - + this.taskNames + ", startFrom='" + this.startFrom + '\'' + ", range='" - + this.range + '\'' + '}'; + + this.fullRelease + ", interactive=" + this.interactive + ", dryRun=" + + this.dryRun + ", taskNames=" + this.taskNames + ", startFrom='" + + this.startFrom + '\'' + ", range='" + this.range + '\'' + '}'; } } diff --git a/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/options/OptionsBuilder.java b/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/options/OptionsBuilder.java index edd46ba0..f1efff5b 100644 --- a/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/options/OptionsBuilder.java +++ b/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/options/OptionsBuilder.java @@ -27,6 +27,8 @@ public class OptionsBuilder { private Boolean interactive = true; + private Boolean dryRun = false; + private List taskNames = new ArrayList<>(); private String startFrom = ""; @@ -48,6 +50,11 @@ public class OptionsBuilder { return this; } + public OptionsBuilder dryRun(Boolean dryRun) { + this.dryRun = dryRun; + return this; + } + public OptionsBuilder taskNames(List taskNames) { this.taskNames = taskNames; return this; @@ -65,7 +72,7 @@ public class OptionsBuilder { public Options options() { return new Options(this.metaRelease, this.fullRelease, this.interactive, - this.taskNames, this.startFrom, this.range); + this.dryRun, this.taskNames, this.startFrom, this.range); } } diff --git a/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/OptionsParser.java b/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/OptionsParser.java index b2646400..a65adb09 100644 --- a/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/OptionsParser.java +++ b/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/OptionsParser.java @@ -58,6 +58,10 @@ class OptionsParser implements Parser { Arrays.asList("i", "interactive"), "Do you want to set the properties from the command line of a single project?") .withRequiredArg().ofType(Boolean.class).defaultsTo(true); + ArgumentAcceptingOptionSpec dryRunOpt = parser.acceptsAll( + Arrays.asList("dr", "dry-run"), + "Do you want to do the release / meta release with build and install projects locally only?") + .withRequiredArg().ofType(Boolean.class).defaultsTo(false); Tasks.NON_COMPOSITE_TASKS.forEach( task -> parser.acceptsAll(Arrays.asList(task.shortName, task.name), task.description).withOptionalArg()); @@ -85,6 +89,7 @@ class OptionsParser implements Parser { } Boolean metaRelease = options.valueOf(metaReleaseOpt); Boolean interactive = options.valueOf(interactiveOpt); + Boolean dryRun = options.valueOf(dryRunOpt); Boolean fullRelease = options.has(fullReleaseOpt); List providedTaskNames = StringUtils.hasText(options .valueOf(taskNamesOpt)) ? Arrays.asList( @@ -107,7 +112,7 @@ class OptionsParser implements Parser { String startFrom = options.valueOf(startFromOpt); String range = options.valueOf(rangeOpt); Options buildOptions = new OptionsBuilder().metaRelease(metaRelease) - .fullRelease(fullRelease).interactive(interactive) + .fullRelease(fullRelease).interactive(interactive).dryRun(dryRun) .taskNames(taskNames).startFrom(startFrom).range(range).options(); log.info( "\n\nWill use the following options to process the project\n\n{}\n\n", diff --git a/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/ReleaserPropertiesUpdater.java b/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/ReleaserPropertiesUpdater.java index c0dcdc17..9b79aed8 100644 --- a/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/ReleaserPropertiesUpdater.java +++ b/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/ReleaserPropertiesUpdater.java @@ -51,7 +51,7 @@ class ReleaserPropertiesUpdater { ReleaserProperties props = updatePropertiesFromFile(properties, clonedProjectFromOrg); props.setWorkingDir(clonedProjectFromOrg.getAbsolutePath()); - log.info("Updated properties [\n\n{}\n\n]", props); + log.trace("Updated properties [\n\n{}\n\n]", props); updateProperties(props); return props; } diff --git a/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/SpringReleaser.java b/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/SpringReleaser.java index d8bdba50..8215141d 100644 --- a/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/SpringReleaser.java +++ b/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/SpringReleaser.java @@ -121,7 +121,7 @@ public class SpringReleaser { List processedProjects = new ArrayList<>(); if (options.metaRelease) { ReleaserProperties original = this.properties.copy(); - log.debug("The following properties were found [{}]", original); + log.trace("The following properties were found [{}]", original); processedProjects = metaReleaseProjects(options).stream() .map(project -> processProjectForMetaRelease(original.copy(), options, project)) @@ -133,8 +133,10 @@ public class SpringReleaser { File projectFolder = projectFolder(); projectsAndVersion = processProject(options, projectFolder, TaskType.RELEASE); } - this.optionsProcessor.postReleaseOptions(options, - postReleaseOptionsAgs(options, projectsAndVersion, processedProjects)); + if (!options.dryRun) { + this.optionsProcessor.postReleaseOptions(options, postReleaseOptionsAgs( + options, projectsAndVersion, processedProjects)); + } } private void prepareForMetaRelease(Options options) { @@ -146,7 +148,7 @@ public class SpringReleaser { ProcessedProject processProjectForMetaRelease(ReleaserProperties copy, Options options, String project) { - log.info("Original properties [\n\n{}\n\n]", copy); + log.trace("Original properties [\n\n{}\n\n]", copy); File clonedProjectFromOrg = this.releaser.clonedProjectFromOrg(project); copy = updatePropertiesIfCustomConfigPresent(copy, clonedProjectFromOrg); log.info("Successfully cloned the project [{}] to [{}]", project, @@ -289,7 +291,7 @@ public class SpringReleaser { projectsAndVersion.projectVersions, originalVersion, projectsAndVersion.versionFromBom, this.properties, options.interactive, taskType, this.applicationEventPublisher); - log.debug("Processing project [{}] with args [{}]", project, defaultArgs); + log.trace("Processing project [{}] with args [{}]", project, defaultArgs); this.optionsProcessor.processOptions(options, defaultArgs); return projectsAndVersion; } diff --git a/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/Tasks.java b/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/Tasks.java index 9f2f915c..1c343887 100644 --- a/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/Tasks.java +++ b/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/Tasks.java @@ -116,6 +116,9 @@ final class Tasks { Tasks.CLOSE_MILESTONE, Tasks.UPDATE_SAGAN) .collect(Collectors.toList()); + static final List DEFAULT_DRY_RUN_TASKS_PER_PROJECT = Stream + .of(Tasks.UPDATING_POMS, Tasks.BUILD_PROJECT).collect(Collectors.toList()); + static final List DEFAULT_TASKS_PER_RELEASE = Stream .of(Tasks.RUN_UPDATED_SAMPLES, Tasks.CREATE_TEMPLATES, Tasks.UPDATE_GUIDES, Tasks.UPDATE_START_SPRING_IO, @@ -133,6 +136,12 @@ final class Tasks { static Task RELEASE = Tasks.task("release", "fr", "FULL RELEASE", "Perform a full release of this project without interruptions", args -> new CompositeConsumer(DEFAULT_TASKS_PER_PROJECT).accept(args)); + + static Task DRY_RUN = Tasks.task("dryRun", "dr", "DRY RUN", + "Perform a dry run release of a single project - bumps versions and installs them locally", + args -> new CompositeConsumer(DEFAULT_DRY_RUN_TASKS_PER_PROJECT) + .accept(args)); + static Task POST_RELEASE = Tasks.task("postRelease", "pr", "POST RELEASE TASKS", "Perform post release tasks for this release without interruptions", args -> new CompositeConsumer(DEFAULT_TASKS_PER_RELEASE).accept(args), @@ -146,9 +155,14 @@ final class Tasks { args -> new CompositeConsumer(DEFAULT_TASKS_PER_PROJECT, (args1 -> args.properties.getMetaRelease().setEnabled(true))) .accept(args)); + static Task META_RELEASE_DRY_RUN = Tasks.task("metaReleaseDryRun", "xdr", + "META RELEASE DRY RUN", "Perform a meta release dry run of projects", + args -> new CompositeConsumer(DEFAULT_DRY_RUN_TASKS_PER_PROJECT, + (args1 -> args.properties.getMetaRelease().setEnabled(true))) + .accept(args)); - static final List COMPOSITE_TASKS = Stream - .of(RELEASE, RELEASE_VERBOSE, META_RELEASE, POST_RELEASE) + static final List COMPOSITE_TASKS = Stream.of(RELEASE, RELEASE_VERBOSE, DRY_RUN, + META_RELEASE, POST_RELEASE, META_RELEASE_DRY_RUN) .collect(Collectors.toList()); static final List ALL_TASKS_PER_PROJECT = Stream diff --git a/spring-cloud-release-tools-spring/src/test/java/org/springframework/cloud/release/internal/spring/AcceptanceTests.java b/spring-cloud-release-tools-spring/src/test/java/org/springframework/cloud/release/internal/spring/AcceptanceTests.java index f50e44f2..d5b9e19d 100644 --- a/spring-cloud-release-tools-spring/src/test/java/org/springframework/cloud/release/internal/spring/AcceptanceTests.java +++ b/spring-cloud-release-tools-spring/src/test/java/org/springframework/cloud/release/internal/spring/AcceptanceTests.java @@ -270,16 +270,45 @@ public class AcceptanceTests { thenUpdateReleaseTrainDocsWasCalled(); } + @Test + public void should_perform_a_meta_release_dry_run_of_sc_release_and_consul() + throws Exception { + // simulates an org + GitTestUtils.openGitProject(file("/projects/spring-cloud-release/")).checkout() + .setName("Edgware").call(); + SpringReleaser releaser = metaReleaserDryRun(edgwareSr10()); + + releaser.release(new OptionsBuilder().metaRelease(true).dryRun(true).options()); + + // consul, release + then(this.nonAssertingGitHandler.clonedProjects).hasSize(2); + thenAllDryRunStepsWereExecutedForEachProject(); + thenSaganWasNotCalled(); + thenDocumentationWasNotUpdated(); + BDDAssertions.then(clonedProject("spring-cloud-consul").tagList().call()) + .extracting("name").doesNotContain("refs/tags/v5.3.5.RELEASE"); + thenRunUpdatedTestsWereNotCalled(); + thenUpdateReleaseTrainDocsWasNotCalled(); + } + private void thenRunUpdatedTestsWereCalled() { BDDMockito.then(this.postReleaseActions).should() .runUpdatedTests(BDDMockito.any(Projects.class)); } + private void thenRunUpdatedTestsWereNotCalled() { + BDDMockito.then(this.postReleaseActions).shouldHaveNoInteractions(); + } + private void thenUpdateReleaseTrainDocsWasCalled() { BDDMockito.then(this.postReleaseActions).should() .generateReleaseTrainDocumentation(BDDMockito.any(Projects.class)); } + private void thenUpdateReleaseTrainDocsWasNotCalled() { + BDDMockito.then(this.postReleaseActions).shouldHaveNoInteractions(); + } + private Map edgwareSr10() { Map versions = new LinkedHashMap<>(); versions.put("spring-boot", "5.5.16.RELEASE"); @@ -320,6 +349,10 @@ public class AcceptanceTests { BDDMockito.any(ProjectVersion.class)); } + private void thenSaganWasNotCalled() { + BDDMockito.then(this.saganUpdater).shouldHaveNoInteractions(); + } + private void thenAllStepsWereExecutedForEachProject() { this.nonAssertingGitHandler.clonedProjects.stream() .filter(f -> !f.getName().contains("angel") @@ -333,6 +366,20 @@ public class AcceptanceTests { }); } + private void thenAllDryRunStepsWereExecutedForEachProject() { + this.nonAssertingGitHandler.clonedProjects.stream() + .filter(f -> !f.getName().contains("angel") + && !f.getName().equals("spring-cloud")) + .forEach(project -> { + then(Arrays.asList("spring-cloud-starter-build", + "spring-cloud-consul")) + .contains(pom(project).getArtifactId()); + then(this.capture.toString()).contains("executed_build"); + then(this.capture.toString()).doesNotContain("executed_deploy", + "executed_docs"); + }); + } + @Test public void should_not_clone_any_projects_when_they_are_on_list_of_projects_to_skip() throws Exception { @@ -414,6 +461,10 @@ public class AcceptanceTests { BDDMockito.any(ProjectVersion.class), BDDMockito.anyString()); } + private void thenDocumentationWasNotUpdated() { + BDDMockito.then(this.documentationUpdater).shouldHaveNoInteractions(); + } + private void thenWikiPageWasUpdated() { BDDMockito.then(this.documentationUpdater).should() .updateReleaseTrainWiki(BDDMockito.any(Projects.class)); @@ -636,6 +687,12 @@ public class AcceptanceTests { return metaReleaserWithFullDeployment(properties); } + private SpringReleaser metaReleaserDryRun(Map versions) + throws Exception { + ReleaserProperties properties = metaReleaserProperties(versions); + return metaReleaserWithDryRun(properties); + } + private SpringReleaser releaserWithFullDeployment(String expectedVersion, String projectName, ReleaserProperties properties) throws Exception { Releaser releaser = defaultReleaser(expectedVersion, projectName, properties); @@ -672,6 +729,25 @@ public class AcceptanceTests { }, this.updater, this.applicationEventPublisher); } + private SpringReleaser metaReleaserWithDryRun(ReleaserProperties properties) + throws Exception { + Releaser releaser = defaultMetaReleaser(properties); + return new SpringReleaser(releaser, properties, new OptionsProcessor(releaser, + properties, this.applicationEventPublisher) { + @Override + String chosenOption() { + // meta release dry run + return String.valueOf(TaskUtils.indexOf(Tasks.META_RELEASE_DRY_RUN)); + } + + @Override + void postReleaseOptions(Options options, Args defaultArgs) { + options.interactive = false; + super.postReleaseOptions(options, defaultArgs); + } + }, this.updater, this.applicationEventPublisher); + } + private SpringReleaser releaserWithSnapshotScRelease(File projectFile, String projectName, String branch, String expectedVersion) throws Exception { ReleaserProperties properties = snapshotScReleaseReleaserProperties(projectFile, @@ -687,7 +763,7 @@ public class AcceptanceTests { properties, this.applicationEventPublisher) { @Override String chosenOption() { - return "13"; + return String.valueOf(TaskUtils.indexOf(Tasks.CREATE_TEMPLATES)); } @Override diff --git a/spring-cloud-release-tools-spring/src/test/java/org/springframework/cloud/release/internal/spring/TaskUtils.java b/spring-cloud-release-tools-spring/src/test/java/org/springframework/cloud/release/internal/spring/TaskUtils.java new file mode 100644 index 00000000..a518c25b --- /dev/null +++ b/spring-cloud-release-tools-spring/src/test/java/org/springframework/cloud/release/internal/spring/TaskUtils.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013-2019 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 org.springframework.cloud.release.internal.spring; + +import java.util.List; + +class TaskUtils { + + static int indexOf(Task task) { + List compositeTasks = Tasks.ALL_TASKS_PER_PROJECT; + for (int i = 0; i < compositeTasks.size(); i++) { + if (task == compositeTasks.get(i)) { + return i; + } + } + return -1; + } + +}