Meta release (#84)

## How it works?

- Uses the fixed versions to clone and check out each project (e.g. `spring-cloud-sleuth: 2.1.0.RELEASE`)
- From the version analyzes the branch and checks it out. E.g.
  - for `spring-cloud-release`'s `Finchley.RELEASE` version will resolve either `Finchley.x` branch or will fallback to `master` if there's no `Finchley.x` branch.
  - for `spring-cloud-sleuth`'s `2.1.0.RELEASE` version will resolve `2.1.x` branch
- Performs the release tasks per each project
- Performs the post release tasks at the end of the release

## Required options

- `releaser.fixed-versions` - A String to String mapping of manually set versions. E.g. `"spring-cloud-cli" -> "1.0.0.RELEASE"` will set
the `spring-cloud-cli.version` to `1.0.0.RELEASE` regardless of what was set in `spring-cloud-release` project. Example `--releaser.fixed-versions[spring-cloud-cli]=1.0.0.RELEASE`.
Use these properties to provide versions for the meta release.

- `releaser.meta-release.enabled` - You have to turn it on to enable a meta release. Defaults to `false`
- `releaser.meta-release.git-org-url` - The URL of the Git organization. We'll append each project's name to it.
Defaults to `https://github.com/spring-cloud`

run the task via `-x=true`
This commit is contained in:
Marcin Grzejszczak
2018-06-25 09:21:24 +02:00
committed by GitHub
parent 8f3936d727
commit 9578294fbd
39 changed files with 1156 additions and 214 deletions

View File

@@ -25,6 +25,10 @@ why this tool makes it easy to automate the release / dependency update process
=== What does it do?
==== Single project
For a single project
- Clones the Spring Cloud Release project and picks all versions (Boot + Cloud projects)
- Modifies the project versions with values from SC-Release
* throws an exception when we bump versions to release and there's a SNAPSHOT version referenced in the POM
@@ -36,6 +40,12 @@ why this tool makes it easy to automate the release / dependency update process
- Publishes the docs (to `spring-cloud-static` for non-snapshots, to `gh-pages` for snapshots)
- Reverts back to snapshots, bumps the version by a patch (`1.0.1.RELEASE` -> `1.0.2.BUILD-SNAPSHOT`) (ONLY FOR RELEASE VERSIONS)
- Closes the milestone on Github (e.g. `v1.0.1.RELEASE`) (ONLY FOR NON-SNAPSHOT VERSIONS)
IMPORTANT: Starting with version that does Sagan integration, you MUST pass the OAuth token,
otherwise the application will fail to start
After project release
- Generates an email template under `target/email.txt` (ONLY FOR NON-SNAPSHOT VERSIONS)
- Generates a blog template under `target/blog.md` (ONLY FOR NON-SNAPSHOT VERSIONS)
- Generates a tweet template under `target/tweet.txt` (ONLY FOR NON-SNAPSHOT VERSIONS)
@@ -44,8 +54,17 @@ why this tool makes it easy to automate the release / dependency update process
- For `GA`/ `SR` release will create an issue in Spring Guides under https://github.com/spring-guides/getting-started-guides/issues/
- For `GA`/ `SR` release will update the links under https://github.com/spring-cloud/spring-cloud-static/tree/gh-pages/current
IMPORTANT: Starting with version that does Sagan integration, you MUST pass the OAuth token,
otherwise the application will fail to start
==== Meta-release
- Uses the fixed versions to clone and check out each project (e.g. `spring-cloud-sleuth: 2.1.0.RELEASE`)
- From the version analyzes the branch and checks it out. E.g.
** for `spring-cloud-release`'s `Finchley.RELEASE` version will resolve either `Finchley.x` branch or will fallback to `master` if there's no `Finchley.x` branch.
** for `spring-cloud-sleuth`'s `2.1.0.RELEASE` version will resolve `2.1.x` branch
- Performs the release tasks per each project
- Performs the post release tasks at the end of the release
IMPORTANT: For the meta-releaser to work we assume that the path to the
custom configuration file for each project is always `config/releaser.yml`.
=== What should I do first?
@@ -225,10 +244,31 @@ $ java -jar ~/repo/spring-cloud-release-tools/spring-cloud-release-tools-spring/
$ java -jar ~/repo/spring-cloud-release-tools/spring-cloud-release-tools-spring/target/spring-cloud-release-tools-spring-1.0.0.BUILD-SNAPSHOT.jar --releaser.pom.branch=vDalston.SR1 --spring.config.name=releaser --closeMilestone -i=false
----
=== How to run meta-release (automatic-mode)
All you have to do is run the jar with the releaser and pass the
`-x=true` option to turn on meta-release and a list of fixed versions
in the `--"releaser.fixed-versions[project-name]=project-version" format
```
$ java -jar spring-cloud-release-tools-spring/target/spring-cloud-release-tools-spring-1.0.0.BUILD-SNAPSHOT.jar --spring.config.name=releaser -x=true --"releaser.fixed-versions[spring-cloud-sleuth]=2.0.1.BUILD-SNAPSHOT"
```
IMPORTANT: For the meta release the `startFrom` or `taskNames` take into consideration
the project names, not task names. E.g. you can start from `spring-cloud-netflix` project,
or build only tasks with names `spring-cloud-build,spring-cloud-sleuth`.
=== Project options
- `releaser.fixed-versions` - A String to String mapping of manually set versions. E.g. `"spring-cloud-cli" -> "1.0.0.RELEASE"` will set
the `spring-cloud-cli.version` to `1.0.0.RELEASE` regardless of what was set in `spring-cloud-release` project. Example `--releaser.fixed-versions[spring-cloud-cli]=1.0.0.RELEASE`.
Use these properties to provide versions for the meta release.
- `releaser.meta-release.enabled` - You have to turn it on to enable a meta release. Defaults to `false`
- `releaser.meta-release.release-train-project-name` - Name of the project that represents the BOM of the release train. Defaults to `spring-cloud-release`
- `releaser.meta-release.git-org-url` - The URL of the Git organization. We'll append each project's name to it.
Defaults to `https://github.com/spring-cloud`
- `releaser.git.fetch-versions-from-git` - If `true` then should fill the map of versions from Git. If `false` then picks fixed versions
- `releaser.git.clone-destination-dir` - Where should the Spring Cloud Release repo get cloned to. If null defaults to a temporary directory
- `releaser.git.spring-cloud-release-git-url` - URL to Spring Cloud Release Git repository. Defaults to `https://github.com/spring-cloud/spring-cloud-release`
@@ -282,6 +322,9 @@ $ java -jar target/spring-cloud-release-tools-spring-1.0.0.M1.jar --spring.confi
TIP: Notice that we're downloading the jar to a parent folder, not to `target`. That's because `target` get cleaned
during the build process
IMPORTANT: For the meta-releaser to work we assume that the path to the
configuration file is always `config/releaser.yml`.
==== Specifying A Branch
By deafult the releaser will default to using the `master` branch of `spring-cloud-release`.

View File

@@ -15,6 +15,10 @@ why this tool makes it easy to automate the release / dependency update process
=== What does it do?
==== Single project
For a single project
- Clones the Spring Cloud Release project and picks all versions (Boot + Cloud projects)
- Modifies the project versions with values from SC-Release
* throws an exception when we bump versions to release and there's a SNAPSHOT version referenced in the POM
@@ -26,6 +30,12 @@ why this tool makes it easy to automate the release / dependency update process
- Publishes the docs (to `spring-cloud-static` for non-snapshots, to `gh-pages` for snapshots)
- Reverts back to snapshots, bumps the version by a patch (`1.0.1.RELEASE` -> `1.0.2.BUILD-SNAPSHOT`) (ONLY FOR RELEASE VERSIONS)
- Closes the milestone on Github (e.g. `v1.0.1.RELEASE`) (ONLY FOR NON-SNAPSHOT VERSIONS)
IMPORTANT: Starting with version that does Sagan integration, you MUST pass the OAuth token,
otherwise the application will fail to start
After project release
- Generates an email template under `target/email.txt` (ONLY FOR NON-SNAPSHOT VERSIONS)
- Generates a blog template under `target/blog.md` (ONLY FOR NON-SNAPSHOT VERSIONS)
- Generates a tweet template under `target/tweet.txt` (ONLY FOR NON-SNAPSHOT VERSIONS)
@@ -34,8 +44,17 @@ why this tool makes it easy to automate the release / dependency update process
- For `GA`/ `SR` release will create an issue in Spring Guides under https://github.com/spring-guides/getting-started-guides/issues/
- For `GA`/ `SR` release will update the links under https://github.com/spring-cloud/spring-cloud-static/tree/gh-pages/current
IMPORTANT: Starting with version that does Sagan integration, you MUST pass the OAuth token,
otherwise the application will fail to start
==== Meta-release
- Uses the fixed versions to clone and check out each project (e.g. `spring-cloud-sleuth: 2.1.0.RELEASE`)
- From the version analyzes the branch and checks it out. E.g.
** for `spring-cloud-release`'s `Finchley.RELEASE` version will resolve either `Finchley.x` branch or will fallback to `master` if there's no `Finchley.x` branch.
** for `spring-cloud-sleuth`'s `2.1.0.RELEASE` version will resolve `2.1.x` branch
- Performs the release tasks per each project
- Performs the post release tasks at the end of the release
IMPORTANT: For the meta-releaser to work we assume that the path to the
custom configuration file for each project is always `config/releaser.yml`.
=== What should I do first?
@@ -215,10 +234,31 @@ $ java -jar ~/repo/spring-cloud-release-tools/spring-cloud-release-tools-spring/
$ java -jar ~/repo/spring-cloud-release-tools/spring-cloud-release-tools-spring/target/spring-cloud-release-tools-spring-1.0.0.BUILD-SNAPSHOT.jar --releaser.pom.branch=vDalston.SR1 --spring.config.name=releaser --closeMilestone -i=false
----
=== How to run meta-release (automatic-mode)
All you have to do is run the jar with the releaser and pass the
`-x=true` option to turn on meta-release and a list of fixed versions
in the `--"releaser.fixed-versions[project-name]=project-version" format
```
$ java -jar spring-cloud-release-tools-spring/target/spring-cloud-release-tools-spring-1.0.0.BUILD-SNAPSHOT.jar --spring.config.name=releaser -x=true --"releaser.fixed-versions[spring-cloud-sleuth]=2.0.1.BUILD-SNAPSHOT"
```
IMPORTANT: For the meta release the `startFrom` or `taskNames` take into consideration
the project names, not task names. E.g. you can start from `spring-cloud-netflix` project,
or build only tasks with names `spring-cloud-build,spring-cloud-sleuth`.
=== Project options
- `releaser.fixed-versions` - A String to String mapping of manually set versions. E.g. `"spring-cloud-cli" -> "1.0.0.RELEASE"` will set
the `spring-cloud-cli.version` to `1.0.0.RELEASE` regardless of what was set in `spring-cloud-release` project. Example `--releaser.fixed-versions[spring-cloud-cli]=1.0.0.RELEASE`.
Use these properties to provide versions for the meta release.
- `releaser.meta-release.enabled` - You have to turn it on to enable a meta release. Defaults to `false`
- `releaser.meta-release.release-train-project-name` - Name of the project that represents the BOM of the release train. Defaults to `spring-cloud-release`
- `releaser.meta-release.git-org-url` - The URL of the Git organization. We'll append each project's name to it.
Defaults to `https://github.com/spring-cloud`
- `releaser.git.fetch-versions-from-git` - If `true` then should fill the map of versions from Git. If `false` then picks fixed versions
- `releaser.git.clone-destination-dir` - Where should the Spring Cloud Release repo get cloned to. If null defaults to a temporary directory
- `releaser.git.spring-cloud-release-git-url` - URL to Spring Cloud Release Git repository. Defaults to `https://github.com/spring-cloud/spring-cloud-release`
@@ -272,6 +312,9 @@ $ java -jar target/spring-cloud-release-tools-spring-1.0.0.M1.jar --spring.confi
TIP: Notice that we're downloading the jar to a parent folder, not to `target`. That's because `target` get cleaned
during the build process
IMPORTANT: For the meta-releaser to work we assume that the path to the
configuration file is always `config/releaser.yml`.
==== Specifying A Branch
By deafult the releaser will default to using the `master` branch of `spring-cloud-release`.

View File

@@ -12,7 +12,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId>
<version>1.3.9.RELEASE</version>
<version>2.0.0.RELEASE</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
@@ -24,7 +24,7 @@
</modules>
<properties>
<spring-cloud-bom.version>Edgware.BUILD-SNAPSHOT</spring-cloud-bom.version>
<spring-cloud-bom.version>Finchley.RELEASE</spring-cloud-bom.version>
</properties>
<dependencyManagement>

View File

View File

@@ -37,6 +37,7 @@
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.4.1.Final</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>

View File

@@ -46,6 +46,10 @@ public class Releaser {
this.documentationUpdater = documentationUpdater;
}
public File clonedProjectFromOrg(String projectName) {
return this.projectGitHandler.cloneProjectFromOrg(projectName);
}
public Projects retrieveVersionsFromSCRelease() {
return this.projectPomUpdater.retrieveVersionsFromSCRelease();
}

View File

@@ -45,8 +45,56 @@ public class ReleaserProperties {
private Sagan sagan = new Sagan();
/**
* Project name to its version - overrides all versions
* retrieved from a repository like Spring Cloud Release
*/
private Map<String, String> fixedVersions = new HashMap<>();
private MetaRelease metaRelease = new MetaRelease();
public static class MetaRelease {
/**
* Are we releasing the whole suite of apps or only one?
*/
private boolean enabled = false;
/**
* Name of the release train project
*/
private String releaseTrainProjectName = "spring-cloud-release";
/**
* The URL of the Git organization. We'll append each project's
* name to it
*/
private String gitOrgUrl = "https://github.com/spring-cloud";
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getGitOrgUrl() {
return this.gitOrgUrl;
}
public void setGitOrgUrl(String gitOrgUrl) {
this.gitOrgUrl = gitOrgUrl;
}
public String getReleaseTrainProjectName() {
return this.releaseTrainProjectName;
}
public void setReleaseTrainProjectName(String releaseTrainProjectName) {
this.releaseTrainProjectName = releaseTrainProjectName;
}
}
public static class Git {
/**
@@ -388,6 +436,14 @@ public class ReleaserProperties {
this.fixedVersions = fixedVersions;
}
public MetaRelease getMetaRelease() {
return this.metaRelease;
}
public void setMetaRelease(MetaRelease metaRelease) {
this.metaRelease = metaRelease;
}
public Sagan getSagan() {
return this.sagan;
}

View File

@@ -0,0 +1,9 @@
package org.springframework.cloud.release.internal;
/**
* @author Marcin Grzejszczak
*/
public interface ReleaserPropertiesAware {
void setReleaserProperties(ReleaserProperties properties);
}

View File

@@ -112,13 +112,12 @@ class GitRepo {
/**
* Checks out a branch for a project
* @param project - a Git project
* @param branch - branch to check out
*/
void checkout(File project, String branch) {
void checkout(String branch) {
try {
log.info("Checking out branch [{}] for repo [{}]", branch, this.basedir);
checkoutBranch(project, branch);
checkoutBranch(this.basedir, branch);
log.info("Successfully checked out the branch [{}]", branch);
}
catch (Exception e) {
@@ -128,11 +127,10 @@ class GitRepo {
/**
* Performs a commit
* @param project - a Git project
* @param message - commit message
*/
void commit(File project, String message) {
try(Git git = this.gitFactory.open(file(project))) {
void commit(String message) {
try(Git git = this.gitFactory.open(file(this.basedir))) {
git.add().addFilepattern(".").call();
git.commit().setAllowEmpty(false).setMessage(message).call();
printLog(git);
@@ -143,6 +141,16 @@ class GitRepo {
}
}
boolean hasBranch(String branch) {
try(Git git = this.gitFactory.open(file(this.basedir))) {
List<Ref> refs = git.branchList().setListMode(ListBranchCommand.ListMode.ALL)
.call();
return refs.stream().anyMatch(ref -> ref.getName().contains(branch));
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private void printLog(Git git) throws GitAPIException, IOException {
int maxCount = 5;
String currentBranch = git.getRepository().getBranch();
@@ -155,11 +163,10 @@ class GitRepo {
/**
* Creates a tag with a given name
* @param project
* @param tagName
*/
void tag(File project, String tagName) {
try(Git git = this.gitFactory.open(file(project))) {
void tag(String tagName) {
try(Git git = this.gitFactory.open(file(this.basedir))) {
git.tag().setName(tagName).call();
} catch (Exception e) {
throw new IllegalStateException(e);
@@ -168,11 +175,10 @@ class GitRepo {
/**
* Pushes the commits to {@code origin} remote branch
* @param project - Git project
* @param branch - remote branch to which the code should be pushed
*/
void pushBranch(File project, String branch) {
try(Git git = this.gitFactory.open(file(project))) {
void pushBranch(String branch) {
try(Git git = this.gitFactory.open(file(this.basedir))) {
String localBranch = git.getRepository().getFullBranch();
RefSpec refSpec = new RefSpec(localBranch + ":" + branch);
this.gitFactory.push(git).setPushTags().setRefSpecs(refSpec).call();
@@ -183,10 +189,9 @@ class GitRepo {
/**
* Pushes the commits od current branch
* @param project - Git project
*/
void pushCurrentBranch(File project) {
try(Git git = this.gitFactory.open(file(project))) {
void pushCurrentBranch() {
try(Git git = this.gitFactory.open(file(this.basedir))) {
this.gitFactory.push(git).call();
} catch (Exception e) {
throw new IllegalStateException(e);
@@ -195,11 +200,10 @@ class GitRepo {
/**
* Pushes the commits to {@code origin} remote tag
* @param project - Git project
* @param tagName - remote tag to which the code should be pushed
*/
void pushTag(File project, String tagName) {
try(Git git = this.gitFactory.open(file(project))) {
void pushTag(String tagName) {
try(Git git = this.gitFactory.open(file(this.basedir))) {
String localBranch = git.getRepository().getFullBranch();
RefSpec refSpec = new RefSpec(localBranch + ":" + "refs/tags/" + tagName);
this.gitFactory.push(git).setPushTags().setRefSpecs(refSpec).call();
@@ -208,8 +212,8 @@ class GitRepo {
}
}
void revert(File project, String message) {
try(Git git = this.gitFactory.open(file(project))) {
void revert(String message) {
try(Git git = this.gitFactory.open(file(this.basedir))) {
RevCommit commit = git.log().setMaxCount(1).call().iterator().next();
String shortMessage = commit.getShortMessage();
String id = commit.getId().getName();
@@ -227,8 +231,8 @@ class GitRepo {
}
}
String currentBranch(File project) {
try(Git git = this.gitFactory.open(file(project))) {
String currentBranch() {
try(Git git = this.gitFactory.open(file(this.basedir))) {
return git.getRepository().getBranch();
} catch (Exception e) {
throw new IllegalStateException(e);

View File

@@ -8,15 +8,17 @@ import java.nio.file.Files;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.release.internal.ReleaserProperties;
import org.springframework.cloud.release.internal.ReleaserPropertiesAware;
import org.springframework.cloud.release.internal.pom.ProjectVersion;
import org.springframework.cloud.release.internal.pom.Projects;
import org.springframework.util.StringUtils;
/**
* Contains business logic around Git & Github operations
*
* @author Marcin Grzejszczak
*/
public class ProjectGitHandler {
public class ProjectGitHandler implements ReleaserPropertiesAware {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@@ -25,7 +27,7 @@ public class ProjectGitHandler {
private static final String POST_RELEASE_MSG = "Going back to snapshots";
private static final String POST_RELEASE_BUMP_MSG = "Bumping versions to %s after release";
private final ReleaserProperties properties;
private ReleaserProperties properties;
private final GithubMilestones githubMilestones;
private final GithubIssues githubIssues;
@@ -39,13 +41,13 @@ public class ProjectGitHandler {
GitRepo gitRepo = gitRepo(project);
if (version.isSnapshot()) {
log.info("Snapshot version [{}] found. Will only commit the changed poms", version);
gitRepo.commit(project, MSG);
gitRepo.commit(MSG);
} else {
log.info("NON-snapshot version [{}] found. Will commit the changed poms, tag the version and push the tag", version);
gitRepo.commit(project, String.format(PRE_RELEASE_MSG, version.version));
gitRepo.commit(String.format(PRE_RELEASE_MSG, version.version));
String tagName = "v" + version.version;
gitRepo.tag(project, tagName);
gitRepo.pushTag(project, tagName);
gitRepo.tag(tagName);
gitRepo.pushTag(tagName);
}
}
@@ -60,7 +62,7 @@ public class ProjectGitHandler {
public void commit(File project, String message) {
GitRepo gitRepo = gitRepo(project);
gitRepo.commit(project, message);
gitRepo.commit(message);
}
public File cloneScReleaseProject() {
@@ -73,7 +75,36 @@ public class ProjectGitHandler {
return clonedProject;
}
private File cloneProject(String url) {
/**
* For meta-release. Works with fixed versions only
* @param projectName - name of the project to clone
* @return location of the cloned project
*/
public File cloneProjectFromOrg(String projectName) {
String orgUrl = this.properties.getMetaRelease().getGitOrgUrl();
String fullUrl = orgUrl.endsWith("/") ? orgUrl + projectName : orgUrl + "/" +
projectName + suffixNonHttpRepo(orgUrl);
File clonedProject = cloneProject(fullUrl);
String version = this.properties.getFixedVersions().get(projectName);
if (StringUtils.isEmpty(version)) {
throw new IllegalStateException("You haven't provided a version for project [" + projectName + "]");
}
String branchFromVersion = branchFromVersion(version);
boolean branchExists = gitRepo(clonedProject).hasBranch(branchFromVersion);
if (!branchExists) {
log.info("Branch [{}] does not exist. Assuming that should work with master branch", branchFromVersion);
return clonedProject;
}
log.info("Branch [{}] exists. Will check it out", branchFromVersion);
checkout(clonedProject, branchFromVersion);
return clonedProject;
}
private String suffixNonHttpRepo(String orgUrl) {
return orgUrl.startsWith("http") || orgUrl.startsWith("git") ? "" : "/";
}
File cloneProject(String url) {
try {
File destinationDir = properties.getGit().getCloneDestinationDir() != null ?
new File(properties.getGit().getCloneDestinationDir()) :
@@ -85,7 +116,28 @@ public class ProjectGitHandler {
}
public void checkout(File project, String branch) {
gitRepo(project).checkout(project, branch);
gitRepo(project).checkout(branch);
}
// let's go with convention... If fixed version contains e.g.
// 2.3.4.RELEASE of Sleuth, we will first check if `2.0.x` branch
// exists. If not, then we will assume that `master` contains it
private String branchFromVersion(String version) {
// 2.3.4.RELEASE -> 2.3.4
// Camden.RELEASE -> Camden
String versionTillPatch = version.substring(0, version.lastIndexOf("."));
// 2.3.4 -> [2,3,4]
// Camden -> [Camden]
String[] splitVersion = versionTillPatch.split("\\.");
if (splitVersion.length == 3) {
// [2,3,4] -> 2.3.x
return splitVersion[0] + "." + splitVersion[1] + ".x";
} else if (splitVersion.length == 1) {
// [Camden] -> [Camden.x]
return splitVersion[0] + ".x";
}
throw new IllegalStateException("Wrong version [" + version + "]. Can't extract semver pieces of it");
}
public void revertChangesIfApplicable(File project, ProjectVersion version) {
@@ -94,11 +146,11 @@ public class ProjectGitHandler {
return;
}
log.info("Reverting last commit");
gitRepo(project).revert(project, POST_RELEASE_MSG);
gitRepo(project).revert(POST_RELEASE_MSG);
}
public void pushCurrentBranch(File project) {
gitRepo(project).pushCurrentBranch(project);
gitRepo(project).pushCurrentBranch();
}
public void closeMilestone(ProjectVersion releaseVersion) {
@@ -114,10 +166,14 @@ public class ProjectGitHandler {
}
public String currentBranch(File project) {
return gitRepo(project).currentBranch(project);
return gitRepo(project).currentBranch();
}
GitRepo gitRepo(File workingDir) {
return new GitRepo(workingDir, this.properties);
}
@Override public void setReleaserProperties(ReleaserProperties properties) {
this.properties = properties;
}
}

View File

@@ -17,17 +17,18 @@ import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.release.internal.ReleaserProperties;
import org.springframework.cloud.release.internal.ReleaserPropertiesAware;
import org.springframework.cloud.release.internal.pom.ProjectVersion;
import org.springframework.cloud.release.internal.pom.Projects;
/**
* @author Marcin Grzejszczak
*/
public class GradleUpdater {
public class GradleUpdater implements ReleaserPropertiesAware {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final ReleaserProperties properties;
private ReleaserProperties properties;
public GradleUpdater(ReleaserProperties properties) {
this.properties = properties;
@@ -59,6 +60,10 @@ public class GradleUpdater {
}
}
@Override public void setReleaserProperties(ReleaserProperties properties) {
this.properties = properties;
}
private class GradlePropertiesWalker extends SimpleFileVisitor<Path> {
private static final String GRADLE_PROPERTIES = "gradle.properties";

View File

@@ -29,12 +29,13 @@ import java.util.Scanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.release.internal.ReleaserProperties;
import org.springframework.cloud.release.internal.ReleaserPropertiesAware;
import org.springframework.cloud.release.internal.git.ProjectGitHandler;
/**
* @author Marcin Grzejszczak
*/
public class ProjectPomUpdater {
public class ProjectPomUpdater implements ReleaserPropertiesAware {
private static final List<String> IGNORED_SNAPSHOT_LINE_PATTERNS = Arrays.asList(
"^.*replace=.*$",
@@ -44,7 +45,7 @@ public class ProjectPomUpdater {
private static final Logger log = LoggerFactory.getLogger(ProjectPomUpdater.class);
private final ReleaserProperties properties;
private ReleaserProperties properties;
private final ProjectGitHandler gitRepo;
private final PomUpdater pomUpdater = new PomUpdater();
@@ -103,6 +104,10 @@ public class ProjectPomUpdater {
}
}
@Override public void setReleaserProperties(ReleaserProperties properties) {
this.properties = properties;
}
private class PomWalker extends SimpleFileVisitor<Path> {
private static final String POM_XML = "pom.xml";

View File

@@ -66,6 +66,11 @@ public class Projects extends HashSet<ProjectVersion> {
.orElseThrow(() -> exception(projectName));
}
public boolean containsProject(String projectName) {
return this.stream()
.anyMatch(projectVersion -> projectVersion.projectName.equals(projectName));
}
public List<ProjectVersion> forNameStartingWith(String projectName) {
return this.stream().filter(projectVersion -> projectVersion.projectName.startsWith(projectName))
.collect(Collectors.toList());

View File

@@ -45,7 +45,7 @@ class SCReleasePomParser {
private final File springCloudReleaseDir;
private final String bootPom;
private final String dependenciesPom;
private final String dependenciesPomPath;
private final PomReader pomReader = new PomReader();
SCReleasePomParser(File springCloudReleaseDir) {
@@ -55,7 +55,7 @@ class SCReleasePomParser {
SCReleasePomParser(File springCloudReleaseDir, String bootPom, String dependenciesPom) {
this.springCloudReleaseDir = springCloudReleaseDir;
this.bootPom = bootPom;
this.dependenciesPom = dependenciesPom;
this.dependenciesPomPath = dependenciesPom;
}
Versions allVersions() {
@@ -96,7 +96,7 @@ class SCReleasePomParser {
}
Versions springCloudVersions() {
Model model = pom(this.dependenciesPom);
Model model = pom(this.dependenciesPomPath);
String buildArtifact = model.getParent().getArtifactId();
log.debug("[{}] artifact id is equal to [{}]", SpringCloudConstants.CLOUD_DEPENDENCIES_ARTIFACT_ID, buildArtifact);
if (!SpringCloudConstants.CLOUD_DEPENDENCIES_ARTIFACT_ID.equals(buildArtifact)) {
@@ -109,6 +109,8 @@ class SCReleasePomParser {
.filter(propertyMatchesSCPattern())
.map(toProject())
.collect(Collectors.toSet());
String scReleaseVersion = model.getVersion();
projects.add(new Project("spring-cloud-release", scReleaseVersion));
return new Versions(buildVersion, projects);
}

View File

@@ -18,6 +18,7 @@ import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.release.internal.ReleaserPropertiesAware;
import org.springframework.cloud.release.internal.ReleaserProperties;
import org.springframework.cloud.release.internal.pom.ProjectVersion;
import org.springframework.util.StringUtils;
@@ -25,12 +26,12 @@ import org.springframework.util.StringUtils;
/**
* @author Marcin Grzejszczak
*/
public class ProjectBuilder {
public class ProjectBuilder implements ReleaserPropertiesAware {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final String VERSION_MUSTACHE = "{{version}}";
private final ReleaserProperties properties;
private ReleaserProperties properties;
private final ProcessExecutor executor;
public ProjectBuilder(ReleaserProperties properties) {
@@ -161,12 +162,17 @@ public class ProjectBuilder {
}
return commandsList.toArray(new String[commandsList.size()]);
}
@Override public void setReleaserProperties(ReleaserProperties properties) {
this.properties = properties;
this.executor.setReleaserProperties(properties);
}
}
class ProcessExecutor {
class ProcessExecutor implements ReleaserPropertiesAware {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final ReleaserProperties properties;
private ReleaserProperties properties;
ProcessExecutor(ReleaserProperties properties) {
this.properties = properties;
@@ -175,7 +181,8 @@ class ProcessExecutor {
void runCommand(String[] commands, long waitTimeInMinutes) {
try {
String workingDir = this.properties.getWorkingDir();
log.debug("Will run the build via {} and wait for result for [{}] minutes", commands, waitTimeInMinutes);
log.debug("Will run the build from [{}] via {} and wait for result for [{}] minutes",
workingDir, commands, waitTimeInMinutes);
ProcessBuilder builder = builder(commands, workingDir);
Process process = startProcess(builder);
boolean finished = process.waitFor(waitTimeInMinutes, TimeUnit.MINUTES);
@@ -202,6 +209,10 @@ class ProcessExecutor {
.directory(new File(workingDir))
.inheritIO();
}
@Override public void setReleaserProperties(ReleaserProperties properties) {
this.properties = properties;
}
}
class HtmlFileWalker extends SimpleFileVisitor<Path> {

View File

@@ -9,13 +9,14 @@ import java.io.File;
import java.io.IOException;
import org.springframework.cloud.release.internal.ReleaserProperties;
import org.springframework.cloud.release.internal.ReleaserPropertiesAware;
import org.springframework.cloud.release.internal.git.ProjectGitHandler;
import org.springframework.cloud.release.internal.pom.Projects;
/**
* @author Marcin Grzejszczak
*/
public class TemplateGenerator {
public class TemplateGenerator implements ReleaserPropertiesAware {
private static final String EMAIL_TEMPLATE = "email";
private static final String BLOG_TEMPLATE = "blog";
@@ -25,7 +26,7 @@ public class TemplateGenerator {
private final File blogOutput;
private final File tweetOutput;
private final File releaseNotesOutput;
private final ReleaserProperties props;
private ReleaserProperties props;
private final ProjectGitHandler handler;
public TemplateGenerator(ReleaserProperties props, ProjectGitHandler handler) {
@@ -109,4 +110,8 @@ public class TemplateGenerator {
throw new IllegalStateException(e);
}
}
@Override public void setReleaserProperties(ReleaserProperties properties) {
this.props = properties;
}
}

View File

@@ -70,7 +70,7 @@ public class GitRepoTests {
@Test
public void should_check_out_a_branch_on_cloned_repo() throws IOException {
File project = this.gitRepo.cloneProject(this.springCloudReleaseProject.toURI());
this.gitRepo.checkout(project, "vCamden.SR3");
new GitRepo(project).checkout("vCamden.SR3");
File pom = new File(this.tmpFolder, "pom.xml");
then(pom).exists();
@@ -80,18 +80,32 @@ public class GitRepoTests {
@Test
public void should_check_out_a_branch_on_cloned_repo2() throws IOException {
File project = this.gitRepo.cloneProject(this.springCloudReleaseProject.toURI());
this.gitRepo.checkout(project, "Camden.x");
new GitRepo(project).checkout("Camden.x");
File pom = new File(this.tmpFolder, "pom.xml");
then(pom).exists();
then(Files.lines(pom.toPath()).anyMatch(s -> s.contains("<version>Camden.BUILD-SNAPSHOT</version>"))).isTrue();
}
@Test
public void should_return_true_if_branch_exists() throws IOException {
File project = new GitRepo(this.tmpFolder).cloneProject(this.springCloudReleaseProject.toURI());
then(new GitRepo(project).hasBranch("Camden.x")).isTrue();
}
@Test
public void should_return_false_if_branch_does_not_exist() throws IOException {
File project = new GitRepo(this.tmpFolder).cloneProject(this.springCloudReleaseProject.toURI());
then(new GitRepo(project).hasBranch("aksjdhkasjkajshd")).isFalse();
}
@Test
public void should_throw_an_exception_when_checking_out_nonexisting_branch() throws IOException {
File project = this.gitRepo.cloneProject(this.springCloudReleaseProject.toURI());
File project = new GitRepo(this.tmpFolder).cloneProject(this.springCloudReleaseProject.toURI());
try {
this.gitRepo.checkout(project, "nonExistingBranch");
new GitRepo(project).checkout("nonExistingBranch");
fail("should throw an exception");
} catch (IllegalStateException e) {
then(e).hasMessageContaining("Ref nonExistingBranch can not be resolved");
@@ -100,10 +114,10 @@ public class GitRepoTests {
@Test
public void should_commit_changes() throws Exception {
File project = this.gitRepo.cloneProject(this.springCloudReleaseProject.toURI());
File project = new GitRepo(this.tmpFolder).cloneProject(this.springCloudReleaseProject.toURI());
createNewFile(project);
this.gitRepo.commit(project, "some message");
new GitRepo(project).commit("some message");
try(Git git = openGitProject(project)) {
RevCommit revCommit = git.log().call().iterator().next();
@@ -113,11 +127,11 @@ public class GitRepoTests {
@Test
public void should_not_commit_empty_changes() throws Exception {
File project = this.gitRepo.cloneProject(this.springCloudReleaseProject.toURI());
File project = new GitRepo(this.tmpFolder).cloneProject(this.springCloudReleaseProject.toURI());
createNewFile(project);
this.gitRepo.commit(project, "some message");
new GitRepo(project).commit("some message");
this.gitRepo.commit(project, "empty commit");
new GitRepo(project).commit("empty commit");
try(Git git = openGitProject(project)) {
RevCommit revCommit = git.log().call().iterator().next();
@@ -127,11 +141,11 @@ public class GitRepoTests {
@Test
public void should_create_a_tag() throws Exception {
File project = this.gitRepo.cloneProject(this.springCloudReleaseProject.toURI());
File project = new GitRepo(this.tmpFolder).cloneProject(this.springCloudReleaseProject.toURI());
createNewFile(project);
this.gitRepo.commit(project, "some message");
new GitRepo(project).commit("some message");
this.gitRepo.tag(project, "v1.0.0");
new GitRepo(project).tag("v1.0.0");
try(Git git = openGitProject(project)) {
tagIsPresent(git, "v1.0.0");
@@ -147,12 +161,12 @@ public class GitRepoTests {
@Test
public void should_push_changes_to_master_branch() throws Exception {
File origin = clonedProject(this.tmp.newFolder(), this.springCloudReleaseProject);
File project = this.gitRepo.cloneProject(this.springCloudReleaseProject.toURI());
File project = new GitRepo(this.tmpFolder).cloneProject(this.springCloudReleaseProject.toURI());
setOriginOnProjectToTmp(origin, project);
createNewFile(project);
this.gitRepo.commit(project, "some message");
new GitRepo(project).commit("some message");
this.gitRepo.pushBranch(project, "master");
new GitRepo(project).pushBranch("master");
try(Git git = openGitProject(origin)) {
RevCommit revCommit = git.log().call().iterator().next();
@@ -163,12 +177,12 @@ public class GitRepoTests {
@Test
public void should_push_changes_to_current_branch() throws Exception {
File origin = clonedProject(this.tmp.newFolder(), this.springCloudReleaseProject);
File project = this.gitRepo.cloneProject(this.springCloudReleaseProject.toURI());
File project = new GitRepo(this.tmpFolder).cloneProject(this.springCloudReleaseProject.toURI());
setOriginOnProjectToTmp(origin, project);
createNewFile(project);
this.gitRepo.commit(project, "some message");
new GitRepo(project).commit("some message");
this.gitRepo.pushCurrentBranch(project);
new GitRepo(project).pushCurrentBranch();
try(Git git = openGitProject(origin)) {
RevCommit revCommit = git.log().call().iterator().next();
@@ -179,11 +193,11 @@ public class GitRepoTests {
@Test
public void should_return_the_branch_name() throws Exception {
File origin = clonedProject(this.tmp.newFolder(), this.springCloudReleaseProject);
File project = this.gitRepo.cloneProject(this.springCloudReleaseProject.toURI());
File project = new GitRepo(this.tmpFolder).cloneProject(this.springCloudReleaseProject.toURI());
setOriginOnProjectToTmp(origin, project);
createNewFile(project);
String branch = this.gitRepo.currentBranch(project);
String branch = new GitRepo(project).currentBranch();
then(branch).isEqualTo("master");
}
@@ -191,13 +205,13 @@ public class GitRepoTests {
@Test
public void should_push_a_tag_to_new_branch_in_origin() throws Exception {
File origin = clonedProject(this.tmp.newFolder(), this.springCloudReleaseProject);
File project = this.gitRepo.cloneProject(this.springCloudReleaseProject.toURI());
File project = new GitRepo(this.tmpFolder).cloneProject(this.springCloudReleaseProject.toURI());
setOriginOnProjectToTmp(origin, project);
createNewFile(project);
this.gitRepo.commit(project, "some message");
this.gitRepo.tag(project, "v5.6.7.RELEASE");
new GitRepo(project).commit("some message");
new GitRepo(project).tag("v5.6.7.RELEASE");
this.gitRepo.pushTag(project, "v5.6.7.RELEASE");
new GitRepo(project).pushTag("v5.6.7.RELEASE");
try(Git git = openGitProject(origin)) {
tagIsPresent(git, "v5.6.7");
@@ -225,12 +239,12 @@ public class GitRepoTests {
@Test
public void should_revert_changes() throws Exception {
File project = this.gitRepo.cloneProject(this.springCloudReleaseProject.toURI());
File project = new GitRepo(this.tmpFolder).cloneProject(this.springCloudReleaseProject.toURI());
File foo = new File(project, "foo");
foo.createNewFile();
this.gitRepo.commit(project, "Update SNAPSHOT to 1.0.0.RC1");
new GitRepo(project).commit("Update SNAPSHOT to 1.0.0.RC1");
this.gitRepo.revert(project, "Reverting the commit");
new GitRepo(project).revert("Reverting the commit");
try(Git git = openGitProject(project)) {
RevCommit revCommit = git.log().call().iterator().next();
@@ -240,10 +254,10 @@ public class GitRepoTests {
@Test
public void should_not_revert_changes_when_commit_message_is_not_related_to_updating_snapshots() throws Exception {
File project = this.gitRepo.cloneProject(this.springCloudReleaseProject.toURI());
File project = new GitRepo(tmpFolder).cloneProject(this.springCloudReleaseProject.toURI());
BDDAssertions.thenThrownBy(
() -> this.gitRepo.revert(project, "some message"))
() -> new GitRepo(project).revert("some message"))
.hasMessageContaining("Won't revert the commit with id");
}

View File

@@ -2,13 +2,16 @@ package org.springframework.cloud.release.internal.git;
import java.io.File;
import org.assertj.core.api.BDDAssertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.cloud.release.internal.ReleaserProperties;
import org.springframework.cloud.release.internal.pom.ProjectVersion;
import org.springframework.cloud.release.internal.sagan.Release;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
@@ -22,10 +25,15 @@ import static org.mockito.Mockito.never;
public class ProjectGitHandlerTests {
@Mock GitRepo gitRepo;
ProjectGitHandler updater = new ProjectGitHandler(new ReleaserProperties()) {
ReleaserProperties properties = new ReleaserProperties();
ProjectGitHandler updater = new ProjectGitHandler(this.properties) {
@Override GitRepo gitRepo(File workingDir) {
return ProjectGitHandlerTests.this.gitRepo;
}
@Override File cloneProject(String url) {
return new File(".");
}
};
File file = new File("");
@@ -33,54 +41,95 @@ public class ProjectGitHandlerTests {
public void should_only_commit_without_pushing_changes_when_version_is_snapshot() {
this.updater.commitAndTagIfApplicable(this.file, projectVersion("1.0.0.BUILD-SNAPSHOT"));
then(this.gitRepo).should().commit(any(File.class), eq("Bumping versions"));
then(this.gitRepo).should(never()).tag(any(File.class), anyString());
then(this.gitRepo).should().commit(eq("Bumping versions"));
then(this.gitRepo).should(never()).tag(anyString());
}
@Test
public void should_commit_tag_and_push_tag_when_version_is_not_snapshot() {
this.updater.commitAndTagIfApplicable(this.file, projectVersion("1.0.0.RELEASE"));
then(this.gitRepo).should().commit(any(File.class), eq("Update SNAPSHOT to 1.0.0.RELEASE"));
then(this.gitRepo).should().tag(any(File.class), eq("v1.0.0.RELEASE"));
then(this.gitRepo).should().pushTag(any(File.class), eq("v1.0.0.RELEASE"));
then(this.gitRepo).should().commit(eq("Update SNAPSHOT to 1.0.0.RELEASE"));
then(this.gitRepo).should().tag(eq("v1.0.0.RELEASE"));
then(this.gitRepo).should().pushTag(eq("v1.0.0.RELEASE"));
}
@Test
public void should_commit_when_snapshot_version_is_present_with_post_release_msg() {
this.updater.commitAfterBumpingVersions(this.file, projectVersion("1.0.0.BUILD-SNAPSHOT"));
then(this.gitRepo).should().commit(any(File.class), eq("Bumping versions to 1.0.1.BUILD-SNAPSHOT after release"));
then(this.gitRepo).should(never()).tag(any(File.class), anyString());
then(this.gitRepo).should().commit(eq("Bumping versions to 1.0.1.BUILD-SNAPSHOT after release"));
then(this.gitRepo).should(never()).tag(anyString());
}
@Test
public void should_not_commit_when_non_snapshot_version_is_present() {
this.updater.commitAfterBumpingVersions(this.file, projectVersion("1.0.0.RELEASE"));
then(this.gitRepo).should(never()).commit(any(File.class), eq("Bumping versions after release"));
then(this.gitRepo).should(never()).tag(any(File.class), anyString());
then(this.gitRepo).should(never()).commit(eq("Bumping versions after release"));
then(this.gitRepo).should(never()).tag(anyString());
}
@Test
public void should_not_revert_changes_for_snapshots() {
this.updater.revertChangesIfApplicable(this.file, projectVersion("1.0.0.BUILD-SNAPSHOT"));
then(this.gitRepo).should(never()).revert(any(File.class), anyString());
then(this.gitRepo).should(never()).revert(anyString());
}
@Test
public void should_revert_changes_when_version_is_not_snapshot() {
this.updater.revertChangesIfApplicable(this.file, projectVersion("1.0.0.RELEASE"));
then(this.gitRepo).should().revert(any(File.class), eq("Going back to snapshots"));
then(this.gitRepo).should().revert(eq("Going back to snapshots"));
}
@Test
public void should_push_current_branch() {
this.updater.pushCurrentBranch(this.file);
then(this.gitRepo).should().pushCurrentBranch(any(File.class));
then(this.gitRepo).should().pushCurrentBranch();
}
@Test
public void should_throw_exception_when_no_fixed_version_passed_for_the_project() {
BDDAssertions.thenThrownBy(() -> this.updater
.cloneProjectFromOrg("spring-cloud-sleuth"))
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("You haven't provided a version");
}
@Test
public void should_not_check_out_a_branch_if_it_does_not_exist_when_cloning_from_org() {
this.properties.getFixedVersions().put("spring-cloud-sleuth", "2.3.4.RELEASE");
given(this.gitRepo.hasBranch(anyString())).willReturn(true);
given(this.gitRepo.hasBranch("2.3.x")).willReturn(false);
this.updater.cloneProjectFromOrg("spring-cloud-sleuth");
then(this.gitRepo).should(never()).checkout(anyString());
}
@Test
public void should_check_out_a_branch_if_it_exists_when_cloning_from_org() {
this.properties.getFixedVersions().put("spring-cloud-sleuth", "2.3.4.RELEASE");
given(this.gitRepo.hasBranch(anyString())).willReturn(false);
given(this.gitRepo.hasBranch("2.3.x")).willReturn(true);
this.updater.cloneProjectFromOrg("spring-cloud-sleuth");
then(this.gitRepo).should().checkout("2.3.x");
}
@Test
public void should_check_out_a_branch_if_it_exists_when_cloning_from_org_and_its_a_release_train_version() {
this.properties.getFixedVersions().put("spring-cloud-release", "Finchley.SR6");
given(this.gitRepo.hasBranch(anyString())).willReturn(false);
given(this.gitRepo.hasBranch("Finchley.x")).willReturn(true);
this.updater.cloneProjectFromOrg("spring-cloud-release");
then(this.gitRepo).should().checkout("Finchley.x");
}
private ProjectVersion projectVersion(String version) {

View File

@@ -35,6 +35,15 @@ public class ProjectsTests {
then(projects.forName("spring-cloud-starter-build").version).isEqualTo("1.0.0");
}
@Test
public void should_return_true_when_a_project_by_name_exists() {
Set<ProjectVersion> projectVersions = new HashSet<>();
projectVersions.add(new ProjectVersion("spring-cloud-starter-build", "1.0.0"));
Projects projects = new Projects(projectVersions);
then(projects.containsProject("spring-cloud-starter-build")).isTrue();
}
@Test
public void should_find_projects_starting_with_name() {
Set<ProjectVersion> projectVersions = new HashSet<>();

View File

@@ -2,14 +2,12 @@ package org.springframework.cloud.release.internal.pom;
import org.apache.maven.plugin.logging.Log;
import org.codehaus.mojo.versions.rewriting.ModifiedPomXMLEventReader;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.BDDMockito;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.BDDMockito.then;
@@ -30,15 +28,8 @@ public class PropertyStorerTests {
}
private String containsWarnMsgAboutEmptyVersion() {
return BDDMockito.argThat(new TypeSafeMatcher<String>() {
@Override protected boolean matchesSafely(String item) {
return item.contains("is empty. Will not set it");
}
@Override public void describeTo(Description description) {
}
});
return BDDMockito.argThat(
argument -> argument.contains("is empty. Will not set it"));
}
}

View File

@@ -50,6 +50,15 @@ public class SCReleasePomParserTests {
.hasMessageContaining("The pom doesn't have a [spring-boot-starter-parent] artifact id");
}
@Test
public void should_populate_sc_release_version() {
SCReleasePomParser parser = new SCReleasePomParser(this.springCloudReleaseProject);
String scReleaseVersion = parser.allVersions().versionForProject("spring-cloud-release");
then(scReleaseVersion).isEqualTo("Dalston.BUILD-SNAPSHOT");
}
@Test
public void should_populate_boot_version() {
SCReleasePomParser parser = new SCReleasePomParser(this.springCloudReleaseProject);

View File

@@ -8,10 +8,11 @@ import org.hamcrest.TypeSafeMatcher;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
import org.mockito.BDDMockito;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.cloud.release.internal.pom.ProjectVersion;
import static org.mockito.BDDMockito.then;
@@ -103,23 +104,17 @@ public class SaganUpdaterTest {
"http://cloud.spring.io/foo/1.1.x/", "SNAPSHOT")));
}
private TypeSafeMatcher<List<ReleaseUpdate>> withReleaseUpdate(final String version,
private ArgumentMatcher<List<ReleaseUpdate>> withReleaseUpdate(final String version,
final String refDocUrl, final String releaseStatus) {
return new TypeSafeMatcher<List<ReleaseUpdate>>() {
@Override protected boolean matchesSafely(List<ReleaseUpdate> items) {
ReleaseUpdate item = items.get(0);
return argument -> {
ReleaseUpdate item = argument.get(0);
return "foo".equals(item.artifactId) &&
releaseStatus.equals(item.releaseStatus) &&
version.equals(item.version) &&
refDocUrl.equals(item.apiDocUrl) &&
refDocUrl.equals(item.refDocUrl) &&
item.current;
}
@Override public void describeTo(Description description) {
}
};
};
}
}

View File

@@ -36,6 +36,10 @@
<version>5.0.3</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.release.internal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.release.internal.options.Options;
import org.springframework.cloud.release.internal.options.Parser;
@@ -28,7 +29,7 @@ public class ReleaserApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(ReleaserApplication.class);
application.setWebEnvironment(false);
application.setWebApplicationType(WebApplicationType.NONE);
application.run(args);
}

View File

@@ -7,14 +7,17 @@ import java.util.List;
* @author Marcin Grzejszczak
*/
public class Options {
public Boolean fullRelease = true;
public Boolean interactive = true;
public List<String> taskNames = new ArrayList<>();
public Boolean metaRelease;
public Boolean fullRelease;
public Boolean interactive;
public List<String> taskNames;
public String startFrom = "";
public String range = "";
Options(Boolean fullRelease, Boolean interactive, List<String> taskNames, String startFrom,
Options(Boolean metaRelease, Boolean fullRelease,
Boolean interactive, List<String> taskNames, String startFrom,
String range) {
this.metaRelease = metaRelease;
this.fullRelease = fullRelease;
this.interactive = interactive;
this.taskNames = taskNames;

View File

@@ -4,12 +4,18 @@ import java.util.ArrayList;
import java.util.List;
public class OptionsBuilder {
private Boolean metaRelease = false;
private Boolean fullRelease = false;
private Boolean interactive = true;
private List<String> taskNames = new ArrayList<>();
private String startFrom = "";
private String range = "";
public OptionsBuilder metaRelease(Boolean metaRelease) {
this.metaRelease = metaRelease;
return this;
}
public OptionsBuilder fullRelease(Boolean fullRelease) {
this.fullRelease = fullRelease;
return this;
@@ -36,7 +42,8 @@ public class OptionsBuilder {
}
public Options options() {
return new Options(this.fullRelease, this.interactive, this.taskNames, this.startFrom,
return new Options(this.metaRelease, this.fullRelease,
this.interactive, this.taskNames, this.startFrom,
this.range);
}
}

View File

@@ -2,6 +2,7 @@ package org.springframework.cloud.release.internal.spring;
import java.io.File;
import org.apache.commons.validator.Arg;
import org.springframework.cloud.release.internal.Releaser;
import org.springframework.cloud.release.internal.ReleaserProperties;
import org.springframework.cloud.release.internal.pom.ProjectVersion;
@@ -18,10 +19,11 @@ class Args {
final ProjectVersion versionFromScRelease;
final ReleaserProperties properties;
final boolean interactive;
final TaskType taskType;
Args(Releaser releaser, File project, Projects projects, ProjectVersion originalVersion,
ProjectVersion versionFromScRelease, ReleaserProperties properties,
boolean interactive) {
boolean interactive, TaskType taskType) {
this.releaser = releaser;
this.project = project;
this.projects = projects;
@@ -29,5 +31,33 @@ class Args {
this.versionFromScRelease = versionFromScRelease;
this.properties = properties;
this.interactive = interactive;
this.taskType = taskType;
}
// Used by meta-release task
Args(Releaser releaser, Projects projects,
ProjectVersion versionFromScRelease,
ReleaserProperties properties,
boolean interactive) {
this.releaser = releaser;
this.project = null;
this.projects = projects;
this.originalVersion = null;
this.versionFromScRelease = versionFromScRelease;
this.properties = properties;
this.interactive = interactive;
this.taskType = TaskType.POST_RELEASE;
}
// Used for tests
Args(TaskType taskType) {
this.releaser = null;
this.project = null;
this.projects = null;
this.originalVersion = null;
this.versionFromScRelease = null;
this.properties = null;
this.interactive = false;
this.taskType = taskType;
}
}

View File

@@ -23,15 +23,19 @@ class OptionsParser implements Parser {
OptionParser parser = new OptionParser();
parser.allowsUnrecognizedOptions();
try {
ArgumentAcceptingOptionSpec<Boolean> metaReleaseOpt = parser
.acceptsAll(Arrays.asList("x", "meta-release"),
"Do you want to do the meta release?")
.withRequiredArg().ofType(Boolean.class).defaultsTo(false);
ArgumentAcceptingOptionSpec<Boolean> fullReleaseOpt = parser
.acceptsAll(Arrays.asList("f", "full-release"),
"Do you want to do the full release")
"Do you want to do the full release of a single project?")
.withOptionalArg().ofType(Boolean.class).defaultsTo(false);
ArgumentAcceptingOptionSpec<Boolean> interactiveOpt = parser
.acceptsAll(Arrays.asList("i", "interactive"),
"Do you want to set the properties from the command line")
"Do you want to set the properties from the command line of a single project?")
.withRequiredArg().ofType(Boolean.class).defaultsTo(true);
Tasks.ALL_TASKS.forEach(task ->
Tasks.NON_COMPOSITE_TASKS.forEach(task ->
parser.acceptsAll(Arrays.asList(task.shortName, task.name),
task.description)
.withOptionalArg());
@@ -49,14 +53,16 @@ class OptionsParser implements Parser {
printHelpMessage(parser);
System.exit(0);
}
Boolean metaRelease = options.valueOf(metaReleaseOpt);
Boolean interactive = options.valueOf(interactiveOpt);
Boolean fullRelease = options.has(fullReleaseOpt);
List<String> taskNames = Tasks.ALL_TASKS.stream()
List<String> taskNames = Tasks.NON_COMPOSITE_TASKS.stream()
.filter(task -> options.has(task.name)).map(task -> task.name)
.collect(Collectors.toList());
String startFrom = options.valueOf(startFromOpt);
String range = options.valueOf(rangeOpt);
return new OptionsBuilder()
.metaRelease(metaRelease)
.fullRelease(fullRelease)
.interactive(interactive)
.taskNames(taskNames)
@@ -94,7 +100,7 @@ class OptionsParser implements Parser {
}
private String intro() {
return "\nHere you can find the list of tasks in order\n\n[" + Tasks.tasksInOrder() + "]\n\n";
return "\nHere you can find the list of tasks in order\n\n[" + Tasks.allTasksInOrder() + "]\n\n";
}
private String examples() {

View File

@@ -23,7 +23,7 @@ class OptionsProcessor {
private final List<Task> allTasks;
OptionsProcessor(Releaser releaser, ReleaserProperties properties) {
this(releaser, properties, Tasks.ALL_TASKS);
this(releaser, properties, Tasks.ALL_TASKS_PER_PROJECT);
}
OptionsProcessor(Releaser releaser, ReleaserProperties properties, List<Task> allTasks) {
@@ -33,26 +33,60 @@ class OptionsProcessor {
}
void processOptions(Options options, Args defaultArgs) {
processOptions(options, defaultArgs, this.allTasks);
}
void processOptions(Options options, Args defaultArgs, List<Task> tasks) {
Args args = args(defaultArgs, options.interactive);
if (args.taskType == TaskType.POST_RELEASE) {
String chosenOption = chosenOption();
int pickedInteger = Integer.parseInt(chosenOption);
boolean pickedOptionIsComposite = pickedInteger <= 1;
boolean pickedOptionIsFromPostRelease = pickedInteger >= Tasks.ALL_TASKS_PER_PROJECT.size()
- Tasks.DEFAULT_TASKS_PER_RELEASE.size();
if (options.fullRelease || pickedOptionIsComposite) {
postReleaseTask().execute(args);
} else if (pickedOptionIsFromPostRelease) {
processNonComposite(options, tasks, args);
} else {
log.info("Picked option [{}] doesn't allow post release steps", pickedInteger);
}
return;
}
if (options.fullRelease && !options.interactive) {
log.info("Executing a full release in non-interactive mode");
releaseTask().execute(args);
} else if (options.fullRelease && options.interactive) {
log.info("Executing a full release in interactive mode");
releaseVerboseTask().execute(args);
} else if (StringUtils.hasText(options.startFrom)) {
startFrom(options, args);
} else {
processNonComposite(options, tasks, args);
}
}
private void processNonComposite(Options options, List<Task> tasks, Args args) {
if (StringUtils.hasText(options.startFrom)) {
startFrom(tasks, options, args);
} else if (StringUtils.hasText(options.range)) {
range(options.range, args);
range(tasks, options.range, args);
} else if (!options.taskNames.isEmpty()) {
tasks(options.taskNames, args);
tasks(tasks, options.taskNames, args);
} else if (options.interactive) {
interactiveOnly(args);
interactiveOnly(tasks, args);
} else {
throw new IllegalStateException("You haven't picked any recognizable option");
}
}
void postReleaseOptions(Options options, Args defaultArgs) {
Args args = args(defaultArgs, options.interactive);
processOptions(options, args);
}
Task postReleaseTask() {
return Tasks.POST_RELEASE;
}
Task releaseTask() {
return Tasks.RELEASE;
}
@@ -61,16 +95,18 @@ class OptionsProcessor {
return Tasks.RELEASE_VERBOSE;
}
private void interactiveOnly(Args defaultArgs) {
log.info(buildOptionsText().toString());
executeTaskFromOption(defaultArgs);
private void interactiveOnly(List<Task> tasks, Args defaultArgs) {
if (defaultArgs.taskType != TaskType.POST_RELEASE) {
log.info(buildOptionsText().toString());
}
executeTaskFromOption(tasks, defaultArgs);
}
private void tasks(List<String> taskNames, Args defaultArgs) {
Tasks.forNames(this.allTasks, taskNames).forEach(task -> task.execute(defaultArgs));
private void tasks(List<Task> tasks, List<String> taskNames, Args defaultArgs) {
Tasks.forNames(tasks, taskNames).forEach(task -> task.execute(defaultArgs));
}
private void range(String range, Args defaultArgs) {
private void range(List<Task> tasks, String range, Args defaultArgs) {
String[] splitRange = range.split("-");
String start = splitRange[0];
String stop = "";
@@ -79,7 +115,7 @@ class OptionsProcessor {
}
boolean started = false;
boolean sameRange = start.equals(stop);
for (Task task : this.allTasks) {
for (Task task : tasks) {
if (start.equals(task.name) || start.equals(task.shortName)) {
started = true;
task.execute(defaultArgs);
@@ -95,9 +131,9 @@ class OptionsProcessor {
}
}
private void startFrom(Options options, Args defaultArgs) {
private void startFrom(List<Task> tasks, Options options, Args defaultArgs) {
boolean started = false;
for (Task task : this.allTasks) {
for (Task task : tasks) {
if (options.startFrom.equals(task.name) || options.startFrom.equals(task.shortName)) {
started = true;
task.execute(defaultArgs);
@@ -120,25 +156,25 @@ class OptionsProcessor {
return msg;
}
void executeTaskFromOption(Args defaultArgs) {
void executeTaskFromOption(List<Task> tasks, Args defaultArgs) {
String input = chosenOption();
switch (input.toLowerCase()) {
case "q":
System.exit(0);
default:
if (input.contains("-")) {
rangeInteractive(defaultArgs, input);
rangeInteractive(tasks, defaultArgs, input);
} else if (input.contains(",")) {
tasksInteractive(defaultArgs, input);
tasksInteractive(tasks, defaultArgs, input);
} else {
singleTask(defaultArgs, input);
singleTask(tasks, defaultArgs, input);
}
}
}
private void singleTask(Args defaultArgs, String input) {
private void singleTask(List<Task> tasks, Args defaultArgs, String input) {
int chosenOption = Integer.parseInt(input);
Task task = this.allTasks.get(chosenOption);
Task task = tasks.get(chosenOption);
boolean interactive = false;
if (task == Tasks.RELEASE_VERBOSE) {
interactive = true;
@@ -147,31 +183,32 @@ class OptionsProcessor {
task.execute(args(defaultArgs, interactive));
}
private void tasksInteractive(Args defaultArgs, String input) {
List<String> tasks = Arrays.asList(input.split(","));
private void tasksInteractive(List<Task> tasks, Args defaultArgs, String input) {
List<String> tasksFromInput = Arrays.asList(input.split(","));
List<String> taskNames = new ArrayList<>();
for (String task : tasks) {
for (String task : tasksFromInput) {
Integer taskIndex = Integer.valueOf(task);
taskNames.add(this.allTasks.get(taskIndex).name);
taskNames.add(tasks.get(taskIndex).name);
}
tasks(taskNames, defaultArgs);
tasks(tasks, taskNames, defaultArgs);
}
private void rangeInteractive(Args defaultArgs, String input) {
private void rangeInteractive(List<Task> tasks, Args defaultArgs, String input) {
String[] range = input.split("-");
Integer start = Integer.valueOf(range[0]);
Integer stop = null;
if (range.length == 2) {
stop = Integer.valueOf(range[1]);
}
String firstName = this.allTasks.get(start).name;
String second = stop != null ? this.allTasks.get(stop).name : "";
range(firstName + "-" + second, defaultArgs);
String firstName = tasks.get(start).name;
String second = stop != null ? tasks.get(stop).name : "";
range(tasks, firstName + "-" + second, defaultArgs);
}
private Args args(Args defaultArgs, boolean interactive) {
return new Args(this.releaser, defaultArgs.project, defaultArgs.projects,
defaultArgs.originalVersion, defaultArgs.versionFromScRelease, this.properties, interactive);
defaultArgs.originalVersion, defaultArgs.versionFromScRelease,
this.properties, interactive, defaultArgs.taskType);
}
String chosenOption() {

View File

@@ -15,18 +15,21 @@
*/
package org.springframework.cloud.release.internal.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.release.internal.Releaser;
import org.springframework.cloud.release.internal.ReleaserProperties;
import org.springframework.cloud.release.internal.docs.DocumentationUpdater;
import org.springframework.cloud.release.internal.gradle.GradleUpdater;
import org.springframework.cloud.release.internal.options.Parser;
import org.springframework.cloud.release.internal.sagan.Release;
import org.springframework.cloud.release.internal.sagan.SaganClient;
import org.springframework.cloud.release.internal.sagan.SaganUpdater;
import org.springframework.cloud.release.internal.template.TemplateGenerator;
import org.springframework.cloud.release.internal.project.ProjectBuilder;
import org.springframework.cloud.release.internal.git.ProjectGitHandler;
import org.springframework.cloud.release.internal.pom.ProjectPomUpdater;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -34,14 +37,37 @@ import org.springframework.context.annotation.Configuration;
@EnableConfigurationProperties(ReleaserProperties.class)
class ReleaserConfiguration {
@Bean SpringReleaser releaser(ReleaserProperties properties, SaganClient saganClient) {
ProjectPomUpdater pomUpdater = new ProjectPomUpdater(properties);
ProjectGitHandler handler = new ProjectGitHandler(properties);
SaganUpdater saganUpdater = new SaganUpdater(saganClient);
return new SpringReleaser(new Releaser(pomUpdater, new ProjectBuilder(properties),
handler, new TemplateGenerator(properties, handler),
new GradleUpdater(properties), saganUpdater,
new DocumentationUpdater(handler)), properties);
@Autowired ReleaserProperties properties;
@Bean SpringReleaser springReleaser(Releaser releaser,
ReleaserPropertiesUpdater updater) {
return new SpringReleaser(releaser, this.properties, updater);
}
@Bean ProjectBuilder projectBuilder() { return new ProjectBuilder(this.properties); }
@Bean ProjectPomUpdater pomUpdater() { return new ProjectPomUpdater(this.properties); }
@Bean ProjectGitHandler projectGitHandler() { return new ProjectGitHandler(this.properties); }
@Bean TemplateGenerator templateGenerator(ProjectGitHandler handler) { return new TemplateGenerator(this.properties, handler); }
@Bean GradleUpdater gradleUpdater() { return new GradleUpdater(this.properties); }
@Bean SaganUpdater saganUpdater(SaganClient saganClient) { return new SaganUpdater(saganClient); }
@Bean DocumentationUpdater documentationUpdater(ProjectGitHandler handler) { return new DocumentationUpdater(handler); }
@Bean Releaser releaser(ProjectPomUpdater projectPomUpdater, ProjectBuilder projectBuilder,
ProjectGitHandler projectGitHandler, TemplateGenerator templateGenerator,
GradleUpdater gradleUpdater, SaganUpdater saganUpdater,
DocumentationUpdater documentationUpdater) {
return new Releaser(projectPomUpdater, projectBuilder, projectGitHandler,
templateGenerator, gradleUpdater, saganUpdater, documentationUpdater);
}
@Bean ReleaserPropertiesUpdater releaserPropertiesUpdater(ApplicationContext context) {
return new ReleaserPropertiesUpdater(context);
}
@Bean Parser optionsParser() {

View File

@@ -0,0 +1,24 @@
package org.springframework.cloud.release.internal.spring;
import java.util.Map;
import org.springframework.cloud.release.internal.ReleaserProperties;
import org.springframework.cloud.release.internal.ReleaserPropertiesAware;
import org.springframework.context.ApplicationContext;
/**
* @author Marcin Grzejszczak
*/
class ReleaserPropertiesUpdater {
private final ApplicationContext context;
public ReleaserPropertiesUpdater(ApplicationContext context) {
this.context = context;
}
public void updateProperties(ReleaserProperties properties) {
Map<String, ReleaserPropertiesAware> beans = this.context
.getBeansOfType(ReleaserPropertiesAware.class);
beans.values().forEach(aware -> aware.setReleaserProperties(properties));
}
}

View File

@@ -1,20 +1,23 @@
package org.springframework.cloud.release.internal.spring;
import java.io.File;
import java.lang.invoke.MethodHandles;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.BeanUtils;
import org.springframework.cloud.release.internal.Releaser;
import org.springframework.cloud.release.internal.ReleaserProperties;
import org.springframework.cloud.release.internal.options.Options;
import org.springframework.cloud.release.internal.options.OptionsBuilder;
import org.springframework.cloud.release.internal.pom.ProjectVersion;
import org.springframework.cloud.release.internal.pom.Projects;
import org.springframework.cloud.release.internal.sagan.Project;
import org.springframework.util.StringUtils;
/**
* Releaser that gets input from console
@@ -22,24 +25,28 @@ import org.springframework.cloud.release.internal.sagan.Project;
* @author Marcin Grzejszczak
*/
public class SpringReleaser {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final Logger log = LoggerFactory.getLogger(SpringReleaser.class);
private final Releaser releaser;
private final ReleaserProperties properties;
private final OptionsProcessor optionsProcessor;
private final ReleaserPropertiesUpdater updater;
private final ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
@Autowired
public SpringReleaser(Releaser releaser, ReleaserProperties properties) {
public SpringReleaser(Releaser releaser, ReleaserProperties properties,
ReleaserPropertiesUpdater updater) {
this.releaser = releaser;
this.properties = properties;
this.updater = updater;
this.optionsProcessor = new OptionsProcessor(releaser, properties);
}
SpringReleaser(Releaser releaser, ReleaserProperties properties,
OptionsProcessor optionsProcessor) {
OptionsProcessor optionsProcessor, ReleaserPropertiesUpdater updater) {
this.releaser = releaser;
this.properties = properties;
this.optionsProcessor = optionsProcessor;
this.updater = updater;
}
/**
@@ -50,17 +57,108 @@ public class SpringReleaser {
}
public void release(Options options) {
ProjectsAndVersion projectsAndVersion = null;
// if meta release, first clone, then continue as usual
if (options.metaRelease) {
log.info("Meta Release picked. Will iterate over all projects and perform release of each one");
this.properties.getGit().setFetchVersionsFromGit(false);
metaReleaseProjects(options).forEach(project -> {
processProjectForMetaRelease(options, project);
});
} else {
log.info("Single project release picked. Will release only the current project");
File projectFolder = projectFolder();
projectsAndVersion = processProject(options, projectFolder, TaskType.RELEASE);
}
this.optionsProcessor.postReleaseOptions(options, postReleaseOptionsAgs(options, projectsAndVersion));
}
private void processProjectForMetaRelease(Options options, String project) {
File clonedProjectFromOrg = this.releaser.clonedProjectFromOrg(project);
ReleaserProperties copy = clonePropertiesForProject();
updatePropertiesIfCustomConfigPresent(copy, clonedProjectFromOrg);
log.info("Successfully cloned the project [{}] to [{}]", project, clonedProjectFromOrg);
try {
processProject(options, clonedProjectFromOrg, TaskType.RELEASE);
} catch (Exception e) {
log.error("\n\n\nBUILD FAILED!!!\n\nException occurred for project <" +
project + "> \n\n");
throw e;
}
}
private ReleaserProperties clonePropertiesForProject() {
ReleaserProperties copy = new ReleaserProperties();
BeanUtils.copyProperties(this.properties, copy);
return copy;
}
private void updatePropertiesIfCustomConfigPresent(ReleaserProperties copy,
File clonedProjectFromOrg) {
File releaserConfig = new File(clonedProjectFromOrg, "config/releaser.yml");
if (releaserConfig.exists()) {
try {
ReleaserProperties releaserProperties = this.objectMapper
.readValue(releaserConfig, ReleaserProperties.class);
log.info("config/releaser.yml found. Will update the current properties");
copy.setMaven(releaserProperties.getMaven());
copy.setGradle(releaserProperties.getGradle());
}
catch (IOException e) {
throw new IllegalStateException(e);
}
}
log.info("Updating working directory to [{}]", clonedProjectFromOrg.getAbsolutePath());
copy.setWorkingDir(clonedProjectFromOrg.getAbsolutePath());
this.updater.updateProperties(copy);
}
private List<String> metaReleaseProjects(Options options) {
List<String> projects = new ArrayList<>(this.properties.getFixedVersions().keySet());
if (StringUtils.hasText(options.startFrom)) {
int projectIndex = projects.indexOf(options.startFrom);
if (projectIndex < 0) throw new IllegalStateException("Project [" + options.startFrom + "] not found");
projects = projects.subList(projectIndex, projects.size());
options.startFrom = "";
} else if (!options.taskNames.isEmpty()) {
projects = projects.stream()
.filter(project -> options.taskNames.contains(project))
.collect(Collectors.toList());
options.taskNames = new ArrayList<>();
}
log.info("\n\n\nFor meta-release, will release the projects {}\n\n\n", projects);
return projects;
}
private File projectFolder() {
String workingDir = this.properties.getWorkingDir();
File project = new File(workingDir);
ProjectVersion originalVersion = new ProjectVersion(project);
return new File(workingDir);
}
Args postReleaseOptionsAgs(Options options, ProjectsAndVersion projectsAndVersion) {
Projects projects = projectsAndVersion == null ?
projectsToUpdateForFixedVersions() : projectsAndVersion.projectVersions;
ProjectVersion version = projects.containsProject(this.properties.getMetaRelease().getReleaseTrainProjectName()) ?
projects.forName(this.properties.getMetaRelease().getReleaseTrainProjectName()) : versionFromBranch();
return new Args(this.releaser, projects, version,
this.properties, options.interactive);
}
private ProjectVersion versionFromBranch() {
String branch = this.properties.getPom().getBranch();
return new ProjectVersion(projectFolder().getName(), branch.startsWith("v") ? branch.substring(1) : branch);
}
private ProjectsAndVersion projects(File project) {
ProjectVersion versionFromScRelease;
Projects projectsToUpdate;
if (this.properties.getGit().isFetchVersionsFromGit()) {
if (this.properties.getGit().isFetchVersionsFromGit() && !this.properties.getMetaRelease().isEnabled()) {
printVersionRetrieval();
projectsToUpdate = this.releaser.retrieveVersionsFromSCRelease();
versionFromScRelease = projectsToUpdate.forFile(project);
assertNoSnapshotsForANonSnapshotProject(projectsToUpdate, versionFromScRelease);
} else {
ProjectVersion originalVersion = new ProjectVersion(project);
String fixedVersionForProject = this.properties.getFixedVersions().get(originalVersion.projectName);
versionFromScRelease = new ProjectVersion(originalVersion.projectName, fixedVersionForProject == null ?
originalVersion.version : fixedVersionForProject);
@@ -70,9 +168,35 @@ public class SpringReleaser {
projectsToUpdate.add(versionFromScRelease);
printSettingVersionFromFixedVersions(projectsToUpdate);
}
final Args defaultArgs = new Args(this.releaser, project, projectsToUpdate,
originalVersion, versionFromScRelease, this.properties, options.interactive);
return new ProjectsAndVersion(projectsToUpdate, versionFromScRelease);
}
class ProjectsAndVersion {
final Projects projectVersions;
final ProjectVersion versionFromScRelease;
ProjectsAndVersion(Projects projectVersions, ProjectVersion versionFromScRelease) {
this.projectVersions = projectVersions;
this.versionFromScRelease = versionFromScRelease;
}
}
private ProjectsAndVersion processProject(Options options, File project, TaskType taskType) {
ProjectsAndVersion projectsAndVersion = projects(project);
ProjectVersion originalVersion = new ProjectVersion(project);
final Args defaultArgs = new Args(this.releaser, project, projectsAndVersion.projectVersions,
originalVersion, projectsAndVersion.versionFromScRelease, this.properties,
options.interactive, taskType);
this.optionsProcessor.processOptions(options, defaultArgs);
return projectsAndVersion;
}
private Projects projectsToUpdateForFixedVersions() {
Projects projectsToUpdate = this.properties.getFixedVersions().entrySet().stream()
.map(entry -> new ProjectVersion(entry.getKey(), entry.getValue()))
.distinct().collect(Collectors.toCollection(Projects::new));
printSettingVersionFromFixedVersions(projectsToUpdate);
return projectsToUpdate;
}
private void printVersionRetrieval() {

View File

@@ -18,17 +18,30 @@ class Task {
final String shortName;
final String header;
final String description;
final TaskType taskType;
private final Consumer<Args> consumer;
Task(String name, String shortName, String header, String description, Consumer<Args> consumer) {
Task(String name, String shortName, String header, String description,
Consumer<Args> consumer) {
this(name, shortName, header, description, consumer, TaskType.RELEASE);
}
Task(String name, String shortName, String header, String description,
Consumer<Args> consumer, TaskType taskType) {
this.name = name;
this.shortName = shortName;
this.header = header;
this.description = description;
this.consumer = consumer;
this.taskType = taskType;
}
void execute(Args args) {
if (args.taskType != this.taskType) {
log.info("Skipping [{}] since task type is [{}] and should be [{}]]",
this.name, this.taskType, args.taskType);
return;
}
try {
boolean interactive = args.interactive;
printLog(interactive);
@@ -48,7 +61,7 @@ class Task {
}
private void printLog(boolean interactive) {
log.info("\n\n\n=== {} ===\n\n{} {}\n\n", header, description, interactive ? MSG : "");
log.info("\n\n\n=== {} ===\n\n{} {}\n\n", header, description, interactive ? MSG : "");
}
boolean skipStep() {
@@ -67,5 +80,4 @@ class Task {
String chosenOption() {
return System.console().readLine();
}
}

View File

@@ -1,5 +1,6 @@
package org.springframework.cloud.release.internal.spring;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -51,13 +52,13 @@ class Tasks {
args.releaser.createBlog(args.versionFromScRelease, args.projects);
args.releaser.createTweet(args.versionFromScRelease);
args.releaser.createReleaseNotes(args.versionFromScRelease, args.projects);
});
},TaskType.POST_RELEASE);
static Task UPDATE_GUIDES = task("updateGuides", "ug",
"UPDATE GUIDES",
"Updating Spring Guides",
args -> {
args.releaser.updateSpringGuides(args.versionFromScRelease, args.projects);
});
},TaskType.POST_RELEASE);
static Task UPDATE_SAGAN = task("updateSagan", "g",
"UPDATE SAGAN",
"Updating Sagan with release info",
@@ -69,9 +70,9 @@ class Tasks {
"Updating documentation repository",
args -> {
args.releaser.updateDocumentationRepository(args.properties, args.versionFromScRelease);
});
},TaskType.POST_RELEASE);
static final List<Task> DEFAULT_TASKS = Stream.of(
static final List<Task> DEFAULT_TASKS_PER_PROJECT = Stream.of(
Tasks.UPDATING_POMS,
Tasks.BUILD_PROJECT,
Tasks.COMMIT,
@@ -80,32 +81,61 @@ class Tasks {
Tasks.SNAPSHOTS,
Tasks.PUSH,
Tasks.CLOSE_MILESTONE,
Tasks.UPDATE_SAGAN
).collect(Collectors.toList());
static final List<Task> DEFAULT_TASKS_PER_RELEASE = Stream.of(
Tasks.CREATE_TEMPLATES,
Tasks.UPDATE_SAGAN,
Tasks.UPDATE_GUIDES,
Tasks.UPDATE_DOCUMENTATION
).collect(Collectors.toList());
static Task RELEASE = Tasks.task("release", "r",
static final List<Task> NON_COMPOSITE_TASKS = new ArrayList<Task>() {
{
addAll(DEFAULT_TASKS_PER_PROJECT);
addAll(DEFAULT_TASKS_PER_RELEASE);
}
};
static Task RELEASE = Tasks.task("release", "fr",
"FULL RELEASE",
"Perform a full release of this project without interruptions",
args -> DEFAULT_TASKS.forEach(task -> task.execute(args)));
args -> DEFAULT_TASKS_PER_PROJECT.forEach(task -> task.execute(args)));
static Task POST_RELEASE = Tasks.task("post-release", "pr",
"POST RELEASE TASKS",
"Perform post release tasks for this release without interruptions",
args -> DEFAULT_TASKS_PER_RELEASE.forEach(task -> task.execute(args)),
TaskType.POST_RELEASE);
static Task RELEASE_VERBOSE = Tasks.task("release-verbose", "r",
"FULL VERBOSE RELEASE",
"Perform a full release of this project in interactive mode (you'll be asked about skipping steps)",
args -> DEFAULT_TASKS.forEach(task -> task.execute(args)));
args -> DEFAULT_TASKS_PER_PROJECT.forEach(task -> task.execute(args)));
static Task META_RELEASE = Tasks.task("meta-release", "x",
"META RELEASE",
"Perform a meta release of projects",
args -> DEFAULT_TASKS_PER_PROJECT.forEach(task -> {
args.properties.getMetaRelease().setEnabled(true);
task.execute(args);
}));
static final List<Task> COMPOSITE_TASKS = Stream.of(
RELEASE,
RELEASE_VERBOSE
RELEASE_VERBOSE,
META_RELEASE
).collect(Collectors.toList());
static final List<Task> ALL_TASKS = Stream.of(
COMPOSITE_TASKS, DEFAULT_TASKS
static final List<Task> ALL_TASKS_PER_PROJECT = Stream.of(
COMPOSITE_TASKS, DEFAULT_TASKS_PER_PROJECT, DEFAULT_TASKS_PER_RELEASE
).flatMap(List::stream).collect(Collectors.toList());
static Task task(String name, String shortName, String header, String description, Consumer<Args> function) {
return new Task(name, shortName, header, description, function);
static Task task(String name, String shortName, String header, String description,
Consumer<Args> function) {
return task(name, shortName, header, description, function, TaskType.RELEASE);
}
static Task task(String name, String shortName, String header, String description,
Consumer<Args> function, TaskType taskType) {
return new Task(name, shortName, header, description, function, taskType);
}
static List<Task> forNames(List<Task> tasks, List<String> names) {
@@ -114,7 +144,11 @@ class Tasks {
.collect(Collectors.toList());
}
static String tasksInOrder() {
return DEFAULT_TASKS.stream().map(task -> task.name).collect(Collectors.joining(","));
static String allTasksInOrder() {
return ALL_TASKS_PER_PROJECT.stream().map(task -> task.name).collect(Collectors.joining(","));
}
}
enum TaskType {
RELEASE, POST_RELEASE
}

View File

@@ -7,8 +7,12 @@ import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.maven.model.Model;
import org.assertj.core.api.BDDAssertions;
@@ -20,12 +24,15 @@ import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.BDDMockito;
import org.mockito.Mockito;
import org.springframework.boot.test.rule.OutputCapture;
import org.springframework.cloud.release.internal.Releaser;
import org.springframework.cloud.release.internal.ReleaserProperties;
import org.springframework.cloud.release.internal.docs.DocumentationUpdater;
import org.springframework.cloud.release.internal.git.GitTestUtils;
import org.springframework.cloud.release.internal.git.ProjectGitHandler;
import org.springframework.cloud.release.internal.gradle.GradleUpdater;
import org.springframework.cloud.release.internal.options.Options;
import org.springframework.cloud.release.internal.options.OptionsBuilder;
import org.springframework.cloud.release.internal.pom.ProjectPomUpdater;
import org.springframework.cloud.release.internal.pom.ProjectVersion;
import org.springframework.cloud.release.internal.pom.Projects;
@@ -37,6 +44,7 @@ import org.springframework.cloud.release.internal.sagan.Release;
import org.springframework.cloud.release.internal.sagan.SaganClient;
import org.springframework.cloud.release.internal.sagan.SaganUpdater;
import org.springframework.cloud.release.internal.template.TemplateGenerator;
import org.springframework.context.ApplicationContext;
import org.springframework.util.FileSystemUtils;
/**
@@ -45,13 +53,20 @@ import org.springframework.util.FileSystemUtils;
public class AcceptanceTests {
@Rule public TemporaryFolder tmp = new TemporaryFolder();
@Rule public OutputCapture capture = new OutputCapture();
TestPomReader testPomReader = new TestPomReader();
File springCloudConsulProject;
File temporaryFolder;
File documentationFolder;
TestProjectGitHandler gitHandler;
NonAssertingTestProjectGitHandler nonAssertingGitHandler;
SaganClient saganClient = Mockito.mock(SaganClient.class);
ReleaserProperties releaserProperties;
TemplateGenerator templateGenerator;
SaganUpdater saganUpdater;
DocumentationUpdater documentationUpdater;
ApplicationContext applicationContext = Mockito.mock(ApplicationContext.class);
ReleaserPropertiesUpdater updater = new ReleaserPropertiesUpdater(this.applicationContext);
@Before
public void setup() throws Exception {
@@ -105,6 +120,7 @@ public class AcceptanceTests {
SpringReleaser releaser = templateOnlyReleaser(project, "spring-cloud-consul",
"vCamden.SR5", "1.1.2.RELEASE");
this.releaserProperties.getGit().setFetchVersionsFromGit(false);
this.releaserProperties.getFixedVersions().put("spring-cloud-release", "Finchley.RELEASE");
this.releaserProperties.getFixedVersions().put("spring-cloud-consul", "2.3.4.RELEASE");
File temporaryDestination = tmp.newFolder();
this.releaserProperties.getGit().setCloneDestinationDir(temporaryDestination.getAbsolutePath());
@@ -164,6 +180,80 @@ public class AcceptanceTests {
.contains("Camden.SR5");
}
@Test
public void should_perform_a_meta_release_of_sc_release_and_consul() throws Exception {
// simulates an org
Map<String, String> versions = new HashMap<>();
versions.put("spring-cloud-release", "Camden.BUILD-SNAPSHOT");
versions.put("spring-cloud-consul", "1.1.2.BUILD-SNAPSHOT");
SpringReleaser releaser = metaReleaser(versions);
releaser.release(new OptionsBuilder().metaRelease(true).options());
then(this.nonAssertingGitHandler.clonedProjects).hasSize(2);
this.nonAssertingGitHandler.clonedProjects
.forEach(project -> {
then(Arrays.asList("spring-cloud-starter-build",
"spring-cloud-consul")).contains(pom(project).getArtifactId());
then(capture.toString()).contains("executed_build", "executed_deploy", "executed_docs");
});
BDDMockito.then(saganUpdater).should(BDDMockito.atLeastOnce()).updateSagan(BDDMockito.anyString(),
BDDMockito.any(ProjectVersion.class), BDDMockito.any(ProjectVersion.class));
BDDMockito.then(documentationUpdater).should()
.updateDocsRepo(BDDMockito.any(ProjectVersion.class), BDDMockito.anyString());
}
@Test
public void should_perform_a_meta_release_of_consul_only_when_run_from_got_passed() throws Exception {
// simulates an org
Map<String, String> versions = new HashMap<>();
versions.put("spring-cloud-release", "Camden.BUILD-SNAPSHOT");
versions.put("spring-cloud-build", "1.1.2.BUILD-SNAPSHOT");
versions.put("spring-cloud-consul", "1.1.2.BUILD-SNAPSHOT");
SpringReleaser releaser = metaReleaser(versions);
releaser.release(new OptionsBuilder().metaRelease(true)
.startFrom("spring-cloud-consul")
.options());
then(this.nonAssertingGitHandler.clonedProjects).hasSize(1);
this.nonAssertingGitHandler.clonedProjects
.forEach(project -> {
then(pom(project).getArtifactId()).isEqualTo("spring-cloud-consul");
then(capture.toString()).contains("executed_build", "executed_deploy", "executed_docs");
});
BDDMockito.then(saganUpdater).should(BDDMockito.atLeastOnce()).updateSagan(BDDMockito.anyString(),
BDDMockito.any(ProjectVersion.class), BDDMockito.any(ProjectVersion.class));
BDDMockito.then(documentationUpdater).should()
.updateDocsRepo(BDDMockito.any(ProjectVersion.class), BDDMockito.anyString());
}
@Test
public void should_perform_a_meta_release_of_build_and_consul_only_when_task_names_got_passed() throws Exception {
// simulates an org
Map<String, String> versions = new HashMap<>();
versions.put("spring-cloud-release", "Camden.BUILD-SNAPSHOT");
versions.put("spring-cloud-build", "1.1.2.BUILD-SNAPSHOT");
versions.put("spring-cloud-consul", "1.1.2.BUILD-SNAPSHOT");
SpringReleaser releaser = metaReleaser(versions);
releaser.release(new OptionsBuilder().metaRelease(true)
.taskNames(Arrays.asList("spring-cloud-build", "spring-cloud-consul"))
.options());
then(this.nonAssertingGitHandler.clonedProjects).hasSize(2);
this.nonAssertingGitHandler.clonedProjects
.forEach(project -> {
then(Arrays.asList("spring-cloud-build",
"spring-cloud-consul")).contains(pom(project).getArtifactId());
then(capture.toString()).contains("executed_build", "executed_deploy", "executed_docs");
});
BDDMockito.then(saganUpdater).should(BDDMockito.atLeastOnce()).updateSagan(BDDMockito.anyString(),
BDDMockito.any(ProjectVersion.class), BDDMockito.any(ProjectVersion.class));
BDDMockito.then(documentationUpdater).should()
.updateDocsRepo(BDDMockito.any(ProjectVersion.class), BDDMockito.anyString());
}
// issue #74
@Test
public void should_perform_a_release_of_sc_build() throws Exception {
@@ -366,6 +456,11 @@ public class AcceptanceTests {
return releaserWithFullDeployment(expectedVersion, projectName, properties);
}
private SpringReleaser metaReleaser(Map<String, String> versions) throws Exception {
ReleaserProperties properties = metaReleaserProperties(versions);
return metaReleaserWithFullDeployment(properties);
}
private SpringReleaser releaserWithFullDeployment(String expectedVersion,
String projectName, ReleaserProperties properties) throws Exception {
Releaser releaser = defaultReleaser(expectedVersion, projectName, properties);
@@ -373,7 +468,26 @@ public class AcceptanceTests {
@Override String chosenOption() {
return "0";
}
});
@Override void postReleaseOptions(Options options, Args defaultArgs) {
options.interactive = false;
super.postReleaseOptions(options, defaultArgs);
}
}, updater);
}
private SpringReleaser metaReleaserWithFullDeployment(ReleaserProperties properties) throws Exception {
Releaser releaser = defaultMetaReleaser(properties);
return new SpringReleaser(releaser, properties, new OptionsProcessor(releaser, properties) {
@Override String chosenOption() {
return "0";
}
@Override void postReleaseOptions(Options options, Args defaultArgs) {
options.interactive = false;
super.postReleaseOptions(options, defaultArgs);
}
}, updater);
}
private SpringReleaser releaserWithSnapshotScRelease(File projectFile, String projectName,
@@ -387,9 +501,14 @@ public class AcceptanceTests {
Releaser releaser = defaultReleaser(expectedVersion, projectName, properties);
return new SpringReleaser(releaser, properties, new OptionsProcessor(releaser, properties) {
@Override String chosenOption() {
return "10";
return "12";
}
});
@Override void postReleaseOptions(Options options, Args defaultArgs) {
options.interactive = true;
super.postReleaseOptions(options, defaultArgs);
}
}, updater);
}
private Releaser defaultReleaser(String expectedVersion, String projectName,
@@ -415,15 +534,52 @@ public class AcceptanceTests {
return releaser;
}
private Releaser defaultMetaReleaser(ReleaserProperties properties) throws Exception {
ProjectPomUpdater pomUpdater = new ProjectPomUpdater(properties);
ProjectBuilder projectBuilder = new ProjectBuilder(properties);
NonAssertingTestProjectGitHandler handler = new NonAssertingTestProjectGitHandler(properties);
TemplateGenerator templateGenerator = Mockito.spy(new TemplateGenerator(properties, handler));
GradleUpdater gradleUpdater = new GradleUpdater(properties);
SaganUpdater saganUpdater = Mockito.spy(new SaganUpdater(this.saganClient));
DocumentationUpdater documentationUpdater = Mockito.spy(new DocumentationUpdater(handler) {
@Override public File updateDocsRepo(ProjectVersion currentProject,
String springCloudReleaseBranch) {
File file = super.updateDocsRepo(currentProject, springCloudReleaseBranch);
documentationFolder = file;
return file;
}
});
Releaser releaser = Mockito.spy(new Releaser(pomUpdater, projectBuilder, handler,
templateGenerator, gradleUpdater, saganUpdater, documentationUpdater));
this.nonAssertingGitHandler = handler;
this.templateGenerator = templateGenerator;
this.saganUpdater = saganUpdater;
this.documentationUpdater = documentationUpdater;
return releaser;
}
private ReleaserProperties releaserProperties(File project, String branch) throws URISyntaxException {
ReleaserProperties releaserProperties = new ReleaserProperties();
releaserProperties.getGit().setSpringCloudReleaseGitUrl(file("/projects/spring-cloud-release/").toURI().getPath());
releaserProperties.getGit().setDocumentationUrl(file("/projects/spring-cloud-static-angel/").toURI().getPath());
releaserProperties.getPom().setBranch(branch);
releaserProperties.setWorkingDir(project.getPath());
releaserProperties.getMaven().setBuildCommand("echo build");
releaserProperties.getMaven().setDeployCommand("echo deploy");
releaserProperties.getMaven().setPublishDocsCommands(new String[] { "echo docs"} );
releaserProperties.setWorkingDir(project.getPath());
releaserProperties.getPom().setBranch(branch);
this.releaserProperties = releaserProperties;
return releaserProperties;
}
private ReleaserProperties metaReleaserProperties(Map<String, String> versions) throws URISyntaxException {
ReleaserProperties releaserProperties = new ReleaserProperties();
releaserProperties.getGit().setDocumentationUrl(file("/projects/spring-cloud-static-angel/").toURI().getPath());
releaserProperties.getMaven().setBuildCommand("echo executed_build");
releaserProperties.getMaven().setDeployCommand("echo executed_deploy");
releaserProperties.getMaven().setPublishDocsCommands(new String[] { "echo executed_docs"} );
releaserProperties.getMetaRelease().setGitOrgUrl("file://" + this.temporaryFolder.getAbsolutePath());
releaserProperties.getMetaRelease().setEnabled(true);
releaserProperties.setFixedVersions(versions);
this.releaserProperties = releaserProperties;
return releaserProperties;
}
@@ -466,6 +622,48 @@ public class AcceptanceTests {
}
}
class NonAssertingTestProjectGitHandler extends ProjectGitHandler {
boolean closedMilestones = false;
boolean issueCreatedInSpringGuides = false;
List<File> clonedProjects = new ArrayList<>();
public NonAssertingTestProjectGitHandler(ReleaserProperties properties) {
super(properties);
}
@Override public void closeMilestone(ProjectVersion releaseVersion) {
this.closedMilestones = true;
}
@Override public void createIssueInSpringGuides(Projects projects,
ProjectVersion version) {
this.issueCreatedInSpringGuides = true;
}
@Override public String milestoneUrl(ProjectVersion releaseVersion) {
return "http://foo.bar.com/" + releaseVersion.toString();
}
@Override public File cloneScReleaseProject() {
File file = super.cloneScReleaseProject();
this.clonedProjects.add(file);
return file;
}
@Override public File cloneDocumentationProject() {
File file = super.cloneDocumentationProject();
this.clonedProjects.add(file);
return file;
}
@Override public File cloneProjectFromOrg(String projectName) {
File file = super.cloneProjectFromOrg(projectName);
this.clonedProjects.add(file);
return file;
}
}
private File tmpFile(String relativePath) {
return new File(this.temporaryFolder, relativePath);
}

View File

@@ -210,6 +210,10 @@ public class OptionsProcessorTests {
@Override Task releaseTask() {
return firstTask;
}
@Override String chosenOption() {
return "0";
}
};
Options options = nonInteractiveOpts().fullRelease(true).options();
@@ -226,6 +230,10 @@ public class OptionsProcessorTests {
@Override Task releaseVerboseTask() {
return firstTask;
}
@Override String chosenOption() {
return "0";
}
};
Options options = interactiveOpts().fullRelease(true).options();
@@ -245,7 +253,7 @@ public class OptionsProcessorTests {
}
private Args args() {
return new Args(null, null, null, null, null, null, false);
return new Args(null, null, null, null, null, null, false, TaskType.RELEASE);
}
private List<String> list(String... list) {

View File

@@ -0,0 +1,67 @@
package org.springframework.cloud.release.internal.spring;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.assertj.core.api.BDDAssertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.BDDMockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.cloud.release.internal.ReleaserApplication;
import org.springframework.cloud.release.internal.ReleaserProperties;
import org.springframework.cloud.release.internal.ReleaserPropertiesAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author Marcin Grzejszczak
*/
@RunWith(SpringRunner.class)
@Import(ReleaserPropertiesIntegrationTests.Config.class)
public class ReleaserPropertiesIntegrationTests {
@Autowired List<ReleaserPropertiesAware> propertiesAware;
@Autowired ApplicationContext context;
@Test public void should_update_properties() {
ReleaserProperties properties = new ReleaserProperties();
properties.getPom().setBranch("fooooo");
new ReleaserPropertiesUpdater(this.context).updateProperties(properties);
BDDAssertions.then(this.propertiesAware).hasSize(2);
this.propertiesAware.forEach(aware ->
BDDAssertions.then(((ReleaserPropertiesHaving) aware)
.properties.getPom().getBranch()).isEqualTo("fooooo"));
}
@Configuration
static class Config {
@Bean ReleaserPropertiesAware aware1() {
return new ReleaserPropertiesHaving();
}
@Bean ReleaserPropertiesAware aware2() {
return new ReleaserPropertiesHaving();
}
}
static class ReleaserPropertiesHaving implements ReleaserPropertiesAware {
ReleaserProperties properties;
@Override public void setReleaserProperties(ReleaserProperties properties) {
this.properties = properties;
}
ReleaserProperties getProps() {
return this.properties;
}
}
}

View File

@@ -0,0 +1,48 @@
package org.springframework.cloud.release.internal.spring;
import java.util.HashMap;
import java.util.Map;
import org.assertj.core.api.BDDAssertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.BDDMockito;
import org.springframework.cloud.release.internal.ReleaserProperties;
import org.springframework.cloud.release.internal.ReleaserPropertiesAware;
import org.springframework.context.ApplicationContext;
import static org.junit.Assert.*;
/**
* @author Marcin Grzejszczak
*/
public class ReleaserPropertiesUpdaterTests {
ApplicationContext context = BDDMockito.mock(ApplicationContext.class);
@Test public void should_update_properties() {
Aware aware = new Aware();
BDDMockito.given(this.context.getBeansOfType(BDDMockito.any(Class.class)))
.willReturn(beansOfType(aware));
ReleaserPropertiesUpdater updater = new ReleaserPropertiesUpdater(this.context);
updater.updateProperties(new ReleaserProperties());
BDDAssertions.then(aware.properties).isNotNull();
}
private Map<String, Object> beansOfType(Aware aware) {
Map<String, Object> map = new HashMap<>();
map.put("foo", aware);
return map;
}
class Aware implements ReleaserPropertiesAware {
ReleaserProperties properties;
@Override public void setReleaserProperties(ReleaserProperties properties) {
this.properties = properties;
}
}
}

View File

@@ -10,7 +10,6 @@ import org.springframework.boot.test.rule.OutputCapture;
import static org.assertj.core.api.BDDAssertions.then;
import static org.assertj.core.api.BDDAssertions.thenThrownBy;
import static org.junit.Assert.*;
/**
* @author Marcin Grzejszczak
@@ -27,21 +26,19 @@ public class TaskTests {
}
});
task.execute(Mockito.mock(Args.class));
task.execute(new Args(TaskType.RELEASE));
then(someBool.get()).isTrue();
}
@Test public void should_fail_with_nice_text_on_exception() {
final AtomicBoolean someBool = new AtomicBoolean();
Task task = new Task("foo", "bar", "baz", "descr", new Consumer<Args>() {
@Override public void accept(Args args) {
someBool.set(true);
throw new RuntimeException("foooooooo");
}
Task task = new Task("foo", "bar", "baz", "descr", args -> {
someBool.set(true);
throw new RuntimeException("foooooooo");
});
thenThrownBy(() -> task.execute(Mockito.mock(Args.class)))
thenThrownBy(() -> task.execute(new Args(TaskType.RELEASE)))
.isInstanceOf(RuntimeException.class);
then(someBool.get()).isTrue();
then(this.capture.toString())