#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.
This commit is contained in:
Mark Paluch
2019-11-20 11:17:42 +01:00
parent fa9ed41e2f
commit 5e68493acb
8 changed files with 139 additions and 19 deletions

View File

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

View File

@@ -96,7 +96,7 @@
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
<version>4.2.0.201601211800-r</version>
<version>5.7.0.202003110725-r</version>
</dependency>
<dependency>

View File

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

View File

@@ -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"), //

View File

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

View File

@@ -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, Project> 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;
}
}
}

View File

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

View File

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