diff --git a/spring-cloud-release-tools-core/pom.xml b/spring-cloud-release-tools-core/pom.xml index f0edbad6..ddd99efa 100644 --- a/spring-cloud-release-tools-core/pom.xml +++ b/spring-cloud-release-tools-core/pom.xml @@ -67,16 +67,21 @@ - - org.springframework.boot - spring-boot-starter-test - test - com.jcabi jcabi-github 0.23 + + com.github.jknack + handlebars + 4.0.6 + + + org.springframework.boot + spring-boot-starter-test + test + diff --git a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/Releaser.java b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/Releaser.java index 6406b996..3a917c52 100644 --- a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/Releaser.java +++ b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/Releaser.java @@ -5,6 +5,7 @@ import java.lang.invoke.MethodHandles; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.cloud.release.internal.template.TemplateGenerator; import org.springframework.cloud.release.internal.git.ProjectGitUpdater; import org.springframework.cloud.release.internal.pom.ProjectPomUpdater; import org.springframework.cloud.release.internal.pom.ProjectVersion; @@ -20,12 +21,14 @@ public class Releaser { private final ProjectPomUpdater projectPomUpdater; private final ProjectBuilder projectBuilder; private final ProjectGitUpdater projectGitUpdater; + private final TemplateGenerator templateGenerator; - public Releaser(ProjectPomUpdater projectPomUpdater, - ProjectBuilder projectBuilder, ProjectGitUpdater projectGitUpdater) { + public Releaser(ProjectPomUpdater projectPomUpdater, ProjectBuilder projectBuilder, + ProjectGitUpdater projectGitUpdater, TemplateGenerator templateGenerator) { this.projectPomUpdater = projectPomUpdater; this.projectBuilder = projectBuilder; this.projectGitUpdater = projectGitUpdater; + this.templateGenerator = templateGenerator; } public Projects retrieveVersionsFromSCRelease() { @@ -79,4 +82,13 @@ public class Releaser { this.projectGitUpdater.closeMilestone(releaseVersion); log.info("\nSuccessfully closed milestone"); } + + public void createEmail(ProjectVersion releaseVersion) { + if (releaseVersion.isSnapshot()) { + log.info("\nWon't send an email for a SNAPSHOT version"); + } else { + File email = this.templateGenerator.email(); + log.info("\nSuccessfully created email template at location [{}]", email); + } + } } diff --git a/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/template/TemplateGenerator.java b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/template/TemplateGenerator.java new file mode 100644 index 00000000..5cdb7406 --- /dev/null +++ b/spring-cloud-release-tools-core/src/main/java/org/springframework/cloud/release/internal/template/TemplateGenerator.java @@ -0,0 +1,63 @@ +package org.springframework.cloud.release.internal.template; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +import org.springframework.cloud.release.internal.ReleaserProperties; + +import com.github.jknack.handlebars.Handlebars; +import com.github.jknack.handlebars.Template; +import com.github.jknack.handlebars.helper.StringHelpers; + +/** + * @author Marcin Grzejszczak + */ +public class TemplateGenerator { + + private final String emailTemplate = "/templates/email"; + private final File emailOutput = new File("target/email.txt"); + private final ReleaserProperties props; + + public TemplateGenerator(ReleaserProperties props) { + this.props = props; + } + + public File email() { + try { + Template template = uncheckedCompileTemplate(); + String releaseVersion = parsedVersion(); + String email = template.apply(releaseVersion); + if (emailOutput.exists()) { + emailOutput.delete(); + } + if (!emailOutput.createNewFile()) { + throw new IllegalStateException("Couldn't create a file with email template"); + } + Files.write(emailOutput.toPath(), email.getBytes()); + return emailOutput; + } + catch (IOException e) { + throw new IllegalStateException(e); + } + } + + private String parsedVersion() { + String version = this.props.getPom().getBranch(); + if (version.startsWith("v")) { + return version.substring(1); + } + return version; + } + + private Template uncheckedCompileTemplate() { + try { + Handlebars handlebars = new Handlebars(); + handlebars.registerHelper("replace", StringHelpers.replace); + handlebars.registerHelper("capitalizeFirst", StringHelpers.capitalizeFirst); + return handlebars.compile(this.emailTemplate); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/spring-cloud-release-tools-core/src/main/resources/templates/email.hbs b/spring-cloud-release-tools-core/src/main/resources/templates/email.hbs new file mode 100644 index 00000000..31a9a8d7 --- /dev/null +++ b/spring-cloud-release-tools-core/src/main/resources/templates/email.hbs @@ -0,0 +1,12 @@ +Title: +Spring Cloud {{ this }} available + +Content: +All, + +On behalf of the team and the community, I'm excited to announce Spring Cloud {{ replace this "." " " }} Train release. + +link to blog post +link to twitter + +Cheers, \ No newline at end of file diff --git a/spring-cloud-release-tools-core/src/test/java/org/springframework/cloud/release/internal/ReleaserTests.java b/spring-cloud-release-tools-core/src/test/java/org/springframework/cloud/release/internal/ReleaserTests.java index dd8a6186..52dea902 100644 --- a/spring-cloud-release-tools-core/src/test/java/org/springframework/cloud/release/internal/ReleaserTests.java +++ b/spring-cloud-release-tools-core/src/test/java/org/springframework/cloud/release/internal/ReleaserTests.java @@ -10,6 +10,7 @@ import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.cloud.release.internal.template.TemplateGenerator; import org.springframework.cloud.release.internal.git.ProjectGitUpdater; import org.springframework.cloud.release.internal.pom.ProjectPomUpdater; import org.springframework.cloud.release.internal.pom.ProjectVersion; @@ -28,6 +29,7 @@ public class ReleaserTests { @Mock ProjectPomUpdater projectPomUpdater; @Mock ProjectBuilder projectBuilder; @Mock ProjectGitUpdater projectGitUpdater; + @Mock TemplateGenerator templateGenerator; @InjectMocks Releaser releaser; File pom; @@ -64,4 +66,18 @@ public class ReleaserTests { then(this.projectBuilder).should().bumpVersions(anyString()); } + @Test + public void should_not_generate_email_for_snapshot_version() throws Exception { + this.releaser.createEmail(new ProjectVersion("original", "1.0.0.BUILD-SNAPSHOT")); + + then(this.templateGenerator).should(never()).email(); + } + + @Test + public void should_generate_email_for_release_version() throws Exception { + this.releaser.createEmail(new ProjectVersion("original", "1.0.0.RELEASE")); + + then(this.templateGenerator).should().email(); + } + } \ No newline at end of file diff --git a/spring-cloud-release-tools-core/src/test/java/org/springframework/cloud/release/internal/template/TemplateGeneratorTests.java b/spring-cloud-release-tools-core/src/test/java/org/springframework/cloud/release/internal/template/TemplateGeneratorTests.java new file mode 100644 index 00000000..4137ab18 --- /dev/null +++ b/spring-cloud-release-tools-core/src/test/java/org/springframework/cloud/release/internal/template/TemplateGeneratorTests.java @@ -0,0 +1,44 @@ +package org.springframework.cloud.release.internal.template; + +import java.io.File; + +import org.junit.Test; +import org.springframework.cloud.release.internal.ReleaserProperties; + +import static org.assertj.core.api.BDDAssertions.then; + +/** + * @author Marcin Grzejszczak + */ +public class TemplateGeneratorTests { + + @Test + public void should_generate_email_from_template_for_tag_with_v_prefix() { + ReleaserProperties props = new ReleaserProperties(); + props.getPom().setBranch("vDalston.RELEASE"); + File generatedMail = new TemplateGenerator(props).email(); + + then(generatedMail).hasContent(expectedEmail()); + } + + @Test + public void should_generate_email_from_template_for_tag_without_v_prefix() { + ReleaserProperties props = new ReleaserProperties(); + props.getPom().setBranch("Dalston.RELEASE"); + File generatedMail = new TemplateGenerator(props).email(); + + then(generatedMail).hasContent(expectedEmail()); + } + + private String expectedEmail() { + return "Title:\n" + + "Spring Cloud Dalston.RELEASE available\n\n" + + "Content:\n" + + "All,\n" + "\n" + + "On behalf of the team and the community, I'm excited to announce Spring Cloud Dalston RELEASE Train release.\n" + + "\n" + + "link to blog post\n" + + "link to twitter\n\n" + + "Cheers,\n"; + } +} diff --git a/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/ReleaserConfiguration.java b/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/ReleaserConfiguration.java index 1ec141cc..df155948 100644 --- a/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/ReleaserConfiguration.java +++ b/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/ReleaserConfiguration.java @@ -18,6 +18,7 @@ package org.springframework.cloud.release.internal.spring; 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.template.TemplateGenerator; import org.springframework.cloud.release.internal.project.ProjectBuilder; import org.springframework.cloud.release.internal.git.ProjectGitUpdater; import org.springframework.cloud.release.internal.pom.ProjectPomUpdater; @@ -31,6 +32,6 @@ class ReleaserConfiguration { @Bean SpringReleaser releaser(ReleaserProperties properties) { ProjectPomUpdater pomUpdater = new ProjectPomUpdater(properties); return new SpringReleaser(new Releaser(pomUpdater, new ProjectBuilder(properties, pomUpdater), - new ProjectGitUpdater(properties)), properties); + new ProjectGitUpdater(properties), new TemplateGenerator(properties)), properties); } } diff --git a/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/SpringReleaser.java b/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/SpringReleaser.java index c0855ec5..5e21eed3 100644 --- a/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/SpringReleaser.java +++ b/spring-cloud-release-tools-spring/src/main/java/org/springframework/cloud/release/internal/spring/SpringReleaser.java @@ -37,12 +37,12 @@ public class SpringReleaser { File project = new File(workingDir); ProjectVersion originalVersion = new ProjectVersion(project); Projects projects = this.releaser.retrieveVersionsFromSCRelease(); - ProjectVersion changedVersion = projects.forFile(project); + ProjectVersion versionFromScRelease = projects.forFile(project); log.info("\n\n\n=== UPDATING POMS ===\n\nWill run the application " + "for root folder [{}]. \n\nPress ENTER to continue {}", workingDir, MSG); boolean skipPoms = skipStep(); if (!skipPoms) { - changedVersion = this.releaser.updateProjectFromScRelease(project, projects); + versionFromScRelease = this.releaser.updateProjectFromScRelease(project, projects); } log.info("\n\n\n=== BUILD PROJECT ===\n\nPress ENTER to build the project {}", MSG); boolean skipBuild = skipStep(); @@ -52,7 +52,7 @@ public class SpringReleaser { log.info("\n\n\n=== COMMITTING (ALL) AND PUSHING TAGS (NON-SNAPSHOTS) ===\n\nPress ENTER to commit, tag and push the tag {}", MSG); boolean skipCommit = skipStep(); if (!skipCommit) { - this.releaser.commitAndPushTags(project, changedVersion); + this.releaser.commitAndPushTags(project, versionFromScRelease); } log.info("\n\n\n=== ARTIFACT DEPLOYMENT ===\n\nPress ENTER to deploy the artifacts {}", MSG); boolean skipDeployment = skipStep(); @@ -62,14 +62,14 @@ public class SpringReleaser { log.info("\n\n\n=== PUBLISHING DOCS ===\n\nPress ENTER to publish the docs {}", MSG); boolean skipDocs = skipStep(); if (!skipDocs) { - this.releaser.publishDocs(changedVersion); + this.releaser.publishDocs(versionFromScRelease); } - if (!changedVersion.isSnapshot()) { + if (!versionFromScRelease.isSnapshot()) { log.info("\n\n\n=== REVERTING CHANGES & BUMPING VERSION (RELEASE ONLY)===\n\nPress ENTER to go " + "back to snapshots and bump originalVersion by patch {}", MSG); boolean skipRevert = skipStep(); if (!skipRevert) { - this.releaser.rollbackReleaseVersion(project, originalVersion, changedVersion); + this.releaser.rollbackReleaseVersion(project, originalVersion, versionFromScRelease); } } log.info("\n\n\n=== PUSHING CHANGES===\n\nPress ENTER to push the commits {}", MSG); @@ -77,11 +77,18 @@ public class SpringReleaser { if (!skipPush) { this.releaser.pushCurrentBranch(project); } - if (!changedVersion.isSnapshot()) { + if (!versionFromScRelease.isSnapshot()) { log.info("\n\n\n=== CLOSING MILESTONE===\n\nPress ENTER to close the milestone at Github {}", MSG); boolean skipMilestone = skipStep(); if (!skipMilestone) { - this.releaser.closeMilestone(changedVersion); + this.releaser.closeMilestone(versionFromScRelease); + } + } + if (!versionFromScRelease.isSnapshot()) { + log.info("\n\n\n=== CREATING TEMPLATES===\n\nPress ENTER to create email / tweet etc. templates {}", MSG); + boolean skipTemplates = skipStep(); + if (!skipTemplates) { + this.releaser.createEmail(versionFromScRelease); } } } diff --git a/spring-cloud-release-tools-spring/src/test/java/org/springframework/cloud/release/internal/spring/AcceptanceTests.java b/spring-cloud-release-tools-spring/src/test/java/org/springframework/cloud/release/internal/spring/AcceptanceTests.java index 56428410..62b9db36 100644 --- a/spring-cloud-release-tools-spring/src/test/java/org/springframework/cloud/release/internal/spring/AcceptanceTests.java +++ b/spring-cloud-release-tools-spring/src/test/java/org/springframework/cloud/release/internal/spring/AcceptanceTests.java @@ -1,7 +1,9 @@ package org.springframework.cloud.release.internal.spring; import java.io.File; +import java.io.IOException; import java.net.URISyntaxException; +import java.nio.file.Files; import java.util.Iterator; import org.apache.maven.model.Model; @@ -14,6 +16,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.springframework.cloud.release.internal.Releaser; import org.springframework.cloud.release.internal.ReleaserProperties; +import org.springframework.cloud.release.internal.template.TemplateGenerator; import org.springframework.cloud.release.internal.git.GitTestUtils; import org.springframework.cloud.release.internal.git.ProjectGitUpdater; import org.springframework.cloud.release.internal.pom.ProjectPomUpdater; @@ -64,6 +67,11 @@ public class AcceptanceTests { pomVersionIsEqualTo(project, "1.2.1.BUILD-SNAPSHOT"); pomParentVersionIsEqualTo(project, "1.2.1.BUILD-SNAPSHOT"); then(this.gitUpdater.executed).isTrue(); + then(emailTemplate()).exists(); + String emailTemplateContents = emailTemplateContents(); + then(emailTemplateContents) + .contains("Spring Cloud Camden.SR5 available") + .contains("Spring Cloud Camden SR5 Train release"); } @Test @@ -85,6 +93,11 @@ public class AcceptanceTests { commitIsPresent(iterator, "Update SNAPSHOT to 1.2.0.RC1"); pomVersionIsEqualTo(project, "1.2.0.BUILD-SNAPSHOT"); pomParentVersionIsEqualTo(project, "1.2.0.BUILD-SNAPSHOT"); + then(emailTemplate()).exists(); + String emailTemplateContents = emailTemplateContents(); + then(emailTemplateContents) + .contains("Spring Cloud Dalston.RC1 available") + .contains("Spring Cloud Dalston RC1 Train release"); then(this.gitUpdater.executed).isTrue(); } @@ -124,13 +137,23 @@ public class AcceptanceTests { return this.testPomReader.readPom(new File(dir, "pom.xml")); } + private File emailTemplate() throws URISyntaxException { + return new File("target/email.txt"); + } + + private String emailTemplateContents() throws URISyntaxException, IOException { + return new String(Files.readAllBytes(emailTemplate().toPath())); + } + private SpringReleaser releaser(File projectFile, String branch, String expectedVersion) throws Exception { ReleaserProperties properties = releaserProperties(projectFile, branch); ProjectPomUpdater pomUpdater = new ProjectPomUpdater(properties); ProjectBuilder projectBuilder = new ProjectBuilder(properties, pomUpdater); TestProjectGitUpdater gitUpdater = new TestProjectGitUpdater(properties, expectedVersion); - Releaser releaser = new Releaser(pomUpdater, projectBuilder, gitUpdater); + TemplateGenerator templateGenerator = new TemplateGenerator(properties); + Releaser releaser = new Releaser(pomUpdater, projectBuilder, gitUpdater, + templateGenerator); this.gitUpdater = gitUpdater; return new SpringReleaser(releaser, properties) { @Override boolean skipStep() {