From 9bc7f70b5a4ebd880b8c6c990ba06ec194bc1ff4 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 31 Mar 2021 10:47:18 +0200 Subject: [PATCH] Introduce verify command to verify configuration settings. Closes #182. --- release-tools/readme.md | 9 ++- .../data/release/build/BuildOperations.java | 8 ++ .../data/release/build/BuildSystem.java | 5 ++ .../data/release/build/CommandLine.java | 23 +++--- .../data/release/build/MavenBuildSystem.java | 22 ++++++ .../data/release/cli/VerifyCommands.java | 76 +++++++++++++++++++ .../data/release/git/GitOperations.java | 7 ++ .../data/release/issues/github/GitHub.java | 25 +++++- .../data/release/issues/jira/Jira.java | 18 ++++- .../release/sagan/DefaultSaganClient.java | 22 ++++++ .../data/release/sagan/DummySaganClient.java | 9 +++ .../data/release/sagan/SaganClient.java | 16 ++-- 12 files changed, 220 insertions(+), 20 deletions(-) create mode 100644 release-tools/src/main/java/org/springframework/data/release/cli/VerifyCommands.java diff --git a/release-tools/readme.md b/release-tools/readme.md index 6f654b9..6ba2fb4 100644 --- a/release-tools/readme.md +++ b/release-tools/readme.md @@ -19,8 +19,13 @@ Add an `application-local.properties` to the project root and add the following - `deployment.password` - The encrypted Artifactory password.. - `gpg.keyname` - The GPG key name. - `gpg.password` - The password of your GPG key. -- `gpg.executable` - Path to your GPG executable, typically `/usr/local/MacGPG2/bin/gpg2` or `/usr/local/bin/gpg`. -- `sagan.key` - Sagan authentication token. Must be a valid GitHub token. Can be the same as `git.password` when using a GitHub token as password. +- `gpg.executable` - Path to your GPG executable, typically `/usr/local/MacGPG2/bin/gpg2` + or `/usr/local/bin/gpg`. +- `sagan.key` - Sagan authentication token. Must be a valid GitHub token. Can be the same + as `git.password` when using a GitHub token as password. + +After that, run the `verify` command (`$ verify`) to verify your settings (authentication, +correct Maven, Java, and GPG setup). See `application-local.template` for details. diff --git a/release-tools/src/main/java/org/springframework/data/release/build/BuildOperations.java b/release-tools/src/main/java/org/springframework/data/release/build/BuildOperations.java index 4172ca2..fe37d65 100644 --- a/release-tools/src/main/java/org/springframework/data/release/build/BuildOperations.java +++ b/release-tools/src/main/java/org/springframework/data/release/build/BuildOperations.java @@ -31,6 +31,7 @@ import org.springframework.data.release.model.Module; import org.springframework.data.release.model.ModuleIteration; import org.springframework.data.release.model.Phase; import org.springframework.data.release.model.Project; +import org.springframework.data.release.model.Projects; import org.springframework.data.release.model.Train; import org.springframework.data.release.model.TrainIteration; import org.springframework.data.release.utils.Logger; @@ -198,6 +199,12 @@ public class BuildOperations { executor.doWithBuildSystemAnyOrder(iteration, BuildSystem::triggerPreReleaseCheck); } + public void verify() { + + BuildSystem buildSystem = buildSystems.getRequiredPluginFor(Projects.BUILD); + buildSystem.verify(); + } + /** * Selects the build system for the module contained in the given {@link ModuleIteration} and executes the given * function with it. @@ -215,4 +222,5 @@ public class BuildOperations { return function.apply(buildSystems.getPluginFor(module.getProject(), exception), module); } + } diff --git a/release-tools/src/main/java/org/springframework/data/release/build/BuildSystem.java b/release-tools/src/main/java/org/springframework/data/release/build/BuildSystem.java index 086fb88..dbc71ca 100644 --- a/release-tools/src/main/java/org/springframework/data/release/build/BuildSystem.java +++ b/release-tools/src/main/java/org/springframework/data/release/build/BuildSystem.java @@ -72,4 +72,9 @@ interface BuildSystem extends Plugin { * @return */ M triggerPreReleaseCheck(M module); + + /** + * Verify general functionality and correctness of the build setup. + */ + void verify(); } diff --git a/release-tools/src/main/java/org/springframework/data/release/build/CommandLine.java b/release-tools/src/main/java/org/springframework/data/release/build/CommandLine.java index d1e3829..0f6a5e5 100644 --- a/release-tools/src/main/java/org/springframework/data/release/build/CommandLine.java +++ b/release-tools/src/main/java/org/springframework/data/release/build/CommandLine.java @@ -35,7 +35,7 @@ import org.springframework.util.Assert; /** * Value object to represent a Maven command line. - * + * * @author Oliver Gierke */ @Value @@ -46,7 +46,7 @@ class CommandLine { /** * Creates a new {@link CommandLine} for the given {@link Goal} and {@link Argument}s. - * + * * @param goal must not be {@literal null}. * @param argument must not be {@literal null}. * @return @@ -57,7 +57,7 @@ class CommandLine { /** * Creates a new {@link CommandLine} for the given {@link Goal}s and {@link Argument}s. - * + * * @param goal must not be {@literal null}. * @param argument must not be {@literal null}. * @return @@ -69,7 +69,7 @@ class CommandLine { /** * Returns a new {@link CommandLine} with the given {@link Argument} added in case the given {@link BooleanSupplier} * evaluates to {@literal true}. - * + * * @param argument must not be {@literal null}. * @param condition must not be {@literal null}. * @return @@ -80,7 +80,7 @@ class CommandLine { /** * Returns a new {@link CommandLine} with the given {@link Argument} added. - * + * * @param argument must not be {@literal null}. * @return */ @@ -98,7 +98,7 @@ class CommandLine { /** * Renders the current {@link CommandLine} as a plain {@link List} of {@link String}s using the given {@link Function} * to expand the {@link Goal}s. - * + * * @param goalExpansion must not be {@literal null}. * @return */ @@ -110,7 +110,7 @@ class CommandLine { return Stream.concat(goalStream, argumentStream).collect(Collectors.toList()); } - /* + /* * (non-Javadoc) * @see java.lang.Object#toString() */ @@ -135,6 +135,7 @@ class CommandLine { public static final Goal INSTALL = Goal.goal("install"); public static final Goal DEPLOY = Goal.goal("deploy"); public static final Goal VALIDATE = Goal.goal("validate"); + public static final Goal VERIFY = Goal.goal("verify"); String goal; } @@ -158,7 +159,7 @@ class CommandLine { /** * Enables the given comma-separated profiles for the {@link CommandLine}. - * + * * @param name must not be {@literal null} or empty. * @return */ @@ -196,7 +197,7 @@ class CommandLine { return toNameValuePair(value.map(ArgumentValue::toCommandLine)); } - /* + /* * (non-Javadoc) * @see java.lang.Object#toString() */ @@ -229,7 +230,7 @@ class CommandLine { /** * Returns an {@link ArgumentValue} for the given {@link Masked} value. - * + * * @param masked must not be {@literal null}. * @return */ @@ -239,7 +240,7 @@ class CommandLine { /** * Returns the {@link String} variant of the argument value. - * + * * @return */ public String toCommandLine() { diff --git a/release-tools/src/main/java/org/springframework/data/release/build/MavenBuildSystem.java b/release-tools/src/main/java/org/springframework/data/release/build/MavenBuildSystem.java index 44564b9..a4582f9 100644 --- a/release-tools/src/main/java/org/springframework/data/release/build/MavenBuildSystem.java +++ b/release-tools/src/main/java/org/springframework/data/release/build/MavenBuildSystem.java @@ -306,6 +306,28 @@ class MavenBuildSystem implements BuildSystem { return isMavenProject(project); } + /* + * (non-Javadoc) + * @see org.springframework.data.release.build.BuildSystem#verify() + */ + @Override + public void verify() { + + logger.log(BUILD, "Verifying Maven Build System…"); + + CommandLine arguments = CommandLine.of(Goal.CLEAN, Goal.VERIFY, // + profile("central"), // + SKIP_TESTS, // + arg("gpg.executable").withValue(gpg.getExecutable()), // + arg("gpg.keyname").withValue(gpg.getKeyname()), // + arg("gpg.password").withValue(gpg.getPassword())); + + mvn.execute(BUILD, arguments); + + mvn.execute(BUILD, CommandLine.of(Goal.goal("nexus-staging:rc-list-profiles"), // + profile("central"))); + } + /** * Triggers Maven commands to deploy module artifacts to Spring Artifactory. * diff --git a/release-tools/src/main/java/org/springframework/data/release/cli/VerifyCommands.java b/release-tools/src/main/java/org/springframework/data/release/cli/VerifyCommands.java new file mode 100644 index 0000000..988e3c8 --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/cli/VerifyCommands.java @@ -0,0 +1,76 @@ +/* + * Copyright 2021 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 + * + * https://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.cli; + +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; + +import org.springframework.data.release.CliComponent; +import org.springframework.data.release.TimedCommand; +import org.springframework.data.release.build.BuildOperations; +import org.springframework.data.release.deployment.DeploymentOperations; +import org.springframework.data.release.git.GitOperations; +import org.springframework.data.release.issues.github.GitHub; +import org.springframework.data.release.issues.jira.Jira; +import org.springframework.data.release.sagan.SaganClient; +import org.springframework.data.release.utils.Logger; +import org.springframework.shell.core.annotation.CliCommand; + +/** + * Commands to verify a correct Release Tools Setup. + * + * @author Mark Paluch + */ +@CliComponent +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +class VerifyCommands extends TimedCommand { + + @NonNull GitOperations git; + @NonNull GitHub github; + @NonNull DeploymentOperations deployment; + @NonNull BuildOperations build; + @NonNull Jira jira; + @NonNull SaganClient saganClient; + @NonNull Logger logger; + + @CliCommand("verify") + public void verifyReleaseTools() { + + // Git checkout build + git.verify(); + + // Maven interaction + build.verify(); + + // Artifactory verification + deployment.verifyAuthentication(); + + // GitHub verification + github.verifyAuthentication(); + + // Jira verification + jira.verifyAuthentication(); + + // Sagan Verification + saganClient.verifyAuthentication(); + + logger.log("Verify", "All settings are verified. You can ship a release now."); + } + +} diff --git a/release-tools/src/main/java/org/springframework/data/release/git/GitOperations.java b/release-tools/src/main/java/org/springframework/data/release/git/GitOperations.java index e032065..379cb60 100644 --- a/release-tools/src/main/java/org/springframework/data/release/git/GitOperations.java +++ b/release-tools/src/main/java/org/springframework/data/release/git/GitOperations.java @@ -812,6 +812,13 @@ public class GitOperations { }); } + /** + * Verify general Git operations. + */ + public void verify() { + checkout(Projects.BUILD, Branch.MASTER); + } + private void cherryPickCommitToBranch(ObjectId id, Project project, Branch branch) { doWithGit(project, git -> { diff --git a/release-tools/src/main/java/org/springframework/data/release/issues/github/GitHub.java b/release-tools/src/main/java/org/springframework/data/release/issues/github/GitHub.java index fbd6b55..92f43b3 100644 --- a/release-tools/src/main/java/org/springframework/data/release/issues/github/GitHub.java +++ b/release-tools/src/main/java/org/springframework/data/release/issues/github/GitHub.java @@ -53,6 +53,7 @@ import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.web.client.HttpStatusCodeException; @@ -62,7 +63,7 @@ import org.springframework.web.client.HttpStatusCodeException; * @author Mark Paluch */ @Component -class GitHub extends GitHubSupport implements IssueTracker { +public class GitHub extends GitHubSupport implements IssueTracker { private static final String MILESTONE_URI = "/repos/spring-projects/{repoName}/milestones?state={state}"; private static final String ISSUES_BY_MILESTONE_AND_ASSIGNEE_URI_TEMPLATE = "/repos/spring-projects/{repoName}/issues?milestone={id}&state=all&assignee={assignee}"; @@ -565,6 +566,28 @@ class GitHub extends GitHubSupport implements IssueTracker { logger.log(module, "GitHub Release up to date"); } + /** + * Verify GitHub authentication. + */ + public void verifyAuthentication() { + + logger.log("GitHub", "Verifying GitHub Authentication…"); + + String repositoryName = GitProject.of(Projects.BUILD).getRepositoryName(); + + Map parameters = newUrlTemplateVariables(); + parameters.put("repoName", repositoryName); + + // /user requires authentication + ResponseEntity entity = operations.getForEntity("/user", Object.class); + + if (!entity.getStatusCode().is2xxSuccessful()) { + throw new IllegalStateException(String.format("Cannot obtain /user. Status: %s", entity.getStatusCode())); + } + + logger.log("GitHub", "Authentication verified!"); + } + private String getDocumentationLinks(ModuleIteration module, DocumentationMetadata documentation) { String referenceDocUrl = documentation.getReferenceDocUrl(module.getTrain()); diff --git a/release-tools/src/main/java/org/springframework/data/release/issues/jira/Jira.java b/release-tools/src/main/java/org/springframework/data/release/issues/jira/Jira.java index 1927f9b..8e3f4a6 100644 --- a/release-tools/src/main/java/org/springframework/data/release/issues/jira/Jira.java +++ b/release-tools/src/main/java/org/springframework/data/release/issues/jira/Jira.java @@ -59,7 +59,7 @@ import org.springframework.web.util.DefaultUriBuilderFactory; * @author Mark Paluch */ @Component -class Jira implements JiraConnector { +public class Jira implements JiraConnector { private static final String CREATE_ISSUES_TEMPLATE = "/issue"; private static final String ISSUE_TEMPLATE = "/issue/{ticketId}"; @@ -620,6 +620,22 @@ class Jira implements JiraConnector { return project.uses(Tracker.JIRA); } + /** + * Verify Jira Authentication. + */ + public void verifyAuthentication() { + + logger.log("Jira", "Verifying Jira Authentication…"); + + ResponseEntity entity = operations.getForEntity("/myself", Object.class); + + if (!entity.getStatusCode().is2xxSuccessful()) { + throw new IllegalStateException("Cannot access Jira user profile"); + } + + logger.log("Jira", "Authentication verified!"); + } + protected JiraComponents getJiraComponents(ProjectKey projectKey) { HttpHeaders headers = new HttpHeaders(); diff --git a/release-tools/src/main/java/org/springframework/data/release/sagan/DefaultSaganClient.java b/release-tools/src/main/java/org/springframework/data/release/sagan/DefaultSaganClient.java index 848bc2d..de966df 100644 --- a/release-tools/src/main/java/org/springframework/data/release/sagan/DefaultSaganClient.java +++ b/release-tools/src/main/java/org/springframework/data/release/sagan/DefaultSaganClient.java @@ -27,7 +27,9 @@ import java.util.List; import java.util.stream.Collectors; import org.springframework.data.release.model.Project; +import org.springframework.data.release.model.Projects; import org.springframework.data.release.utils.Logger; +import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestOperations; import com.jayway.jsonpath.JsonPath; @@ -104,6 +106,26 @@ class DefaultSaganClient implements SaganClient { createVersions(versions, resource, versionsInSagan); } + /* + * (non-Javadoc) + * @see org.springframework.data.release.sagan.SaganClient#verifyAuthentication() + */ + @Override + public void verifyAuthentication() { + + URI resource = properties.getProjectReleasesResource(Projects.BUILD); + + logger.log("Sagan", "Verifying Sagan Authentication…"); + + ResponseEntity entity = operations.getForEntity(resource, String.class); + + if (!entity.getStatusCode().is2xxSuccessful()) { + throw new IllegalStateException("Cannot access Jira user profile"); + } + + logger.log("Sagan", "Authentication verified!"); + } + private void createVersions(MaintainedVersions versions, URI resource, List versionsInSagan) { versions.stream() // diff --git a/release-tools/src/main/java/org/springframework/data/release/sagan/DummySaganClient.java b/release-tools/src/main/java/org/springframework/data/release/sagan/DummySaganClient.java index cb0cd04..8a7ae3e 100644 --- a/release-tools/src/main/java/org/springframework/data/release/sagan/DummySaganClient.java +++ b/release-tools/src/main/java/org/springframework/data/release/sagan/DummySaganClient.java @@ -85,4 +85,13 @@ class DummySaganClient implements SaganClient { public String getProjectMetadata(Project project) { return null; } + + /* + * (non-Javadoc) + * @see org.springframework.data.release.sagan.SaganClient#verifyAuthentication() + */ + @Override + public void verifyAuthentication() { + + } } diff --git a/release-tools/src/main/java/org/springframework/data/release/sagan/SaganClient.java b/release-tools/src/main/java/org/springframework/data/release/sagan/SaganClient.java index 304cfab..b3da5a8 100644 --- a/release-tools/src/main/java/org/springframework/data/release/sagan/SaganClient.java +++ b/release-tools/src/main/java/org/springframework/data/release/sagan/SaganClient.java @@ -19,14 +19,15 @@ import org.springframework.data.release.model.Project; /** * Interface to abstract the actual interaction with the Sagan server. - * + * * @author Oliver Gierke + * @author Mark Paluch */ -interface SaganClient { +public interface SaganClient { /** * Returns the project metadata for the given {@link MaintainedVersion}. - * + * * @param version must not be {@literal null}. * @return */ @@ -34,7 +35,7 @@ interface SaganClient { /** * Returns all project release metadata for the given {@link Project}. - * + * * @param project must not be {@literal null}. * @return */ @@ -42,9 +43,14 @@ interface SaganClient { /** * Updates the project metadata for the given {@link Project} to the given {@link MaintainedVersions}. - * + * * @param project must not be {@literal null}. * @param versions must not be {@literal null}. */ void updateProjectMetadata(Project project, MaintainedVersions versions); + + /** + * Verify correct authentication setup. + */ + void verifyAuthentication(); }