#159 - Polishing.

Encapsulate dependency versions in DependencyVersions value object. Use ModuleIteration instead of TrainIteration+Project.
This commit is contained in:
Mark Paluch
2020-11-23 09:52:06 +01:00
parent 766ffea28a
commit c99d6fcdea
11 changed files with 202 additions and 79 deletions

View File

@@ -32,6 +32,8 @@ import java.util.stream.Collectors;
import org.springframework.data.release.CliComponent;
import org.springframework.data.release.TimedCommand;
import org.springframework.data.release.git.GitOperations;
import org.springframework.data.release.issues.Tickets;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.Projects;
import org.springframework.data.release.model.TrainIteration;
@@ -84,7 +86,7 @@ public class DependencyCommands extends TimedCommand {
Map<Dependency, DependencyVersion> dependencies = new TreeMap<>();
for (Project project : projects) {
dependencies.putAll(operations.getCurrentDependencies(project));
operations.getCurrentDependencies(project).forEach(dependencies::put);
}
StringBuilder report = new StringBuilder();
@@ -103,22 +105,28 @@ public class DependencyCommands extends TimedCommand {
}
@CliCommand(value = "dependency upgrade")
public void check(@CliOption(key = "", mandatory = true) TrainIteration iteration)
public void upgrade(@CliOption(key = "", mandatory = true) TrainIteration iteration)
throws IOException, InterruptedException {
git.checkout(iteration.getTrain());
git.checkout(iteration.getTrain(), false);
logger.log(iteration, "Applying dependency upgrades to Spring Data Build");
Properties properties = loadDependencyUpgrades(iteration);
ModuleIteration module = iteration.getModule(Projects.BUILD);
Map<Dependency, DependencyVersion> dependencyUpgrades = DependencyUpgradeProposals.fromProperties(iteration,
properties);
DependencyVersions dependencyVersions = loadDependencyUpgrades(module);
operations.createUpgradeTickets(iteration, Projects.BUILD, dependencyUpgrades);
operations.upgradeDependencies(iteration, Projects.BUILD, dependencyUpgrades);
operations.createUpgradeTickets(module, dependencyVersions);
Tickets tickets = operations.upgradeDependencies(module, dependencyVersions);
git.push(module);
// Allow GitHub to catch up with ticket notifications.
Thread.sleep(1500);
operations.closeUpgradeTickets(module, tickets);
}
protected Properties loadDependencyUpgrades(@CliOption(key = "", mandatory = true) TrainIteration iteration)
private DependencyVersions loadDependencyUpgrades(ModuleIteration iteration)
throws IOException {
if (!Files.exists(Paths.get(BUILD_PROPERTIES))) {
@@ -129,7 +137,8 @@ public class DependencyCommands extends TimedCommand {
try (FileInputStream fis = new FileInputStream(BUILD_PROPERTIES)) {
properties.load(fis);
}
return properties;
return DependencyUpgradeProposals.fromProperties(iteration.getTrainIteration(), properties);
}
private void checkModuleDependencies(TrainIteration iteration, boolean reportAll) throws IOException {

View File

@@ -46,7 +46,6 @@ import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.ModuleIteration;
import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.Projects;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.utils.ExecutionUtils;
import org.springframework.data.release.utils.Logger;
import org.springframework.data.util.Streamable;
@@ -92,10 +91,10 @@ public class DependencyOperations {
*/
public DependencyUpgradeProposals getDependencyUpgradeProposals(Project project, Iteration iteration) {
Map<Dependency, DependencyVersion> currentDependencies = getCurrentDependencies(project);
DependencyVersions currentDependencies = getCurrentDependencies(project);
Map<Dependency, DependencyUpgradeProposal> proposals = Collections.synchronizedMap(new LinkedHashMap<>());
ExecutionUtils.run(executor, Streamable.of(currentDependencies.keySet()), dependency -> {
ExecutionUtils.run(executor, Streamable.of(currentDependencies.getDependencies()), dependency -> {
DependencyVersion currentVersion = currentDependencies.get(dependency);
List<DependencyVersion> versions = getAvailableVersions(dependency);
@@ -110,17 +109,16 @@ public class DependencyOperations {
/**
* Ensures there's a upgrade ticket for each dependency to upgrade.
*
* @param iteration
* @param project
* @param module
* @param dependencyVersions
*/
public void createUpgradeTickets(TrainIteration iteration, Project project,
Map<Dependency, DependencyVersion> dependencyVersions) {
public void createUpgradeTickets(ModuleIteration module, DependencyVersions dependencyVersions) {
Map<Dependency, DependencyVersion> upgrades = getDependencyUpgradesToApply(project, dependencyVersions);
Project project = module.getProject();
DependencyVersions upgrades = getDependencyUpgradesToApply(project, dependencyVersions);
IssueTracker tracker = this.tracker.getRequiredPluginFor(project);
Tickets tickets = tracker.getTicketsFor(iteration);
Tickets tickets = tracker.getTicketsFor(module);
upgrades.forEach((dependency, dependencyVersion) -> {
@@ -131,42 +129,40 @@ public class DependencyOperations {
logger.log(project, "Found upgrade ticket %s", upgradeTicket.get());
} else {
ModuleIteration module = iteration.getModule(project);
logger.log(module, "Creating upgrade ticket for %s", upgradeTicketSummary);
tracker.createTicket(module, upgradeTicketSummary, IssueTracker.TicketType.DependencyUpgrade);
}
});
// flush cache
tracker.reset();
}
/**
* Verifies dependencies to upgrade, applies the upgrade, creates a commit, pushes the repository and resolves the
* upgrade ticket.
* Verifies dependencies to upgrade, applies the upgrade, creates a commit.
*
* @param iteration
* @param project
* @param module
* @param dependencyVersions
* @throws InterruptedException
*/
public void upgradeDependencies(TrainIteration iteration, Project project,
Map<Dependency, DependencyVersion> dependencyVersions) throws InterruptedException {
public Tickets upgradeDependencies(ModuleIteration module, DependencyVersions dependencyVersions) {
Map<Dependency, DependencyVersion> upgrades = getDependencyUpgradesToApply(project, dependencyVersions);
Project project = module.getProject();
DependencyVersions upgrades = getDependencyUpgradesToApply(project, dependencyVersions);
ProjectDependencies dependencies = ProjectDependencies.get(project);
ModuleIteration module = iteration.getModule(project);
if (upgrades.isEmpty()) {
logger.log(module, "No dependency upgrades to apply");
}
IssueTracker tracker = this.tracker.getRequiredPluginFor(project);
Tickets tickets = tracker.getTicketsFor(iteration);
Tickets tickets = tracker.getTicketsFor(module);
List<Ticket> ticketsToClose = new ArrayList<>();
upgrades.forEach((dependency, dependencyVersion) -> {
String upgradeTicketSummary = getUpgradeTicketSummary(dependency, dependencyVersion);
Ticket upgradeTicket = getDependencyUpgradeTicket(tickets, upgradeTicketSummary).get();
tracker.assignTicketToMe(project, upgradeTicket);
String versionProperty = dependencies.getVersionPropertyFor(dependency);
File pom = getPomFile(project);
@@ -174,35 +170,36 @@ public class DependencyOperations {
it.setProperty(versionProperty, dependencyVersion.getIdentifier());
});
gitOperations.commit(module, upgradeTicket, upgradeTicketSummary, Optional.empty(), pom);
gitOperations.commit(module, upgradeTicket, upgradeTicketSummary, Optional.empty());
ticketsToClose.add(upgradeTicket);
});
gitOperations.push(module);
return new Tickets(ticketsToClose);
}
// Allow GitHub to catch up with ticket notifications.
Thread.sleep(1500);
public void closeUpgradeTickets(ModuleIteration module, Tickets tickets) {
for (Ticket ticket : ticketsToClose) {
IssueTracker tracker = this.tracker.getRequiredPluginFor(module.getProject());
for (Ticket ticket : tickets) {
tracker.closeTicket(module, ticket);
}
}
private Map<Dependency, DependencyVersion> getDependencyUpgradesToApply(Project project,
Map<Dependency, DependencyVersion> dependencyVersions) {
private DependencyVersions getDependencyUpgradesToApply(Project project, DependencyVersions dependencyVersions) {
Map<Dependency, DependencyVersion> currentDependencies = getCurrentDependencies(project);
DependencyVersions currentDependencies = getCurrentDependencies(project);
Map<Dependency, DependencyVersion> upgrades = new LinkedHashMap<>();
currentDependencies.forEach((dependency, dependencyVersion) -> {
DependencyVersion upgradeVersion = dependencyVersions.get(dependency);
if (upgradeVersion == null) {
if (!dependencyVersions.hasDependency(dependency)) {
return;
}
DependencyVersion upgradeVersion = dependencyVersions.get(dependency);
if (upgradeVersion.equals(dependencyVersion)) {
logger.log(project, "Skipping upgrade of %s (%s)", dependency.getName(), dependencyVersion.getIdentifier());
return;
@@ -211,7 +208,7 @@ public class DependencyOperations {
upgrades.put(dependency, upgradeVersion);
});
return upgrades;
return new DependencyVersions(upgrades);
}
private Optional<Ticket> getDependencyUpgradeTicket(Tickets tickets, String upgradeTicketSummary) {
@@ -281,10 +278,10 @@ public class DependencyOperations {
() -> new IllegalArgumentException("Cannot determine new minor version from " + availableVersions));
}
Map<Dependency, DependencyVersion> getCurrentDependencies(Project project) {
DependencyVersions getCurrentDependencies(Project project) {
if (!ProjectDependencies.containsProject(project)) {
return Collections.emptyMap();
return DependencyVersions.empty();
}
File pom = getPomFile(project);
@@ -314,7 +311,7 @@ public class DependencyOperations {
}
}
return versions;
return new DependencyVersions(versions);
});
}

View File

@@ -134,7 +134,7 @@ public class DependencyUpgradeProposals {
* @param properties
* @return
*/
public static Map<Dependency, DependencyVersion> fromProperties(TrainIteration iteration, Properties properties) {
public static DependencyVersions fromProperties(TrainIteration iteration, Properties properties) {
Pattern keyPattern = Pattern.compile("dependency\\[([a-zA-Z0-9\\-\\.]+):([a-zA-Z0-9\\-\\.]+)\\]");
@@ -169,7 +169,7 @@ public class DependencyUpgradeProposals {
});
return result;
return new DependencyVersions(result);
}
}

View File

@@ -27,6 +27,11 @@ import java.util.regex.Pattern;
import org.springframework.data.release.model.Version;
/**
* Value object representing a dependency version. The primary identifier is {@link #identifier} that corresponds with
* the actual version identifier. Version identifiers are attempted to be parsed into either a version following Spring
* Version or SemVer rules ({@code 1.2.3.RELEASE}, {@code 1.2.3-rc1}) or train name with counter rules
* ({@code Foo-RELEASE}, {@code Foo-SR1}).
*
* @author Mark Paluch
*/
@Value

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.release.dependency;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.function.BiConsumer;
/**
* Value upgrade capturing {@link DependencyVersion} for a {@link Dependency}.
*
* @author Mark Paluch
*/
@Value
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
class DependencyVersions {
@Getter Map<Dependency, DependencyVersion> versions;
public static DependencyVersions empty() {
return new DependencyVersions(Collections.emptyMap());
}
/**
* Returns whether there's a version specified for {@link Dependency}.
*
* @param dependency
* @return
*/
public boolean hasDependency(Dependency dependency) {
return versions.containsKey(dependency);
}
/**
* Returns the {@link DependencyVersion} for {@link Dependency} or throws {@link IllegalArgumentException} if no
* version was specified. It's recommended to check whether a version has been specified using
* {@link #hasDependency(Dependency)}.
*
* @param dependency
* @return
* @see #hasDependency(Dependency)
*/
public DependencyVersion get(Dependency dependency) {
if (!hasDependency(dependency)) {
throw new IllegalArgumentException(String.format("No such dependency: %s", dependency));
}
return versions.get(dependency);
}
/**
* Apply the {@code action} to all dependeny/dependency version pairs.
*
* @param action
*/
public void forEach(BiConsumer<Dependency, DependencyVersion> action) {
versions.forEach(action);
}
/**
* Returns whether dependency versions are configured.
*
* @return
*/
public boolean isEmpty() {
return versions.isEmpty();
}
public Collection<Dependency> getDependencies() {
return versions.keySet();
}
}

View File

@@ -39,13 +39,21 @@ public interface IssueTracker extends Plugin<Project> {
void reset();
/**
* Returns all {@link Tickets} for the given {@link Train} and {@link Iteration}.
* Returns all {@link Tickets} for the given {@link TrainIteration}.
*
* @param iteration must not be {@literal null}.
* @return
*/
Tickets getTicketsFor(TrainIteration iteration);
/**
* Returns all {@link Tickets} for the given {@link ModuleIteration}.
*
* @param iteration must not be {@literal null}.
* @return
*/
Tickets getTicketsFor(ModuleIteration iteration);
/**
* Returns all {@link Tickets} for the given {@link Train} and {@link Iteration}.
*
@@ -109,9 +117,10 @@ public interface IssueTracker extends Plugin<Project> {
/**
* Assigns the ticket to the current user.
*
* @param project must not be {@literal null}.
* @param ticket must not be {@literal null}.
*/
void assignTicketToMe(Ticket ticket);
Ticket assignTicketToMe(Project project, Ticket ticket);
/**
* Assigns the release ticket for the given {@link ModuleIteration} to the current user.

View File

@@ -290,8 +290,22 @@ class GitHub extends GitHubSupport implements IssueTracker {
* @see org.springframework.data.release.jira.IssueTracker#assignTicketToMe(org.springframework.data.release.jira.Ticket)
*/
@Override
public void assignTicketToMe(Ticket ticket) {
logger.log("Ticket", "Skipping ticket assignment for %s", ticket);
public Ticket assignTicketToMe(Project project, Ticket ticket) {
Assert.notNull(ticket, "Ticket must not be null.");
String repositoryName = GitProject.of(project).getRepositoryName();
Map<String, Object> parameters = newUrlTemplateVariables();
parameters.put("repoName", repositoryName);
parameters.put("id", stripHash(ticket));
GitHubWriteIssue edit = GitHubWriteIssue.assignedTo(properties.getUsername());
GitHubReadIssue response = operations.exchange(ISSUE_BY_ID_URI_TEMPLATE, HttpMethod.PATCH,
new HttpEntity<>(edit, new HttpHeaders()), ISSUE_TYPE, parameters).getBody();
return toTicket(response);
}
/*
@@ -303,19 +317,7 @@ class GitHub extends GitHubSupport implements IssueTracker {
Assert.notNull(module, "ModuleIteration must not be null.");
Ticket releaseTicketFor = getReleaseTicketFor(module);
String repositoryName = GitProject.of(module.getProject()).getRepositoryName();
Map<String, Object> parameters = newUrlTemplateVariables();
parameters.put("repoName", repositoryName);
parameters.put("id", stripHash(releaseTicketFor));
GitHubWriteIssue edit = GitHubWriteIssue.assignedTo(properties.getUsername());
GitHubReadIssue response = operations.exchange(ISSUE_BY_ID_URI_TEMPLATE, HttpMethod.PATCH,
new HttpEntity<>(edit, new HttpHeaders()), ISSUE_TYPE, parameters).getBody();
return toTicket(response);
return assignTicketToMe(module.getProject(), getReleaseTicketFor(module));
}
/*

View File

@@ -351,8 +351,9 @@ class Jira implements JiraConnector {
* @see org.springframework.data.release.jira.JiraConnector#assignTicketToMe(org.springframework.data.release.jira.Ticket)
*/
@Override
public void assignTicketToMe(Ticket ticket) {
public Ticket assignTicketToMe(Project project, Ticket ticket) {
Assert.notNull(project, "Project must not be null.");
Assert.notNull(ticket, "Ticket must not be null.");
HttpHeaders httpHeaders = new HttpHeaders();
@@ -365,7 +366,7 @@ class Jira implements JiraConnector {
if (currentIssue.isAssignedTo(jiraProperties.getUsername())) {
logger.log("Ticket", "Skipping self-assignment of %s", ticket);
return;
return ticket;
}
JiraIssueUpdate editMeta = JiraIssueUpdate.create().assignTo(jiraProperties.getUsername());
@@ -379,6 +380,8 @@ class Jira implements JiraConnector {
logger.warn("Ticket", "Self-assignment of %s failed with status %s (%s)", ticket, e.getStatusCode(),
e.getResponseBodyAsString());
}
return ticket;
}
/*
@@ -389,7 +392,7 @@ class Jira implements JiraConnector {
public Ticket assignReleaseTicketToMe(ModuleIteration module) {
Ticket ticket = getReleaseTicketFor(module);
assignTicketToMe(ticket);
assignTicketToMe(module.getProject(), ticket);
return ticket;
}

View File

@@ -23,6 +23,7 @@ import java.net.URL;
import java.net.URLConnection;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -31,6 +32,7 @@ import org.springframework.data.release.AbstractIntegrationTests;
import org.springframework.data.release.git.GitOperations;
import org.springframework.data.release.model.Iteration;
import org.springframework.data.release.model.Projects;
import org.springframework.data.release.model.ReleaseTrains;
/**
* Integration tests for {@link DependencyOperations}.
@@ -55,6 +57,11 @@ class DependencyOperationsIntegrationTests extends AbstractIntegrationTests {
}
}
@BeforeEach
void setUp() {
git.checkout(ReleaseTrains.MOORE);
}
@Test
void shouldDiscoverDependencyVersions() {
assertThat(operations.getAvailableVersions(Dependencies.PROJECT_REACTOR)).isNotEmpty();
@@ -62,17 +69,15 @@ class DependencyOperationsIntegrationTests extends AbstractIntegrationTests {
@Test
void shouldReportExistingDependencyVersions() {
assertThat(operations.getCurrentDependencies(Projects.BUILD)).isNotEmpty();
assertThat(operations.getCurrentDependencies(Projects.BUILD).isEmpty()).isFalse();
}
@Test
void shouldReportExistingOptionalDependencies() {
// git.checkout(ReleaseTrains.MOORE);
assertThat(operations.getCurrentDependencies(Projects.CASSANDRA)).hasSize(1);
assertThat(operations.getCurrentDependencies(Projects.MONGO_DB)).hasSize(1);
assertThat(operations.getCurrentDependencies(Projects.NEO4J)).hasSize(1);
assertThat(operations.getCurrentDependencies(Projects.CASSANDRA).getVersions()).hasSize(1);
assertThat(operations.getCurrentDependencies(Projects.MONGO_DB).getVersions()).hasSize(2);
assertThat(operations.getCurrentDependencies(Projects.NEO4J).getVersions()).hasSize(1);
}
@Test

View File

@@ -17,7 +17,6 @@ package org.springframework.data.release.dependency;
import static org.assertj.core.api.Assertions.*;
import java.util.Map;
import java.util.Properties;
import org.junit.jupiter.api.Test;
@@ -40,9 +39,10 @@ class DependencyUpgradeProposalsUnitTests {
properties.put("dependency.iteration", "M1");
properties.put("dependency[org.assertj:assertj-core]", "3.18.1");
Map<Dependency, DependencyVersion> dependencies = DependencyUpgradeProposals
DependencyVersions dependencies = DependencyUpgradeProposals
.fromProperties(ReleaseTrains.PASCAL.getIteration(Iteration.M1), properties);
assertThat(dependencies).hasSize(1).containsEntry(Dependencies.ASSERTJ, DependencyVersion.of("3.18.1"));
assertThat(dependencies.getVersions()).hasSize(1).containsEntry(Dependencies.ASSERTJ,
DependencyVersion.of("3.18.1"));
}
}

View File

@@ -251,7 +251,7 @@ class JiraConnectorIntegrationTests extends AbstractIntegrationTests {
mockService.stubFor(put(urlPathMatching("/rest/api/2/issue/DATAREDIS-302")).//
willReturn(aResponse().withStatus(204)));
jira.assignTicketToMe(new Ticket("DATAREDIS-302", "", null, null));
jira.assignTicketToMe(Projects.REDIS, new Ticket("DATAREDIS-302", "", null, null));
verify(putRequestedFor(urlPathMatching("/rest/api/2/issue/DATAREDIS-302")).withRequestBody(equalToJson(
"{\"update\":{\"assignee\":[ {\"set\":{\"name\":\"dummy\"}} ] }, \"transition\":{}, \"fields\":{}}")));
@@ -271,7 +271,7 @@ class JiraConnectorIntegrationTests extends AbstractIntegrationTests {
mockService.stubFor(post(urlPathMatching("/rest/api/2/issue/DATACASS-302")).//
willReturn(aResponse().withStatus(204)));
jira.assignTicketToMe(new Ticket("DATACASS-302", "", null, null));
jira.assignTicketToMe(Projects.CASSANDRA, new Ticket("DATACASS-302", "", null, null));
verify(0, postRequestedFor(urlPathMatching("/rest/api/2/issue/DATACASS-302")));
}