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.. - `deployment.password` - The encrypted Artifactory password..
- `gpg.keyname` - The GPG key name. - `gpg.keyname` - The GPG key name.
- `gpg.password` - The password of your GPG key. - `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`. - `gpg.executable` - Path to your GPG executable, typically `/usr/local/MacGPG2/bin/gpg2`
- `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. 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. 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.ModuleIteration;
import org.springframework.data.release.model.Phase; import org.springframework.data.release.model.Phase;
import org.springframework.data.release.model.Project; 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.Train;
import org.springframework.data.release.model.TrainIteration; import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.utils.Logger; import org.springframework.data.release.utils.Logger;
@@ -198,6 +199,12 @@ public class BuildOperations {
executor.doWithBuildSystemAnyOrder(iteration, BuildSystem::triggerPreReleaseCheck); 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 * Selects the build system for the module contained in the given {@link ModuleIteration} and executes the given
* function with it. * function with it.
@@ -215,4 +222,5 @@ public class BuildOperations {
return function.apply(buildSystems.getPluginFor(module.getProject(), exception), module); return function.apply(buildSystems.getPluginFor(module.getProject(), exception), module);
} }
} }

View File

@@ -72,4 +72,9 @@ interface BuildSystem extends Plugin<Project> {
* @return * @return
*/ */
<M extends ProjectAware> M triggerPreReleaseCheck(M module); <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. * Value object to represent a Maven command line.
* *
* @author Oliver Gierke * @author Oliver Gierke
*/ */
@Value @Value
@@ -46,7 +46,7 @@ class CommandLine {
/** /**
* Creates a new {@link CommandLine} for the given {@link Goal} and {@link Argument}s. * Creates a new {@link CommandLine} for the given {@link Goal} and {@link Argument}s.
* *
* @param goal must not be {@literal null}. * @param goal must not be {@literal null}.
* @param argument must not be {@literal null}. * @param argument must not be {@literal null}.
* @return * @return
@@ -57,7 +57,7 @@ class CommandLine {
/** /**
* Creates a new {@link CommandLine} for the given {@link Goal}s and {@link Argument}s. * Creates a new {@link CommandLine} for the given {@link Goal}s and {@link Argument}s.
* *
* @param goal must not be {@literal null}. * @param goal must not be {@literal null}.
* @param argument must not be {@literal null}. * @param argument must not be {@literal null}.
* @return * @return
@@ -69,7 +69,7 @@ class CommandLine {
/** /**
* Returns a new {@link CommandLine} with the given {@link Argument} added in case the given {@link BooleanSupplier} * Returns a new {@link CommandLine} with the given {@link Argument} added in case the given {@link BooleanSupplier}
* evaluates to {@literal true}. * evaluates to {@literal true}.
* *
* @param argument must not be {@literal null}. * @param argument must not be {@literal null}.
* @param condition must not be {@literal null}. * @param condition must not be {@literal null}.
* @return * @return
@@ -80,7 +80,7 @@ class CommandLine {
/** /**
* Returns a new {@link CommandLine} with the given {@link Argument} added. * Returns a new {@link CommandLine} with the given {@link Argument} added.
* *
* @param argument must not be {@literal null}. * @param argument must not be {@literal null}.
* @return * @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} * 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. * to expand the {@link Goal}s.
* *
* @param goalExpansion must not be {@literal null}. * @param goalExpansion must not be {@literal null}.
* @return * @return
*/ */
@@ -110,7 +110,7 @@ class CommandLine {
return Stream.concat(goalStream, argumentStream).collect(Collectors.toList()); return Stream.concat(goalStream, argumentStream).collect(Collectors.toList());
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see java.lang.Object#toString() * @see java.lang.Object#toString()
*/ */
@@ -135,6 +135,7 @@ class CommandLine {
public static final Goal INSTALL = Goal.goal("install"); public static final Goal INSTALL = Goal.goal("install");
public static final Goal DEPLOY = Goal.goal("deploy"); public static final Goal DEPLOY = Goal.goal("deploy");
public static final Goal VALIDATE = Goal.goal("validate"); public static final Goal VALIDATE = Goal.goal("validate");
public static final Goal VERIFY = Goal.goal("verify");
String goal; String goal;
} }
@@ -158,7 +159,7 @@ class CommandLine {
/** /**
* Enables the given comma-separated profiles for the {@link CommandLine}. * Enables the given comma-separated profiles for the {@link CommandLine}.
* *
* @param name must not be {@literal null} or empty. * @param name must not be {@literal null} or empty.
* @return * @return
*/ */
@@ -196,7 +197,7 @@ class CommandLine {
return toNameValuePair(value.map(ArgumentValue::toCommandLine)); return toNameValuePair(value.map(ArgumentValue::toCommandLine));
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see java.lang.Object#toString() * @see java.lang.Object#toString()
*/ */
@@ -229,7 +230,7 @@ class CommandLine {
/** /**
* Returns an {@link ArgumentValue} for the given {@link Masked} value. * Returns an {@link ArgumentValue} for the given {@link Masked} value.
* *
* @param masked must not be {@literal null}. * @param masked must not be {@literal null}.
* @return * @return
*/ */
@@ -239,7 +240,7 @@ class CommandLine {
/** /**
* Returns the {@link String} variant of the argument value. * Returns the {@link String} variant of the argument value.
* *
* @return * @return
*/ */
public String toCommandLine() { public String toCommandLine() {

View File

@@ -306,6 +306,28 @@ class MavenBuildSystem implements BuildSystem {
return isMavenProject(project); 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. * 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) { private void cherryPickCommitToBranch(ObjectId id, Project project, Branch branch) {
doWithGit(project, git -> { doWithGit(project, git -> {

View File

@@ -53,6 +53,7 @@ import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.HttpStatusCodeException;
@@ -62,7 +63,7 @@ import org.springframework.web.client.HttpStatusCodeException;
* @author Mark Paluch * @author Mark Paluch
*/ */
@Component @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 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}"; 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"); 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) { private String getDocumentationLinks(ModuleIteration module, DocumentationMetadata documentation) {
String referenceDocUrl = documentation.getReferenceDocUrl(module.getTrain()); String referenceDocUrl = documentation.getReferenceDocUrl(module.getTrain());

View File

@@ -59,7 +59,7 @@ import org.springframework.web.util.DefaultUriBuilderFactory;
* @author Mark Paluch * @author Mark Paluch
*/ */
@Component @Component
class Jira implements JiraConnector { public class Jira implements JiraConnector {
private static final String CREATE_ISSUES_TEMPLATE = "/issue"; private static final String CREATE_ISSUES_TEMPLATE = "/issue";
private static final String ISSUE_TEMPLATE = "/issue/{ticketId}"; private static final String ISSUE_TEMPLATE = "/issue/{ticketId}";
@@ -620,6 +620,22 @@ class Jira implements JiraConnector {
return project.uses(Tracker.JIRA); 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) { protected JiraComponents getJiraComponents(ProjectKey projectKey) {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();

View File

@@ -27,7 +27,9 @@ import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.data.release.model.Project; import org.springframework.data.release.model.Project;
import org.springframework.data.release.model.Projects;
import org.springframework.data.release.utils.Logger; import org.springframework.data.release.utils.Logger;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestOperations;
import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.JsonPath;
@@ -104,6 +106,26 @@ class DefaultSaganClient implements SaganClient {
createVersions(versions, resource, versionsInSagan); 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) { private void createVersions(MaintainedVersions versions, URI resource, List<String> versionsInSagan) {
versions.stream() // versions.stream() //

View File

@@ -85,4 +85,13 @@ class DummySaganClient implements SaganClient {
public String getProjectMetadata(Project project) { public String getProjectMetadata(Project project) {
return null; 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. * Interface to abstract the actual interaction with the Sagan server.
* *
* @author Oliver Gierke * @author Oliver Gierke
* @author Mark Paluch
*/ */
interface SaganClient { public interface SaganClient {
/** /**
* Returns the project metadata for the given {@link MaintainedVersion}. * Returns the project metadata for the given {@link MaintainedVersion}.
* *
* @param version must not be {@literal null}. * @param version must not be {@literal null}.
* @return * @return
*/ */
@@ -34,7 +35,7 @@ interface SaganClient {
/** /**
* Returns all project release metadata for the given {@link Project}. * Returns all project release metadata for the given {@link Project}.
* *
* @param project must not be {@literal null}. * @param project must not be {@literal null}.
* @return * @return
*/ */
@@ -42,9 +43,14 @@ interface SaganClient {
/** /**
* Updates the project metadata for the given {@link Project} to the given {@link MaintainedVersions}. * Updates the project metadata for the given {@link Project} to the given {@link MaintainedVersions}.
* *
* @param project must not be {@literal null}. * @param project must not be {@literal null}.
* @param versions must not be {@literal null}. * @param versions must not be {@literal null}.
*/ */
void updateProjectMetadata(Project project, MaintainedVersions versions); void updateProjectMetadata(Project project, MaintainedVersions versions);
/**
* Verify correct authentication setup.
*/
void verifyAuthentication();
} }