From 5e68493acba33f43aeb35ddba6bd712ae2ad31a1 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 20 Nov 2019 11:17:42 +0100 Subject: [PATCH] #130 - Enable commit signing if gpg Properties are set. We now sign commits if GPG properties are set. We pulled GPG properties from deployment.gpg to the top level (gpg). For the deployment, we fall back to deployment.gpg if toplevel GPG properties aren't set. --- release-tools/application-local.template | 6 +- release-tools/pom.xml | 2 +- release-tools/readme.md | 4 + .../data/release/build/MavenBuildSystem.java | 5 +- .../deployment/DeploymentProperties.java | 12 +-- .../data/release/git/GitOperations.java | 81 +++++++++++++++++-- .../data/release/model/Gpg.java | 38 +++++++++ .../src/main/resources/application.properties | 10 ++- 8 files changed, 139 insertions(+), 19 deletions(-) create mode 100644 release-tools/src/main/java/org/springframework/data/release/model/Gpg.java diff --git a/release-tools/application-local.template b/release-tools/application-local.template index 065ebdf..277a68c 100644 --- a/release-tools/application-local.template +++ b/release-tools/application-local.template @@ -20,9 +20,9 @@ jira.password= jira.url=https://jira.spring.io # GPG -deployment.gpg.keyname= -deployment.gpg.password= -# deployment.gpg.executable=/usr/local/bin/gpg2 +gpg.keyname= +gpg.password= +# gpg.executable=/usr/local/bin/gpg2 # A GitHub token with user:email, read:user and read:org scopes. # User needs to be part of the Spring team on GitHub as well. diff --git a/release-tools/pom.xml b/release-tools/pom.xml index 30eef71..d93dc5f 100644 --- a/release-tools/pom.xml +++ b/release-tools/pom.xml @@ -96,7 +96,7 @@ org.eclipse.jgit org.eclipse.jgit - 4.2.0.201601211800-r + 5.7.0.202003110725-r diff --git a/release-tools/readme.md b/release-tools/readme.md index f12270b..4bd1ebf 100644 --- a/release-tools/readme.md +++ b/release-tools/readme.md @@ -16,6 +16,10 @@ Add an `application-local.properties` to the project root and add the following - `maven.mavenHome` - Pointing to the location of your Maven installation. - `deployment.api-key` - The API key to use for artifact promotion. - `deployment.password` - The password of the deployment user (buildmaster). +- `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`. + See `application-local.template` for details. 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 09367fc..11d020d 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 @@ -34,9 +34,9 @@ import org.springframework.data.release.build.Pom.Artifact; import org.springframework.data.release.deployment.DefaultDeploymentInformation; import org.springframework.data.release.deployment.DeploymentInformation; import org.springframework.data.release.deployment.DeploymentProperties; -import org.springframework.data.release.deployment.DeploymentProperties.Gpg; import org.springframework.data.release.io.Workspace; import org.springframework.data.release.model.ArtifactVersion; +import org.springframework.data.release.model.Gpg; import org.springframework.data.release.model.ModuleIteration; import org.springframework.data.release.model.Phase; import org.springframework.data.release.model.Project; @@ -66,6 +66,7 @@ class MavenBuildSystem implements BuildSystem { Logger logger; MavenRuntime mvn; DeploymentProperties properties; + Gpg gpg; /* * (non-Javadoc) @@ -331,7 +332,7 @@ class MavenBuildSystem implements BuildSystem { logger.log(module, "Deploying artifacts to Sonatype OSS Nexus…"); - Gpg gpg = properties.getGpg(); + Gpg gpg = this.gpg.isGpgAvailable() ? this.gpg : properties.getGpg(); CommandLine arguments = CommandLine.of(Goal.DEPLOY, // profile("ci,release,central"), // diff --git a/release-tools/src/main/java/org/springframework/data/release/deployment/DeploymentProperties.java b/release-tools/src/main/java/org/springframework/data/release/deployment/DeploymentProperties.java index 6b5f54a..0492cb4 100644 --- a/release-tools/src/main/java/org/springframework/data/release/deployment/DeploymentProperties.java +++ b/release-tools/src/main/java/org/springframework/data/release/deployment/DeploymentProperties.java @@ -20,6 +20,8 @@ import lombok.Data; import java.net.URI; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; +import org.springframework.data.release.model.Gpg; import org.springframework.data.release.model.Password; import org.springframework.data.release.utils.HttpBasicCredentials; import org.springframework.stereotype.Component; @@ -74,6 +76,11 @@ public class DeploymentProperties { return repositoryPrefix.concat(distributionRepository); } + @DeprecatedConfigurationProperty(reason = "Moved to gpg.", replacement = "gpg") + public Gpg getGpg() { + return gpg; + } + /** * Returns the URI of the staging repository. * @@ -119,9 +126,4 @@ public class DeploymentProperties { } } - @Data - public static class Gpg { - private String keyname, executable; - private Password password; - } } 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 6b821f9..0f66d36 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 @@ -21,8 +21,10 @@ import lombok.experimental.FieldDefaults; import java.io.File; import java.io.IOException; +import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; @@ -32,22 +34,31 @@ import java.util.stream.Stream; import org.eclipse.jgit.api.CheckoutCommand; import org.eclipse.jgit.api.CherryPickResult; import org.eclipse.jgit.api.CherryPickResult.CherryPickStatus; +import org.eclipse.jgit.api.CommitCommand; import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.ResetCommand.ResetType; import org.eclipse.jgit.api.errors.RefNotFoundException; +import org.eclipse.jgit.errors.UnsupportedCredentialItem; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.eclipse.jgit.transport.CredentialItem; +import org.eclipse.jgit.transport.CredentialItem.CharArrayType; +import org.eclipse.jgit.transport.CredentialItem.InformationalMessage; +import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.TagOpt; +import org.eclipse.jgit.transport.URIish; + import org.springframework.data.release.io.Workspace; import org.springframework.data.release.issues.IssueTracker; import org.springframework.data.release.issues.Ticket; import org.springframework.data.release.model.ArtifactVersion; +import org.springframework.data.release.model.Gpg; import org.springframework.data.release.model.Iteration; import org.springframework.data.release.model.ModuleIteration; import org.springframework.data.release.model.Project; @@ -63,6 +74,7 @@ import org.springframework.util.Assert; * Component to execute Git related operations. * * @author Oliver Gierke + * @author Mark Paluch */ @Component @RequiredArgsConstructor @@ -78,6 +90,7 @@ public class GitOperations { Logger logger; PluginRegistry issueTracker; GitProperties gitProperties; + Gpg gpg; /** * Returns the {@link GitProject} for the given {@link Project}. @@ -93,7 +106,6 @@ public class GitOperations { * Resets the repositories for all modules of the given {@link Train}. * * @param train must not be {@literal null}. - * @throws Exception */ public void reset(TrainIteration train) { @@ -437,16 +449,25 @@ public class GitOperations { String author = gitProperties.getAuthor(); String email = gitProperties.getEmail(); - logger.log(module, "git commit -m \"%s\" --author=\"%s <%s>\"", commit, author, email); + logger.log(module, "git commit -m \"%s\" %s --author=\"%s <%s>\"", commit, + gpg.isGpgAvailable() ? "-S" + gpg.getKeyname() : "", author, email); doWithGit(project, git -> { - git.commit()// + CommitCommand commitCommand = git.commit()// .setMessage(commit.toString())// .setAuthor(author, email)// .setCommitter(author, email)// - .setAll(true)// - .call(); + .setAll(true); + + if (gpg.isGpgAvailable()) { + commitCommand.setSign(true).setSigningKey(gpg.getKeyname()) + .setCredentialsProvider(new GpgPassphraseProvider(gpg)); + } else { + commitCommand.setSign(false); + } + + commitCommand.call(); }); } @@ -783,4 +804,54 @@ public class GitOperations { private static interface VoidGitCallback { void doWithGit(Git git) throws Exception; } + + /** + * {@link CredentialsProvider} for GPG Keys used with JGit Commit Signing. + */ + private static class GpgPassphraseProvider extends CredentialsProvider { + + private final Gpg gpg; + + private GpgPassphraseProvider(Gpg gpg) { + this.gpg = gpg; + } + + @Override + public boolean isInteractive() { + return false; + } + + @Override + public boolean supports(CredentialItem... items) { + + boolean matchesKey = matchesKey(items); + boolean hasSettableCharArray = Arrays.stream(items).anyMatch(CharArrayType.class::isInstance); + + return matchesKey && hasSettableCharArray; + } + + private boolean matchesKey(CredentialItem[] items) { + return Arrays.stream(items).filter(InformationalMessage.class::isInstance) // + .map(CredentialItem::getPromptText) // + .map(it -> it.toLowerCase(Locale.US)) // + .anyMatch(it -> it.contains(gpg.getKeyname().toLowerCase(Locale.US))); + } + + @Override + public boolean get(URIish uri, CredentialItem... items) throws UnsupportedCredentialItem { + + if (!matchesKey(items)) { + return false; + } + + for (CredentialItem item : items) { + if (item instanceof CharArrayType) { + ((CharArrayType) item).setValueNoCopy(gpg.getPassword().toString().toCharArray()); + + return true; + } + } + return false; + } + } } diff --git a/release-tools/src/main/java/org/springframework/data/release/model/Gpg.java b/release-tools/src/main/java/org/springframework/data/release/model/Gpg.java new file mode 100644 index 0000000..3d7bad1 --- /dev/null +++ b/release-tools/src/main/java/org/springframework/data/release/model/Gpg.java @@ -0,0 +1,38 @@ +/* + * Copyright 2019 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.model; + +import lombok.Data; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +/** + * @author Oliver Gierke + * @author Mark Paluch + */ +@Data +@Component +@ConfigurationProperties(prefix = "gpg") +public class Gpg { + private String keyname, executable; + private Password password; + + public boolean isGpgAvailable() { + return this.password != null && StringUtils.hasText(keyname); + } +} diff --git a/release-tools/src/main/resources/application.properties b/release-tools/src/main/resources/application.properties index fa3b02e..f4b5380 100644 --- a/release-tools/src/main/resources/application.properties +++ b/release-tools/src/main/resources/application.properties @@ -1,6 +1,7 @@ spring.main.banner-mode=off io.work-dir=~/temp/spring-data-shell/workspace io.logs=logs + # Maven setup maven.local-repository=~/temp/spring-data-shell/repository maven.plugins.versions=org.codehaus.mojo:versions-maven-plugin:2.2 @@ -10,12 +11,15 @@ deployment.staging-repository=libs-staging-local deployment.distribution-repository=temp-private-local deployment.username=buildmaster #deployment.password <- local + # GPG setup -deployment.gpg.executable=/usr/local/bin/gpg2 -# deployment.gpg.keyname -# deployment.gpg.password +gpg.executable=/usr/local/bin/gpg2 +# gpg.keyname +# gpg.password + # JIRA jira.api-url=https://jira.spring.io + # GitHub github.api-url=https://api.github.com logging.pattern.console=%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(%-20.20logger{19}){cyan}%clr(:){faint} %m%n%wEx