Added automatic SC-Guides issue creation; fixes gh-69
This commit is contained in:
@@ -41,6 +41,7 @@ why this tool makes it easy to automate the release / dependency update process
|
||||
- Generates a tweet template under `target/tweet.txt` (ONLY FOR NON-SNAPSHOT VERSIONS)
|
||||
- Generates a release notes template under `target/notes.md` (ONLY FOR NON-SNAPSHOT VERSIONS)
|
||||
- Updates project information in Sagan (http://spring.io) (ONLY FOR SNAPSHOT / RELEASE VERSIONS)
|
||||
- For `GA`/ `SR` release will create an issue in Spring Guides under https://github.com/spring-guides/getting-started-guides/issues/
|
||||
|
||||
IMPORTANT: Starting with version that does Sagan integration, you MUST pass the OAuth token,
|
||||
otherwise the application will fail to start
|
||||
|
||||
@@ -31,6 +31,7 @@ why this tool makes it easy to automate the release / dependency update process
|
||||
- Generates a tweet template under `target/tweet.txt` (ONLY FOR NON-SNAPSHOT VERSIONS)
|
||||
- Generates a release notes template under `target/notes.md` (ONLY FOR NON-SNAPSHOT VERSIONS)
|
||||
- Updates project information in Sagan (http://spring.io) (ONLY FOR SNAPSHOT / RELEASE VERSIONS)
|
||||
- For `GA`/ `SR` release will create an issue in Spring Guides under https://github.com/spring-guides/getting-started-guides/issues/
|
||||
|
||||
IMPORTANT: Starting with version that does Sagan integration, you MUST pass the OAuth token,
|
||||
otherwise the application will fail to start
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.springframework.cloud.release.internal;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -132,6 +134,16 @@ public class Releaser {
|
||||
}
|
||||
File blog = this.templateGenerator.blog(projects);
|
||||
log.info("\nSuccessfully created blog template at location [{}]", blog);
|
||||
|
||||
}
|
||||
|
||||
public void updateSpringGuides(ProjectVersion releaseVersion, Projects projects) {
|
||||
if (!(releaseVersion.isRelease() || releaseVersion.isServiceRelease())) {
|
||||
log.info("\nWon't updated Spring Guides for a non Release / Service Release version");
|
||||
return;
|
||||
}
|
||||
this.projectGitHandler.createIssueInSpringGuides(projects, releaseVersion);
|
||||
log.info("\nSuccessfully updated Spring Guides issues");
|
||||
}
|
||||
|
||||
public void createTweet(ProjectVersion releaseVersion) {
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package org.springframework.cloud.release.internal.git;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.jcabi.github.Coordinates;
|
||||
import com.jcabi.github.Github;
|
||||
import com.jcabi.github.Issue;
|
||||
import com.jcabi.github.Milestone;
|
||||
import com.jcabi.github.Repo;
|
||||
import com.jcabi.github.RtGithub;
|
||||
import com.jcabi.http.wire.RetryWire;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.cloud.release.internal.ReleaserProperties;
|
||||
import org.springframework.cloud.release.internal.pom.ProjectVersion;
|
||||
import org.springframework.cloud.release.internal.pom.Projects;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
class GithubIssues {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
private static final String GITHUB_ISSUE_TITLE = "Spring Cloud Release took place";
|
||||
|
||||
private final Github github;
|
||||
private final ReleaserProperties properties;
|
||||
|
||||
GithubIssues(ReleaserProperties properties) {
|
||||
this.github = new RtGithub(new RtGithub(
|
||||
properties.getGit().getOauthToken()).entry().through(RetryWire.class));
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
GithubIssues(Github github, ReleaserProperties properties) {
|
||||
this.github = github;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
void fileIssue(Projects projects, ProjectVersion version) {
|
||||
Assert.hasText(this.properties.getGit().getOauthToken(),
|
||||
"You have to pass Github OAuth token for milestone closing to be operational");
|
||||
// do this only for RELEASE & SR
|
||||
String releaseVersion = parsedVersion();
|
||||
if (!(version.isRelease() || version.isServiceRelease())) {
|
||||
log.info("Guide issue creation will occur only for Release or Service Release versions. Your version is [{}]", releaseVersion);
|
||||
return;
|
||||
}
|
||||
Repo springGuides = this.github.repos()
|
||||
.get(new Coordinates.Simple("spring-guides", "getting-started-guides"));
|
||||
String issueTitle = StringUtils.capitalize(releaseVersion) + " " + GITHUB_ISSUE_TITLE;
|
||||
// check if the issue is not already there
|
||||
boolean issueAlreadyFiled = issueAlreadyFiled(springGuides, issueTitle);
|
||||
if (issueAlreadyFiled) {
|
||||
log.info("Issue already filed, will not do that again");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
int number = springGuides.issues().create(issueTitle, issueText(projects))
|
||||
.number();
|
||||
log.info("Successfully created an issue with title [{}] in Spring Guides under: https://github.com/spring-guides/getting-started-guides/issues/" + number, issueTitle);
|
||||
}
|
||||
catch (IOException e) {
|
||||
log.error("Exception occurred while trying to create the issue in guides", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String parsedVersion() {
|
||||
String version = this.properties.getPom().getBranch();
|
||||
if (version.startsWith("v")) {
|
||||
return version.substring(1);
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
private String issueText(Projects projects) {
|
||||
StringBuilder builder = new StringBuilder()
|
||||
.append("Spring Cloud [")
|
||||
.append(parsedVersion())
|
||||
.append("] Released with the following projects:")
|
||||
.append("\n\n");
|
||||
projects.forEach(project -> builder
|
||||
.append(project.projectName).append(" : ")
|
||||
.append("`").append(project.version).append("`")
|
||||
.append("\n"));
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private boolean issueAlreadyFiled(Repo springGuides, String issueTitle) {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("state", "open");
|
||||
int counter = 0;
|
||||
int maxIssues = 10;
|
||||
for (Issue issue : springGuides.issues().iterate(map)) {
|
||||
if (counter >= maxIssues) {
|
||||
return false;
|
||||
}
|
||||
Issue.Smart smartIssue = new Issue.Smart(issue);
|
||||
try {
|
||||
if (issueTitle.equals(smartIssue.title())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
counter = counter + 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.cloud.release.internal.ReleaserProperties;
|
||||
import org.springframework.cloud.release.internal.pom.ProjectVersion;
|
||||
import org.springframework.cloud.release.internal.pom.Projects;
|
||||
|
||||
/**
|
||||
* Contains business logic around Git & Github operations
|
||||
@@ -26,10 +27,12 @@ public class ProjectGitHandler {
|
||||
|
||||
private final ReleaserProperties properties;
|
||||
private final GithubMilestones githubMilestones;
|
||||
private final GithubIssues githubIssues;
|
||||
|
||||
public ProjectGitHandler(ReleaserProperties properties) {
|
||||
this.properties = properties;
|
||||
this.githubMilestones = new GithubMilestones(properties);
|
||||
this.githubIssues = new GithubIssues(properties);
|
||||
}
|
||||
|
||||
public void commitAndTagIfApplicable(File project, ProjectVersion version) {
|
||||
@@ -90,6 +93,10 @@ public class ProjectGitHandler {
|
||||
this.githubMilestones.closeMilestone(releaseVersion);
|
||||
}
|
||||
|
||||
public void createIssueInSpringGuides(Projects projects, ProjectVersion version) {
|
||||
this.githubIssues.fileIssue(projects, version);
|
||||
}
|
||||
|
||||
public String milestoneUrl(ProjectVersion releaseVersion) {
|
||||
return this.githubMilestones.milestoneUrl(releaseVersion);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package org.springframework.cloud.release.internal.git;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import javax.json.Json;
|
||||
|
||||
import com.jcabi.github.Coordinates;
|
||||
import com.jcabi.github.Issue;
|
||||
import com.jcabi.github.Milestone;
|
||||
import com.jcabi.github.Repo;
|
||||
import com.jcabi.github.mock.MkGithub;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.boot.test.rule.OutputCapture;
|
||||
import org.springframework.cloud.release.internal.ReleaserProperties;
|
||||
import org.springframework.cloud.release.internal.pom.ProjectVersion;
|
||||
import org.springframework.cloud.release.internal.pom.Projects;
|
||||
|
||||
import static org.assertj.core.api.BDDAssertions.then;
|
||||
import static org.assertj.core.api.BDDAssertions.thenThrownBy;
|
||||
|
||||
/**
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
public class GithubIssuesTests {
|
||||
|
||||
MkGithub github;
|
||||
Repo repo;
|
||||
@Rule public TemporaryFolder folder = new TemporaryFolder();
|
||||
@Rule public OutputCapture capture = new OutputCapture();
|
||||
|
||||
@Before
|
||||
public void setup() throws URISyntaxException, IOException {
|
||||
this.github = new MkGithub("spring-guides");
|
||||
this.repo = createGettingStartedGuides(this.github);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_not_do_anything_for_non_release_train_version() throws IOException {
|
||||
GithubIssues issues = new GithubIssues(this.github, withToken());
|
||||
|
||||
issues.fileIssue(new Projects(
|
||||
new ProjectVersion("foo", "1.0.0.BUILD-SNAPSHOT")
|
||||
), new ProjectVersion("sc-release", "Edgware.BUILD-SNAPSHOT"));
|
||||
|
||||
then(this.capture.toString()).contains("Guide issue creation will occur only");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_file_an_issue_for_release_version() throws IOException {
|
||||
GithubIssues issues = new GithubIssues(this.github, withToken());
|
||||
|
||||
issues.fileIssue(new Projects(
|
||||
new ProjectVersion("foo", "1.0.0.RELEASE"),
|
||||
new ProjectVersion("bar", "2.0.0.RELEASE"),
|
||||
new ProjectVersion("baz", "3.0.0.RELEASE")
|
||||
), new ProjectVersion("sc-release", "Edgware.RELEASE"));
|
||||
|
||||
then(this.capture.toString()).doesNotContain("Guide issue creation will occur only");
|
||||
Issue issue = this.github.repos()
|
||||
.get(new Coordinates.Simple("spring-guides", "getting-started-guides"))
|
||||
.issues().get(1);
|
||||
then(issue.exists()).isTrue();
|
||||
Issue.Smart smartIssue = new Issue.Smart(issue);
|
||||
then(smartIssue.title()).isEqualTo("Edgware.RELEASE Spring Cloud Release took place");
|
||||
then(smartIssue.body())
|
||||
.contains("Spring Cloud [Edgware.RELEASE]")
|
||||
.contains("foo : `1.0.0.RELEASE`")
|
||||
.contains("bar : `2.0.0.RELEASE`")
|
||||
.contains("baz : `3.0.0.RELEASE`");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_throw_exception_when_no_token_was_passed() {
|
||||
GithubIssues issues = new GithubIssues(new ReleaserProperties());
|
||||
|
||||
thenThrownBy(() -> issues.fileIssue(new Projects(Collections.emptySet()),
|
||||
nonGaSleuthProject()))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("You have to pass Github OAuth token for milestone closing to be operational");
|
||||
}
|
||||
|
||||
private Repo createGettingStartedGuides(MkGithub github) throws IOException {
|
||||
return github.repos().create(
|
||||
Json.createObjectBuilder().add(
|
||||
"name",
|
||||
"getting-started-guides"
|
||||
).build()
|
||||
);
|
||||
}
|
||||
|
||||
private ProjectVersion nonGaSleuthProject() {
|
||||
return new ProjectVersion("spring-cloud-sleuth", "0.2.0.BUILD-SNAPSHOT");
|
||||
}
|
||||
|
||||
ReleaserProperties withToken() {
|
||||
ReleaserProperties properties = new ReleaserProperties();
|
||||
properties.getGit().setOauthToken("foo");
|
||||
properties.getPom().setBranch("vEdgware.RELEASE");
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,12 @@ class Tasks {
|
||||
args.releaser.createTweet(args.versionFromScRelease);
|
||||
args.releaser.createReleaseNotes(args.versionFromScRelease, args.projects);
|
||||
});
|
||||
static Task UPDATE_GUIDES = task("updateGuides", "ug",
|
||||
"UPDATE GUIDES",
|
||||
"Updating Spring Guides",
|
||||
args -> {
|
||||
args.releaser.updateSpringGuides(args.versionFromScRelease, args.projects);
|
||||
});
|
||||
static Task UPDATE_SAGAN = task("updateSagan", "g",
|
||||
"UPDATE SAGAN",
|
||||
"Updating Sagan with release info",
|
||||
@@ -69,7 +75,8 @@ class Tasks {
|
||||
Tasks.PUSH,
|
||||
Tasks.CLOSE_MILESTONE,
|
||||
Tasks.CREATE_TEMPLATES,
|
||||
Tasks.UPDATE_SAGAN
|
||||
Tasks.UPDATE_SAGAN,
|
||||
Tasks.UPDATE_GUIDES
|
||||
).collect(Collectors.toList());
|
||||
|
||||
static Task RELEASE = Tasks.task("release", "r",
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.springframework.cloud.release.internal.git.ProjectGitHandler;
|
||||
import org.springframework.cloud.release.internal.gradle.GradleUpdater;
|
||||
import org.springframework.cloud.release.internal.pom.ProjectPomUpdater;
|
||||
import org.springframework.cloud.release.internal.pom.ProjectVersion;
|
||||
import org.springframework.cloud.release.internal.pom.Projects;
|
||||
import org.springframework.cloud.release.internal.pom.TestPomReader;
|
||||
import org.springframework.cloud.release.internal.pom.TestUtils;
|
||||
import org.springframework.cloud.release.internal.project.ProjectBuilder;
|
||||
@@ -104,6 +105,7 @@ public class AcceptanceTests {
|
||||
BDDMockito.then(this.saganClient).should(BDDMockito.times(2)).updateRelease(BDDMockito.eq("spring-cloud-consul"),
|
||||
BDDMockito.anyList());
|
||||
BDDMockito.then(this.saganClient).should().deleteRelease("spring-cloud-consul", "1.1.2.BUILD-SNAPSHOT");
|
||||
then(this.gitHandler.issueCreatedInSpringGuides).isTrue();
|
||||
}
|
||||
|
||||
// issue #74
|
||||
@@ -139,6 +141,7 @@ public class AcceptanceTests {
|
||||
BDDMockito.anyList());
|
||||
BDDMockito.then(this.saganClient).should()
|
||||
.deleteRelease("spring-cloud-build", "1.2.2.BUILD-SNAPSHOT");
|
||||
then(this.gitHandler.issueCreatedInSpringGuides).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -178,6 +181,8 @@ public class AcceptanceTests {
|
||||
.contains("- Spring Cloud Bus `1.3.0.M1` ([issues](http://foo.bar.com/1.3.0.M1))");
|
||||
BDDMockito.then(this.saganClient).should().updateRelease(BDDMockito.eq("spring-cloud-consul"),
|
||||
BDDMockito.anyList());
|
||||
// we update guides only for SR / RELEASE
|
||||
then(this.gitHandler.issueCreatedInSpringGuides).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -209,6 +214,7 @@ public class AcceptanceTests {
|
||||
.contains("- Spring Cloud Bus `1.3.0.M1` ([issues](http://foo.bar.com/1.3.0.M1)");
|
||||
BDDMockito.then(this.saganClient).should(BDDMockito.never()).updateRelease(
|
||||
BDDMockito.anyString(), BDDMockito.anyList());
|
||||
then(this.gitHandler.issueCreatedInSpringGuides).isFalse();
|
||||
}
|
||||
|
||||
private Iterable<RevCommit> listOfCommits(File project) throws GitAPIException {
|
||||
@@ -349,6 +355,7 @@ public class AcceptanceTests {
|
||||
class TestProjectGitHandler extends ProjectGitHandler {
|
||||
|
||||
boolean closedMilestones = false;
|
||||
boolean issueCreatedInSpringGuides = false;
|
||||
final String expectedVersion;
|
||||
final String projectName;
|
||||
|
||||
@@ -365,6 +372,11 @@ public class AcceptanceTests {
|
||||
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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user