Added integration with sagan and an option to update the sagan adoc parts of a project page (#121)

This commit is contained in:
Marcin Grzejszczak
2019-03-04 11:40:05 +01:00
committed by GitHub
parent f0a2ba5a00
commit 9e87362a83
11 changed files with 338 additions and 30 deletions

View File

@@ -281,7 +281,8 @@ public class Releaser {
String currentBranch = this.projectGitHandler.currentBranch(project);
ProjectVersion originalVersion = new ProjectVersion(project);
try {
this.saganUpdater.updateSagan(currentBranch, originalVersion, releaseVersion);
this.saganUpdater.updateSagan(project, currentBranch, originalVersion,
releaseVersion);
log.info("\nSuccessfully updated Sagan for branch [{}]", currentBranch);
}
catch (Exception ex) {

View File

@@ -889,6 +889,23 @@ public class ReleaserProperties implements Serializable {
*/
private String baseUrl = "https://spring.io";
/**
* Folder with asciidoctor files for docs.
*/
private String docsAdocsFile = "docs/src/main/asciidoc";
/**
* Name of the ascii doc file with core part of this project's Sagan project page.
* Linked with {@link this#docsAdocsFile}.
*/
private String indexSectionFileName = "sagan-index.adoc";
/**
* Name of the ascii doc file with boot part of this project's Sagan project page.
* Linked with {@link this#docsAdocsFile}.
*/
private String bootSectionFileName = "sagan-boot.adoc";
public String getBaseUrl() {
return this.baseUrl;
}
@@ -905,6 +922,30 @@ public class ReleaserProperties implements Serializable {
this.updateSagan = updateSagan;
}
public String getDocsAdocsFile() {
return this.docsAdocsFile;
}
public void setDocsAdocsFile(String docsAdocsFile) {
this.docsAdocsFile = docsAdocsFile;
}
public String getIndexSectionFileName() {
return this.indexSectionFileName;
}
public void setIndexSectionFileName(String indexSectionFileName) {
this.indexSectionFileName = indexSectionFileName;
}
public String getBootSectionFileName() {
return this.bootSectionFileName;
}
public void setBootSectionFileName(String bootSectionFileName) {
this.bootSectionFileName = bootSectionFileName;
}
@Override
public String toString() {
return "Sagan{" + "baseUrl='" + this.baseUrl + '\'' + '}';

View File

@@ -49,7 +49,7 @@ import org.springframework.util.StringUtils;
*
* @author Marcin Grzejszczak
*/
public class ProjectVersion {
public class ProjectVersion implements Comparable<ProjectVersion> {
private static final Pattern SNAPSHOT_PATTERN = Pattern
.compile("^.*\\.(BUILD-)?SNAPSHOT.*$");
@@ -244,7 +244,8 @@ public class ProjectVersion {
if (nameComparison != 0) {
return nameComparison;
}
return new VersionNumber(thisValue).compareTo(new VersionNumber(thatValue));
return new TrainVersionNumber(thisValue)
.compareTo(new TrainVersionNumber(thatValue));
}
@Override
@@ -281,18 +282,24 @@ public class ProjectVersion {
Pattern.compile(RC_REGEX));
}
@Override
public int compareTo(ProjectVersion o) {
// very simple comparison
return this.version.compareTo(o.version);
}
}
class VersionNumber implements Comparable<VersionNumber> {
class TrainVersionNumber implements Comparable<TrainVersionNumber> {
private final String version;
VersionNumber(String version) {
TrainVersionNumber(String version) {
this.version = version;
}
@Override
public int compareTo(VersionNumber o) {
public int compareTo(TrainVersionNumber o) {
String thisLower = this.version.toLowerCase();
char thisFirst = thisLower.isEmpty() ? ' ' : thisLower.charAt(0);
String thatLower = o.version.toLowerCase();

View File

@@ -43,28 +43,38 @@ import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Project {
public String id = "";
public String id;
public String name = "";
public String name;
public String repoUrl = "";
public String repoUrl;
public String siteUrl = "";
public String siteUrl;
public String category = "";
public String category;
public String stackOverflowTags;
public List<Release> projectReleases = new ArrayList<>();
public List<String> projectSamples = new ArrayList<>();
public List<Release> nonMostCurrentReleases = new ArrayList<>();
public List<String> stackOverflowTagList = new ArrayList<>();
public MostCurrentRelease mostCurrentRelease = new MostCurrentRelease();
public Boolean aggregator;
public String rawBootConfig;
public String rawOverview;
public int displayOrder = Integer.MAX_VALUE;
public boolean topLevelProject = true;
@Override
public String toString() {
return "Project{" + "id='" + this.id + '\'' + ", name='" + this.name + '\''
@@ -77,4 +87,10 @@ public class Project {
+ this.rawOverview + '\'' + '}';
}
static class MostCurrentRelease {
public boolean present;
}
}

View File

@@ -106,16 +106,16 @@ class RestTemplateSaganClient implements SaganClient {
}
@Override
public void patchProject(Project project) {
public Project patchProject(Project project) {
RequestEntity<Project> request = RequestEntity
.patch(URI
.create(this.baseUrl + "/project_metadata/" + project.name + "/"))
.patch(URI.create(this.baseUrl + "/project_metadata/" + project.id))
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
.body(project);
ResponseEntity<Project> entity = this.restTemplate.exchange(request,
Project.class);
Project updatedProject = entity.getBody();
log.info("Response from Sagan\n\n[{}] \n with body [{}]", entity, updatedProject);
return updatedProject;
}
}

View File

@@ -47,6 +47,6 @@ public interface SaganClient {
Project updateRelease(String projectName, List<ReleaseUpdate> releaseUpdate);
void patchProject(Project project);
Project patchProject(Project project);
}

View File

@@ -32,6 +32,11 @@
package org.springframework.cloud.release.internal.sagan;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Comparator;
import java.util.Optional;
import java.util.stream.Collectors;
import edu.emory.mathcs.backport.java.util.Collections;
@@ -57,18 +62,97 @@ public class SaganUpdater {
this.releaserProperties = releaserProperties;
}
public void updateSagan(String branch, ProjectVersion originalVersion,
ProjectVersion version) {
public void updateSagan(File projectFile, String branch,
ProjectVersion originalVersion, ProjectVersion currentVersion) {
if (!this.releaserProperties.getSagan().isUpdateSagan()) {
log.info("Will not update sagan, since the switch to do so "
+ "is off. Set [releaser.sagan.update-sagan] to [true] to change that");
return;
}
ReleaseUpdate update = releaseUpdate(branch, originalVersion, version);
updateSaganForNonSnapshot(branch, originalVersion, version);
log.info("Updating Sagan with \n\n{}", update);
this.saganClient.updateRelease(version.projectName,
ReleaseUpdate update = releaseUpdate(branch, originalVersion, currentVersion);
updateSaganForNonSnapshot(branch, originalVersion, currentVersion);
log.info("Updating Sagan releases with \n\n{}", update);
Project project = this.saganClient.updateRelease(currentVersion.projectName,
Collections.singletonList(update));
Optional<ProjectVersion> projectVersion = latestVersion(currentVersion, project);
log.info("Found the following latest project version [{}]", projectVersion);
boolean present = projectVersion.isPresent();
if (present && currentVersionNewerOrEqual(currentVersion, projectVersion)) {
updateDocumentationIfNecessary(projectFile, project);
}
else {
log.info(present
? "Latest version [" + projectVersion.get() + "] present and "
+ "the current version [" + currentVersion
+ "] is older than that one. " + "Will do nothing."
: "No latest version found. Will do nothing.");
}
}
private void updateDocumentationIfNecessary(File projectFile, Project project) {
boolean shouldUpdate = false;
File docsModule = docsModule(projectFile);
File indexDoc = new File(docsModule,
this.releaserProperties.getSagan().getIndexSectionFileName());
File bootDoc = new File(docsModule,
this.releaserProperties.getSagan().getBootSectionFileName());
if (indexDoc.exists()) {
log.debug("Index adoc file exists");
String fileText = fileToText(indexDoc);
if (!fileText.equals(project.rawOverview)) {
log.info(
"Index adoc content differs from the previously stored, will update it");
project.rawOverview = fileText;
shouldUpdate = true;
}
}
if (bootDoc.exists()) {
log.debug("Boot adoc file exists");
String fileText = fileToText(bootDoc);
if (!fileText.equals(project.rawBootConfig)) {
log.info(
"Boot adoc content differs from the previously stored, will update it");
project.rawBootConfig = fileText;
shouldUpdate = true;
}
}
if (shouldUpdate) {
this.saganClient.patchProject(project);
log.info("Updating Sagan project with adoc data.");
}
else {
log.info("Nothing changed in project's meta-data. Won't change anything.");
}
}
File docsModule(File projectFile) {
return new File(projectFile,
this.releaserProperties.getSagan().getDocsAdocsFile());
}
private String fileToText(File file) {
try {
return new String(Files.readAllBytes(file.toPath()));
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
private Optional<ProjectVersion> latestVersion(ProjectVersion currentVersion,
Project project) {
if (project == null) {
return Optional.empty();
}
return project.projectReleases.stream().filter(release -> release.current)
.map(release -> new ProjectVersion(currentVersion.projectName,
release.version))
.max(Comparator.comparing(o -> o.version));
}
private boolean currentVersionNewerOrEqual(ProjectVersion currentVersion,
Optional<ProjectVersion> projectVersion) {
return currentVersion.compareTo(projectVersion.get()) >= 0;
}
private void updateSaganForNonSnapshot(String branch, ProjectVersion originalVersion,

View File

@@ -305,6 +305,32 @@ public class ProjectVersionTests {
then(projectVersion(thisVersion).isSameMinor(thatVersion)).isFalse();
}
@Test
public void should_return_equal_when_versions_are_the_same() {
String thisVersion = "1.3.1.SR3";
String thatVersion = "1.3.1.SR3";
then(projectVersion(thisVersion).compareTo(projectVersion(thatVersion))).isZero();
}
@Test
public void should_return_greater_when_versions_this_version_is_greater_than_the_other() {
String thisVersion = "1.3.2.SR3";
String thatVersion = "1.3.1.SR3";
then(projectVersion(thisVersion).compareTo(projectVersion(thatVersion)))
.isPositive();
}
@Test
public void should_return_lower_when_versions_this_version_is_lower_than_the_other() {
String thisVersion = "1.3.0.RC3";
String thatVersion = "1.3.1.RC3";
then(projectVersion(thisVersion).compareTo(projectVersion(thatVersion)))
.isNegative();
}
@Test
public void should_return_empty_group_id_when_it_is_missing() {
ProjectVersion projectVersion = projectVersion("1.0.0.RC1");

View File

@@ -16,14 +16,17 @@
package org.springframework.cloud.release.internal.sagan;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
@@ -47,6 +50,9 @@ public class RestTemplateSaganClientTests {
@Value("${stubrunner.runningstubs.sagan-site.port}")
Integer saganPort;
@Autowired
ObjectMapper objectMapper;
SaganClient client;
@Before
@@ -190,6 +196,23 @@ public class RestTemplateSaganClientTests {
then(project.projectReleases).hasSize(7);
}
@Test
public void should_patch_a_project() throws IOException {
String projectJson = "{\n \"id\" : \"spring-framework\",\n "
+ "\"rawBootConfig\" : \"rawBootConfig\",\n \"rawOverview\" : \"rawOverview\",\n "
+ "\"displayOrder\" : 2147483647,\n \"projectReleases\" : [ ],"
+ "\n \"projectSamples\" : [ ],\n \"mostCurrentRelease\" : {\n \"present\" : false\n },"
+ "\n \"nonMostCurrentReleases\" : [ ],\n \"stackOverflowTagList\" : [ ],\n \"topLevelProject\" : true\n}";
Project project = this.objectMapper.readValue(projectJson, Project.class);
Project patchedProject = this.client.patchProject(project);
then(patchedProject.id).isEqualTo("spring-framework");
then(patchedProject.name).isEqualTo("Spring Framework");
then(patchedProject.rawBootConfig).isEqualTo("rawBootConfig");
then(patchedProject.rawOverview).isEqualTo("rawOverview");
}
private Repository milestone() {
Repository milestone = new Repository();
milestone.id = "spring-milestones";

View File

@@ -16,7 +16,12 @@
package org.springframework.cloud.release.internal.sagan;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
@@ -29,6 +34,7 @@ import org.springframework.cloud.release.internal.ReleaserProperties;
import org.springframework.cloud.release.internal.pom.ProjectVersion;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.never;
@@ -62,14 +68,16 @@ public class SaganUpdaterTest {
public void should_not_update_sagan_when_switch_is_off() {
this.properties.getSagan().setUpdateSagan(false);
this.saganUpdater.updateSagan("master", version("1.0.0.M1"), version("1.0.0.M1"));
this.saganUpdater.updateSagan(new File("."), "master", version("1.0.0.M1"),
version("1.0.0.M1"));
then(this.saganClient).shouldHaveZeroInteractions();
}
@Test
public void should_update_sagan_for_milestone() {
this.saganUpdater.updateSagan("master", version("1.0.0.M1"), version("1.0.0.M1"));
public void should_update_sagan_releases_for_milestone() {
this.saganUpdater.updateSagan(new File("."), "master", version("1.0.0.M1"),
version("1.0.0.M1"));
then(this.saganClient).should().updateRelease(BDDMockito.eq("foo"),
BDDMockito.argThat(withReleaseUpdate("1.0.0.M1",
@@ -78,8 +86,8 @@ public class SaganUpdaterTest {
}
@Test
public void should_update_sagan_for_rc() {
this.saganUpdater.updateSagan("master", version("1.0.0.RC1"),
public void should_update_sagan_releases_for_rc() {
this.saganUpdater.updateSagan(new File("."), "master", version("1.0.0.RC1"),
version("1.0.0.RC1"));
then(this.saganClient).should().updateRelease(BDDMockito.eq("foo"),
@@ -88,6 +96,104 @@ public class SaganUpdaterTest {
"PRERELEASE")));
}
@Test
public void should_not_update_docs_for_sagan_when_current_version_older() {
given(this.saganClient.updateRelease(BDDMockito.anyString(),
BDDMockito.anyList())).willReturn(a2_0_0_ReleaseProject());
this.saganUpdater.updateSagan(new File("."), "master", version("1.0.0.RC1"),
version("1.0.0.RC1"));
then(this.saganClient).should(BDDMockito.never())
.patchProject(BDDMockito.any(Project.class));
}
private Project a2_0_0_ReleaseProject() {
Project project = new Project();
Release release = new Release();
release.version = "2.0.0.RELEASE";
release.current = true;
project.projectReleases = Collections.singletonList(release);
return project;
}
@Test
public void should_not_update_docs_for_sagan_when_files_exist_but_content_does_not_differ()
throws IOException {
Project project = a2_0_0_ReleaseProject();
project.rawOverview = "new overview";
project.rawBootConfig = "new boot";
given(this.saganClient.updateRelease(BDDMockito.anyString(),
BDDMockito.anyList())).willReturn(project);
Path tmp = Files.createTempDirectory("releaser-test");
createFile(tmp, "sagan-index.adoc", "new overview");
createFile(tmp, "sagan-boot.adoc", "new boot");
SaganUpdater saganUpdater = new SaganUpdater(this.saganClient, this.properties) {
@Override
File docsModule(File projectFile) {
return tmp.toFile();
}
};
saganUpdater.updateSagan(new File("."), "master", version("3.0.0.RC1"),
version("3.0.0.RC1"));
then(this.saganClient).should(BDDMockito.never())
.patchProject(BDDMockito.any(Project.class));
}
@Test
public void should_update_docs_for_sagan_when_current_version_newer_and_only_overview_adoc_exists()
throws IOException {
given(this.saganClient.updateRelease(BDDMockito.anyString(),
BDDMockito.anyList())).willReturn(a2_0_0_ReleaseProject());
Path tmp = Files.createTempDirectory("releaser-test");
createFile(tmp, "sagan-index.adoc", "new text");
SaganUpdater saganUpdater = new SaganUpdater(this.saganClient, this.properties) {
@Override
File docsModule(File projectFile) {
return tmp.toFile();
}
};
saganUpdater.updateSagan(new File("."), "master", version("3.0.0.RC1"),
version("3.0.0.RC1"));
then(this.saganClient).should().patchProject(
BDDMockito.argThat(argument -> "new text".equals(argument.rawOverview)));
}
@Test
public void should_update_docs_for_sagan_when_current_version_newer_and_only_boot_adoc_exists()
throws IOException {
given(this.saganClient.updateRelease(BDDMockito.anyString(),
BDDMockito.anyList())).willReturn(a2_0_0_ReleaseProject());
Path tmp = Files.createTempDirectory("releaser-test");
createFile(tmp, "sagan-boot.adoc", "new text");
SaganUpdater saganUpdater = new SaganUpdater(this.saganClient, this.properties) {
@Override
File docsModule(File projectFile) {
return tmp.toFile();
}
};
saganUpdater.updateSagan(new File("."), "master", version("3.0.0.RC1"),
version("3.0.0.RC1"));
then(this.saganClient).should().patchProject(BDDMockito
.argThat(argument -> "new text".equals(argument.rawBootConfig)));
}
private void createFile(Path tmp, String filename, String text) throws IOException {
File overviewAdoc = new File(tmp.toString(), filename);
overviewAdoc.createNewFile();
Files.write(overviewAdoc.toPath(), text.getBytes());
}
private ProjectVersion version(String version) {
return new ProjectVersion("foo", version);
}
@@ -96,7 +202,8 @@ public class SaganUpdaterTest {
public void should_update_sagan_from_master() {
ProjectVersion projectVersion = version("1.0.0.BUILD-SNAPSHOT");
this.saganUpdater.updateSagan("master", projectVersion, projectVersion);
this.saganUpdater.updateSagan(new File("."), "master", projectVersion,
projectVersion);
then(this.saganClient).should().updateRelease(BDDMockito.eq("foo"),
BDDMockito.argThat(withReleaseUpdate("1.0.0.BUILD-SNAPSHOT",
@@ -107,7 +214,8 @@ public class SaganUpdaterTest {
public void should_update_sagan_from_release_version() {
ProjectVersion projectVersion = version("1.0.0.RELEASE");
this.saganUpdater.updateSagan("master", projectVersion, projectVersion);
this.saganUpdater.updateSagan(new File("."), "master", projectVersion,
projectVersion);
then(this.saganClient).should().deleteRelease("foo", "1.0.0.RC1");
then(this.saganClient).should().deleteRelease("foo", "1.0.0.BUILD-SNAPSHOT");
@@ -125,7 +233,8 @@ public class SaganUpdaterTest {
public void should_update_sagan_from_non_master() {
ProjectVersion projectVersion = version("1.1.0.BUILD-SNAPSHOT");
this.saganUpdater.updateSagan("1.1.x", projectVersion, projectVersion);
this.saganUpdater.updateSagan(new File("."), "1.1.x", projectVersion,
projectVersion);
then(this.saganClient).should(never()).deleteRelease(anyString(), anyString());
then(this.saganClient).should().updateRelease(BDDMockito.eq("foo"),

View File

@@ -312,7 +312,8 @@ public class AcceptanceTests {
private void thenSaganWasCalled() {
BDDMockito.then(this.saganUpdater).should(BDDMockito.atLeastOnce()).updateSagan(
BDDMockito.anyString(), BDDMockito.any(ProjectVersion.class),
BDDMockito.any(File.class), BDDMockito.anyString(),
BDDMockito.any(ProjectVersion.class),
BDDMockito.any(ProjectVersion.class));
}