Introduce verify command to verify configuration settings.

Closes #182.
This commit is contained in:
Mark Paluch
2021-03-31 10:47:18 +02:00
parent a214956e68
commit 9bc7f70b5a
12 changed files with 220 additions and 20 deletions

View File

@@ -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.

View File

@@ -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);
}
}

View File

@@ -72,4 +72,9 @@ interface BuildSystem extends Plugin<Project> {
* @return
*/
<M extends ProjectAware> M triggerPreReleaseCheck(M module);
/**
* Verify general functionality and correctness of the build setup.
*/
void verify();
}

View File

@@ -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() {

View File

@@ -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.
*

View File

@@ -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.");
}
}

View File

@@ -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 -> {

View File

@@ -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<String, Object> parameters = newUrlTemplateVariables();
parameters.put("repoName", repositoryName);
// /user requires authentication
ResponseEntity<Object> 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());

View File

@@ -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<Object> 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();

View File

@@ -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<String> 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<String> versionsInSagan) {
versions.stream() //

View File

@@ -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() {
}
}

View File

@@ -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();
}