Support doing releases from CI.

This commit is contained in:
Greg L. Turnquist
2022-07-20 14:45:24 -05:00
parent 2534caf7fd
commit b0ccb313aa
31 changed files with 606 additions and 108 deletions

12
.gitignore vendored Normal file
View File

@@ -0,0 +1,12 @@
target/
.settings/
.project
.classpath
.factorypath
.springBeans
application-local.properties
spring-shell.log
.idea/
logs/
*.iml
dependency-upgrade-*.properties

96
Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,96 @@
def p = [:]
node {
checkout scm
p = readProperties interpolate: true, file: 'ci/release.properties'
}
pipeline {
agent none
options {
disableConcurrentBuilds()
buildDiscarder(logRotator(numToKeepStr: '14'))
}
stages {
stage('Ship It') {
when {
branch 'release'
}
agent {
docker {
image 'springci/spring-data-release-tools:0.1'
}
}
options { timeout(time: 4, unit: 'HOURS') }
environment {
GIT_USERNAME = credentials('spring-data-release-git-username')
GIT_AUTHOR = credentials('spring-data-release-git-author')
GIT_EMAIL = credentials('spring-data-release-git-email')
GIT_PASSWORD = credentials('spring-data-release-git-password')
GITHUB_API_URL = credentials('spring-data-release-github-api-url')
DEPLOYMENT_USERNAME = credentials('spring-data-release-deployment-username')
DEPLOYMENT_PASSWORD = credentials('spring-data-release-deployment-password')
DEPLOYMENT_API_KEY = credentials('spring-data-release-deployment-api-key')
STAGING_PROFILE_ID = credentials('spring-data-release-deployment-maven-central-staging-profile-id')
JIRA_USERNAME = credentials('spring-data-release-jira-username')
JIRA_PASSWORD = credentials('spring-data-release-jira-password')
JIRA_URL = credentials('spring-data-release-jira-url')
PASSPHRASE = credentials('spring-gpg-passphrase')
KEYRING = credentials('spring-signing-secring.gpg')
SONATYPE = credentials('oss-login')
}
steps {
script {
sh "ci/build-spring-data-release-cli.bash"
sh "ci/prepare-and-build.bash ${p['release.version']}"
slackSend(
color: (currentBuild.currentResult == 'SUCCESS') ? 'good' : 'danger',
channel: '#spring-data-dev',
message: (currentBuild.currentResult == 'SUCCESS')
? "`${env.BUILD_URL}` - Build and deploy passed! Conduct smoke tests then report back here."
: "`${env.BUILD_URL}` - Push and distribute failed!")
input("SMOKE TEST: Did the smoke tests for ${p['release.version']} pass?")
sh "ci/conclude.bash ${p['release.version']}"
slackSend(
color: (currentBuild.currentResult == 'SUCCESS') ? 'good' : 'danger',
channel: '#spring-data-dev',
message: "${env.BUILD_URL} - Ready to push and distribute? Check out the logs and click on either `Proceed` or `Abort`")
input("PUSH AND DISTRIBUTE: Ready to push and distribute ${p['release.version']}? (Can't go back after this)")
sh "ci/push-and-distribute.bash ${p['release.version']}"
slackSend(
color: (currentBuild.currentResult == 'SUCCESS') ? 'good' : 'danger',
channel: '#spring-data-dev',
message: (currentBuild.currentResult == 'SUCCESS')
? "`${env.BUILD_URL}` - Push and distribute ${p['release.version']} passed! Release the build (if needed)."
: "`${env.BUILD_URL}` - Push and distribute ${p['release.version']} failed!")
}
}
}
}
post {
changed {
script {
slackSend(
color: (currentBuild.currentResult == 'SUCCESS') ? 'good' : 'danger',
channel: '#spring-data-dev',
message: "${currentBuild.fullDisplayName} - `${currentBuild.currentResult}`\n${env.BUILD_URL}")
emailext(
subject: "[${currentBuild.fullDisplayName}] ${currentBuild.currentResult}",
mimeType: 'text/html',
recipientProviders: [[$class: 'CulpritsRecipientProvider'], [$class: 'RequesterRecipientProvider']],
body: "<a href=\"${env.BUILD_URL}\">${currentBuild.fullDisplayName} is reported as ${currentBuild.currentResult}</a>")
}
}
}
}

44
Jenkinsfile-container Normal file
View File

@@ -0,0 +1,44 @@
pipeline {
agent none
options {
disableConcurrentBuilds()
buildDiscarder(logRotator(numToKeepStr: '14'))
}
stages {
stage('Bake the Spring Data release tools into a container') {
when {
branch 'container'
}
agent {
label 'data'
}
steps {
script {
def image = docker.build("springci/spring-data-release-tools:0.1", "ci")
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
image.push()
}
}
}
}
}
post {
changed {
script {
slackSend(
color: (currentBuild.currentResult == 'SUCCESS') ? 'good' : 'danger',
channel: '#spring-data-dev',
message: "${currentBuild.fullDisplayName} - `${currentBuild.currentResult}`\n${env.BUILD_URL}")
emailext(
subject: "[${currentBuild.fullDisplayName}] ${currentBuild.currentResult}",
mimeType: 'text/html',
recipientProviders: [[$class: 'CulpritsRecipientProvider'], [$class: 'RequesterRecipientProvider']],
body: "<a href=\"${env.BUILD_URL}\">${currentBuild.fullDisplayName} is reported as ${currentBuild.currentResult}</a>")
}
}
}
}

View File

@@ -24,7 +24,7 @@ deployment.api-key=
# GPG
gpg.keyname=
gpg.password=
gpg.passphrase=
gpg.executable=/usr/local/bin/gpg2
# A GitHub token with user:email, read:user and read:org scopes.

21
ci/CI.adoc Normal file
View File

@@ -0,0 +1,21 @@
== Running CI tasks locally
IMPORTANT: To do this you must have Docker installed on your machine.
1. `cd ..`
+
Because this tool is actually a subfolder, you must run the Docker container from up above.
+
2. `docker run -it --mount type=bind,source="$(pwd)",target=/release-tools springci/spring-data-release-tools:0.1 /bin/bash`
+
This will launch the Docker image and mount your source code at `release-tools-github`.
+
3. `cd release-tools`
+
Next, run the `ci/prepare-and-build.bash` script from inside the container:
+
4. `SONATYPE_USR=foo SONATYPE_PSW=bar ci/prepare-and-build.bash <version>`
Since the container is binding to your source, you can make edits from your IDE and continue to run build jobs.
NOTE: Docker containers can eat up disk space fast! From time to time, run `docker system prune` to clean out old images.

30
ci/Dockerfile Executable file
View File

@@ -0,0 +1,30 @@
FROM ubuntu:22.10
ARG USER_UID="1001"
ARG USER_GID="1001"
ARG USER_NAME="jenkins"
RUN groupadd -g $USER_GID $USER_NAME && \
useradd -m -g $USER_GID -u $USER_UID $USER_NAME
RUN apt-get update && \
apt-get -y install curl zip gnupg
RUN rm -rf /var/lib/apt/lists/* && \
rm -rf /tmp/*
USER $USER_UID:$USER_GID
RUN curl -s "https://get.sdkman.io" | bash
RUN bash -c "source $HOME/.sdkman/bin/sdkman-init.sh && \
yes | sdk install java 17.0.2-tem && \
yes | sdk install java 17.0.3-tem && \
yes | sdk install java 8.0.322-tem && \
yes | sdk install java 8.0.332-tem && \
yes | sdk install maven && \
rm -rf $HOME/.sdkman/archives/* && \
rm -rf $HOME/.sdkman/tmp/*"
ENV MAVEN_HOME="$HOME/.sdkman/candidates/maven/current"
ENV JAVA_HOME="$HOME/.sdkman/candidates/java/current"
ENV PATH="$MAVEN_HOME/bin:$JAVA_HOME/bin:$PATH"

View File

@@ -0,0 +1,11 @@
#!/bin/bash -x
set -euo pipefail
export MAVEN_HOME="$HOME/.sdkman/candidates/maven/current"
export JAVA_HOME="$HOME/.sdkman/candidates/java/current"
export PATH="$MAVEN_HOME/bin:$JAVA_HOME/bin:$PATH"
export JENKINS_HOME=/tmp/jenkins-home
mvn clean package -DskipTests

51
ci/conclude.bash Executable file
View File

@@ -0,0 +1,51 @@
#!/bin/bash -x
set -euo pipefail
VERSION=$1
echo "You want me to conclude ${VERSION} ??"
export MAVEN_HOME="$HOME/.sdkman/candidates/maven/current"
export JAVA_HOME="$HOME/.sdkman/candidates/java/current"
export PATH="$MAVEN_HOME/bin:$JAVA_HOME/bin:$PATH"
export JENKINS_HOME=/tmp/jenkins-home
export RELEASE_TOOLS_CACHE=${JENKINS_HOME}/.m2/spring-data-release-tools
export LOGS_DIR=${JENKINS_HOME}/spring-data-shell/logs
export SETTINGS_XML=${JENKINS_HOME}/settings.xml
export GNUPGHOME=~/.gnupg/
if test -f application-local.properties; then
echo "You are running from dev environment! Using application-local.properties."
GIT_BRANCH=""
function spring-data-release-shell {
java \
"-Ddeployment.settings-xml=${SETTINGS_XML}" \
"-Ddeployment.local=true" \
"-Dmaven.mavenHome=${MAVEN_HOME}" \
"-Dgpg.executable=/usr/bin/gpg" \
"-Dio.workDir=dist" \
-jar target/spring-data-release-cli.jar
}
else
echo "You are running inside Jenkins! Using parameters fed from the agent."
# Reinstall Jenkins' GPG keys
\rm -rf ${GNUPGHOME}
mkdir -p ${GNUPGHOME}
chmod 700 ${GNUPGHOME}
cp $KEYRING $GNUPGHOME
function spring-data-release-shell {
java \
-Dspring.profiles.active=jenkins \
-Dmaven.home=${MAVEN_HOME} \
-jar target/spring-data-release-cli.jar
}
fi
echo "About to conclude ${VERSION}."
echo "release conclude ${VERSION}" | spring-data-release-shell

63
ci/prepare-and-build.bash Executable file
View File

@@ -0,0 +1,63 @@
#!/bin/bash -x
set -euo pipefail
VERSION=$1
echo "You want me to build and deploy ${VERSION} ??"
export MAVEN_HOME="$HOME/.sdkman/candidates/maven/current"
export JAVA_HOME="$HOME/.sdkman/candidates/java/current"
export PATH="$MAVEN_HOME/bin:$JAVA_HOME/bin:$PATH"
export JENKINS_HOME=/tmp/jenkins-home
export RELEASE_TOOLS_CACHE=${JENKINS_HOME}/.m2/spring-data-release-tools
export LOGS_DIR=${JENKINS_HOME}/spring-data-shell/logs
export SETTINGS_XML=${JENKINS_HOME}/settings.xml
mkdir -p ${RELEASE_TOOLS_CACHE}
mkdir -p ${LOGS_DIR}
export GNUPGHOME=~/.gnupg/
mkdir -p ${GNUPGHOME}
chmod 700 ${GNUPGHOME}
cp ci/settings.xml ${JENKINS_HOME}
if test -f application-local.properties; then
echo "You are running from dev environment! Using application-local.properties."
GIT_BRANCH=""
function spring-data-release-shell {
java \
"-Ddeployment.settings-xml=${SETTINGS_XML}" \
"-Ddeployment.local=true" \
"-Dmaven.mavenHome=${MAVEN_HOME}" \
"-Dgpg.executable=/usr/bin/gpg" \
"-Dio.workDir=dist" \
-jar target/spring-data-release-cli.jar \
--cmdfile target/prepare-and-build.shell
}
else
echo "You are running inside Jenkins! Using parameters fed from the agent."
cp $KEYRING $GNUPGHOME
ls -ld ~/.gnupg
ls -lR ~/.gnupg
id
function spring-data-release-shell {
java \
-Dspring.profiles.active=jenkins \
-Dmaven.home=${MAVEN_HOME} \
-jar target/spring-data-release-cli.jar \
--cmdfile target/prepare-and-build.shell
}
fi
echo "About to prepare and build ${VERSION}."
sed "s|\${VERSION}|${VERSION}|g" < ci/prepare-and-build.template > target/prepare-and-build.shell
spring-data-release-shell

View File

@@ -0,0 +1,5 @@
workspace cleanup
release prepare ${VERSION}
repository open ${VERSION}
release build ${VERSION}
repository close ${VERSION}

50
ci/push-and-distribute.bash Executable file
View File

@@ -0,0 +1,50 @@
#!/bin/bash -x
set -euo pipefail
VERSION=$1
echo "You want me to push and distribute ${VERSION} ??"
export MAVEN_HOME="$HOME/.sdkman/candidates/maven/current"
export JAVA_HOME="$HOME/.sdkman/candidates/java/current"
export PATH="$MAVEN_HOME/bin:$JAVA_HOME/bin:$PATH"
export JENKINS_HOME=/tmp/jenkins-home
export RELEASE_TOOLS_CACHE=${JENKINS_HOME}/.m2/spring-data-release-tools
export LOGS_DIR=${JENKINS_HOME}/spring-data-shell/logs
export SETTINGS_XML=${JENKINS_HOME}/settings.xml
export GNUPGHOME=~/.gnupg/
if test -f application-local.properties; then
echo "You are running from dev environment! Using application-local.properties."
GIT_BRANCH=""
function spring-data-release-shell {
java \
"-Ddeployment.settings-xml=${SETTINGS_XML}" \
"-Ddeployment.local=true" \
"-Dmaven.mavenHome=${MAVEN_HOME}" \
"-Dgpg.executable=/usr/bin/gpg" \
"-Dio.workDir=dist" \
-jar target/spring-data-release-cli.jar \
--cmdfile target/push-and-distribute.shell
}
else
echo "You are running inside Jenkins! Using parameters fed from the agent."
function spring-data-release-shell {
java \
-Dspring.profiles.active=jenkins \
-Dmaven.home=${MAVEN_HOME} \
-jar target/spring-data-release-cli.jar \
--cmdfile target/push-and-distribute.shell
}
fi
echo "About to push and distribute ${VERSION}."
sed "s|\${VERSION}|${VERSION}|g" < ci/push-and-distribute.template > target/push-and-distribute.shell
spring-data-release-shell

View File

@@ -0,0 +1,2 @@
github push ${VERSION}
release distribute ${VERSION}

3
ci/release.properties Normal file
View File

@@ -0,0 +1,3 @@
# Release train version
release.version=2021.1.7
#release.version=2022.0.0-M6

12
ci/settings.xml Normal file
View File

@@ -0,0 +1,12 @@
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
<server>
<id>sonatype</id>
<username>${env.SONATYPE_USR}</username>
<password>${env.SONATYPE_PSW}</password>
</server>
</servers>
</settings>

View File

@@ -25,7 +25,7 @@ Add an `application-local.properties` to the project root and add the following
- `deployment.api-key` - The Artifactory API key to use for artifact promotion.
- `deployment.password` - The encrypted Artifactory password..
- `gpg.keyname` - The GPG key name.
- `gpg.password` - The password of your GPG key.
- `gpg.passphrase` - 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

View File

@@ -21,12 +21,7 @@ import lombok.SneakyThrows;
import java.io.File;
import java.io.FileInputStream;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
@@ -39,7 +34,6 @@ import java.util.stream.Collectors;
import javax.annotation.PreDestroy;
import org.apache.commons.io.IOUtils;
import org.springframework.data.release.infra.InfrastructureOperations;
import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.model.JavaVersion;
@@ -106,6 +100,13 @@ class BuildExecutor {
skip.forEach(it -> results.put(it, CompletableFuture.completedFuture(null)));
for (M moduleIteration : iteration) {
if (skip.contains(moduleIteration.getProject())) {
continue;
}
}
for (M moduleIteration : iteration) {
if (skip.contains(moduleIteration.getProject())) {
@@ -134,6 +135,7 @@ class BuildExecutor {
}
CompletableFuture<T> result = run(moduleIteration, function);
results.put(moduleIteration.getProject(), result);
}

View File

@@ -25,15 +25,8 @@ import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.assertj.core.util.VisibleForTesting;
import org.springframework.data.release.deployment.DeploymentInformation;
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.model.*;
import org.springframework.data.release.utils.Logger;
import org.springframework.plugin.core.PluginRegistry;
import org.springframework.stereotype.Component;
@@ -179,6 +172,21 @@ public class BuildOperations {
return properties.getLocalRepository().toPath();
}
/**
* Opens a repository to stage artifacts for this {@link ModuleIteration}.
*
* @param module must not be {@literal null}.
*/
public void open() {
buildSystems.getRequiredPluginFor(Projects.BUILD) //
.withJavaVersion(executor.detectJavaVersion(Projects.BUILD)).open();
}
public void close() {
buildSystems.getRequiredPluginFor(Projects.BUILD) //
.withJavaVersion(executor.detectJavaVersion(Projects.BUILD)).close();
}
/**
* Builds the release for the given {@link ModuleIteration} and deploys it to the staging repository.
*
@@ -241,5 +249,4 @@ public class BuildOperations {
return function.apply(buildSystem.withJavaVersion(executor.detectJavaVersion(module.getProject())), module);
}
}

View File

@@ -16,11 +16,7 @@
package org.springframework.data.release.build;
import org.springframework.data.release.deployment.DeploymentInformation;
import org.springframework.data.release.model.JavaVersion;
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.ProjectAware;
import org.springframework.data.release.model.*;
import org.springframework.plugin.core.Plugin;
/**
@@ -48,6 +44,13 @@ interface BuildSystem extends Plugin<Project> {
*/
ModuleIteration prepareVersion(ModuleIteration module, Phase phase);
/**
* Open a remote repository for staging artifacts.
*/
void open();
void close();
/**
* Deploy artifacts for the given {@link ModuleIteration} and return the {@link DeploymentInformation}.
*

View File

@@ -20,11 +20,7 @@ import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -181,6 +177,10 @@ class CommandLine {
return Argument.of("-X");
}
public static Argument settingsXml(String path) {
return Argument.of("-s " + path);
}
public Argument withValue(Object value) {
return new Argument(name, ArgumentValue.of(value));
}

View File

@@ -16,21 +16,18 @@
package org.springframework.data.release.build;
import static org.springframework.data.release.build.CommandLine.Argument.*;
import static org.springframework.data.release.build.CommandLine.Goal.*;
import static org.springframework.data.release.model.Projects.*;
import static org.springframework.data.release.model.Projects.BOM;
import static org.springframework.data.release.model.Projects.BUILD;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.function.Consumer;
import java.util.regex.Pattern;
@@ -40,8 +37,9 @@ import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.commons.io.IOUtils;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.data.release.build.CommandLine.Argument;
import org.springframework.data.release.build.CommandLine.Goal;
import org.springframework.data.release.build.Pom.Artifact;
@@ -49,18 +47,10 @@ 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.io.Workspace;
import org.springframework.data.release.model.ArtifactVersion;
import org.springframework.data.release.model.Gpg;
import org.springframework.data.release.model.JavaVersion;
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.ProjectAware;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.model.*;
import org.springframework.data.release.utils.Logger;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.xmlbeam.ProjectionFactory;
import org.xmlbeam.XBProjector;
import org.xmlbeam.dom.DOMAccess;
@@ -69,6 +59,7 @@ import org.xmlbeam.io.XBStreamInput;
/**
* @author Oliver Gierke
* @author Mark Paluch
* @author Greg Turnquist
*/
@Component
@Order(100)
@@ -85,10 +76,17 @@ class MavenBuildSystem implements BuildSystem {
DeploymentProperties properties;
Gpg gpg;
Environment env;
static String stagingRepositoryId = null;
static final String REPO_OPENING_TAG = "<repository>";
static final String REPO_CLOSING_TAG = "</repository>";
@Override
public BuildSystem withJavaVersion(JavaVersion javaVersion) {
return new MavenBuildSystem(workspace, projectionFactory, logger, mvn.withJavaVersion(javaVersion), properties,
gpg);
return new MavenBuildSystem(workspace, projectionFactory, logger, mvn.withJavaVersion(javaVersion), properties, gpg,
env);
}
/*
@@ -245,7 +243,7 @@ class MavenBuildSystem implements BuildSystem {
Project project = module.getProject();
UpdateInformation information = UpdateInformation.of(module.getTrainIteration(), phase);
CommandLine goals = CommandLine.of(goal("versions:set"), goal("versions:commit"));
CommandLine goals = CommandLine.of(Goal.goal("versions:set"), Goal.goal("versions:commit"));
if (BOM.equals(project)) {
@@ -277,6 +275,59 @@ class MavenBuildSystem implements BuildSystem {
return module;
}
/**
* Perform a {@literal nexus-staging:rc-open} and extract the stagingProfileId from the results.
*/
@Override
public void open() {
try {
CommandLine arguments = CommandLine.of(Goal.goal("nexus-staging:rc-open"), //
profile("central"), //
of("-s " + properties.getSettingsXml()), //
arg("stagingProfileId").withValue(properties.getMavenCentral().getStagingProfileId()), //
arg("openedRepositoryMessageFormat").withValue("'" + REPO_OPENING_TAG + "%s" + REPO_CLOSING_TAG + "'"));
mvn.execute(BUILD, arguments);
String rcOpenLogfile = "mvn-" + BUILD.getName() + "-nexus-staging.rc-open.log";
logger.log(BUILD, "Searching " + this.workspace.getLogsDirectory().getAbsolutePath() + " for " + rcOpenLogfile);
Path rcOpenLogfilePath = Paths.get(this.workspace.getLogsDirectory().getAbsolutePath(), rcOpenLogfile);
logger.log(BUILD, "The log file is at " + rcOpenLogfilePath.toAbsolutePath() + " and "
+ (rcOpenLogfilePath.toFile().exists() ? " it exists!" : " it does NOT exist!"));
List<String> rcOpenLogContents = Files.readAllLines(rcOpenLogfilePath);
stagingRepositoryId = rcOpenLogContents.stream() //
.filter(line -> line.contains(REPO_OPENING_TAG) && !line.contains("%s")) //
.reduce((first, second) -> second) // find the last entry, a.k.a. the most recent log line
.map(s -> s.substring( //
s.indexOf(REPO_OPENING_TAG) + REPO_OPENING_TAG.length(), //
s.indexOf(REPO_CLOSING_TAG))) //
.orElse("");
logger.log(BUILD, "We just grabbed the staging repository ID at " + stagingRepositoryId);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Perform a {@literal nexus-staging:rc-close}.
*/
@Override
public void close() {
CommandLine arguments = CommandLine.of(Goal.goal("nexus-staging:rc-close"), //
profile("central"), //
of("-s " + properties.getSettingsXml()), //
arg("stagingRepositoryId").withValue(stagingRepositoryId));
mvn.execute(BUILD, arguments);
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.build.BuildSystem#deploy(org.springframework.data.release.model.ModuleIteration)
@@ -289,6 +340,7 @@ class MavenBuildSystem implements BuildSystem {
DeploymentInformation information = new DefaultDeploymentInformation(module, properties);
deployToArtifactory(module, information);
deployToMavenCentral(module);
return information;
@@ -342,8 +394,8 @@ class MavenBuildSystem implements BuildSystem {
profile("central"), //
SKIP_TESTS, //
arg("gpg.executable").withValue(gpg.getExecutable()), //
arg("gpg.keyname").withValue(gpg.getKeyname()), //
arg("gpg.password").withValue(gpg.getPassword()));
arg("gpg.passphrase").withValue(gpg.getPassphrase()), //
arg("gpg.secretKeyring").withValue(gpg.getSecretKeyring()));
mvn.execute(BUILD, arguments);
@@ -403,9 +455,13 @@ class MavenBuildSystem implements BuildSystem {
CommandLine arguments = CommandLine.of(Goal.CLEAN, Goal.DEPLOY, //
profile("ci,release,central"), //
SKIP_TESTS, //
settingsXml(properties.getSettingsXml()), //
arg("gpg.executable").withValue(gpg.getExecutable()), //
arg("gpg.keyname").withValue(gpg.getKeyname()), //
arg("gpg.password").withValue(gpg.getPassword()));
arg("gpg.passphrase").withValue(gpg.getPassphrase()), //
arg("stagingRepositoryId").withValue(stagingRepositoryId)) //
.conditionalAnd(arg("gpg.secretKeyring").withValue(gpg.getSecretKeyring()),
() -> env.acceptsProfiles(Profiles.of("jenkins")));
mvn.execute(module.getProject(), arguments);
}

View File

@@ -18,20 +18,11 @@ package org.springframework.data.release.build;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.*;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.maven.shared.invoker.DefaultInvocationRequest;
import org.apache.maven.shared.invoker.DefaultInvoker;
import org.apache.maven.shared.invoker.InvocationRequest;
import org.apache.maven.shared.invoker.InvocationResult;
import org.apache.maven.shared.invoker.Invoker;
import org.apache.maven.shared.invoker.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.io.JavaRuntimes;
import org.springframework.data.release.io.Workspace;
@@ -79,6 +70,10 @@ class MavenRuntime {
return new MavenRuntime(workspace, logger, properties, javaVersion);
}
public MavenProperties getProperties() {
return this.properties;
}
public void execute(Project project, CommandLine arguments) {
logger.log(project, "📦 Executing mvn %s", arguments.toString());

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.data.release.cli;
import static org.springframework.data.release.model.Projects.*;
import static org.springframework.data.release.model.Projects.COMMONS;
import lombok.AccessLevel;
import lombok.NonNull;
@@ -31,14 +31,7 @@ import org.springframework.data.release.git.GitOperations;
import org.springframework.data.release.issues.IssueTrackerCommands;
import org.springframework.data.release.issues.github.GitHubCommands;
import org.springframework.data.release.misc.ReleaseOperations;
import org.springframework.data.release.model.ArtifactVersion;
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.ReleaseTrains;
import org.springframework.data.release.model.Train;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.model.*;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;
import org.springframework.util.Assert;
@@ -111,6 +104,22 @@ class ReleaseCommands extends TimedCommand {
git.commit(iteration, "Release version %s.");
}
@CliCommand(value = "repository open")
public void repositoryOpen(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
if (iteration.getIteration().isPublic()) {
build.open();
}
}
@CliCommand(value = "repository close")
public void repositoryClose(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
if (iteration.getIteration().isPublic()) {
build.close();
}
}
@CliCommand(value = "release build")
public void buildRelease(@CliOption(key = "", mandatory = true) TrainIteration iteration, //
@CliOption(key = "project", mandatory = false) String projectName) {

View File

@@ -62,8 +62,12 @@ public class DeploymentProperties {
*/
private String distributionRepository;
private String settingsXml;
private String repositoryPrefix = "";
private MavenCentral mavenCentral;
public String getStagingRepository() {
return repositoryPrefix.concat(stagingRepository);
}
@@ -117,4 +121,11 @@ public class DeploymentProperties {
}
}
@Data
public static class MavenCentral {
private String stagingProfileId;
}
}

View File

@@ -34,12 +34,8 @@ import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.apache.commons.io.FileUtils;
import org.eclipse.jgit.api.AddCommand;
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CommitCommand;
import org.eclipse.jgit.api.*;
import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.LogCommand;
import org.eclipse.jgit.api.ResetCommand.ResetType;
import org.eclipse.jgit.api.errors.EmptyCommitException;
import org.eclipse.jgit.api.errors.RefNotFoundException;
@@ -51,29 +47,15 @@ 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.*;
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.issues.TicketReference;
import org.springframework.data.release.issues.TicketStatus;
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;
import org.springframework.data.release.model.ProjectAware;
import org.springframework.data.release.model.Projects;
import org.springframework.data.release.model.ReleaseTrains;
import org.springframework.data.release.model.Train;
import org.springframework.data.release.model.TrainIteration;
import org.springframework.data.release.model.*;
import org.springframework.data.release.utils.ExecutionUtils;
import org.springframework.data.release.utils.Logger;
import org.springframework.data.util.Pair;
@@ -710,7 +692,9 @@ public class GitOperations {
.setAll(all);
if (gpg.isGpgAvailable()) {
commitCommand.setSign(true).setSigningKey(gpg.getKeyname())
commitCommand //
.setSign(true) //
.setSigningKey(gpg.getKeyname()) //
.setCredentialsProvider(new GpgPassphraseProvider(gpg));
} else {
commitCommand.setSign(false);
@@ -1101,6 +1085,7 @@ public class GitOperations {
}
private boolean matchesKey(CredentialItem[] items) {
return Arrays.stream(items).filter(InformationalMessage.class::isInstance) //
.map(CredentialItem::getPromptText) //
.map(it -> it.toLowerCase(Locale.US)) //
@@ -1116,7 +1101,7 @@ public class GitOperations {
for (CredentialItem item : items) {
if (item instanceof CharArrayType) {
((CharArrayType) item).setValueNoCopy(gpg.getPassword().toString().toCharArray());
((CharArrayType) item).setValueNoCopy(gpg.getPassphrase().toCharArray());
return true;
}

View File

@@ -21,7 +21,6 @@ import lombok.extern.slf4j.Slf4j;
import java.io.File;
import org.apache.commons.io.FileUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

View File

@@ -30,11 +30,12 @@ import org.springframework.util.StringUtils;
@ConfigurationProperties(prefix = "gpg")
public class Gpg {
private String keyname, executable;
private Password password;
private String keyname;
private String executable;
private String passphrase;
private String secretKeyring;
public boolean isGpgAvailable() {
return this.password != null && StringUtils.hasText(keyname);
return this.passphrase != null && StringUtils.hasText(secretKeyring);
}
}

View File

@@ -78,7 +78,7 @@ public class ReleaseTrains {
.filterModules(module -> !module.getProject().equals(ENVERS))
.filterModules(module -> !module.getProject().equals(R2DBC))
.filterModules(module -> !module.getProject().equals(JDBC)) // filter "old" JDBC without R2DBC submodule
.withIterations(new Train.Iterations(M1, M2, M3, M4, M5, RC1, RC2, GA, SR1, SR2, SR3, SR4, SR5));
.withIterations(new Train.Iterations(M1, M2, M3, M4, M5, M6, RC1, RC2, GA, SR1, SR2, SR3, SR4, SR5));
// Trains

View File

@@ -267,7 +267,7 @@ public class Train implements Streamable<Module> {
@ToString
public static class Iterations implements Iterable<Iteration> {
public static Iterations DEFAULT = new Iterations(M1, M2, M3, RC1, RC2, GA, SR1, SR2, SR3, SR4, SR5, SR6, SR7, SR8,
public static Iterations DEFAULT = new Iterations(M1, M2, M3, M4, M5, M6, RC1, RC2, GA, SR1, SR2, SR3, SR4, SR5, SR6, SR7, SR8,
SR9, SR10, SR11, SR12, SR13, SR14, SR15, SR16, SR17, SR18, SR19, SR20, SR21, SR22, SR23, SR24);
private final List<Iteration> iterations;

View File

@@ -0,0 +1,30 @@
git.username=${GIT_USERNAME}
git.author=${GIT_AUTHOR}
git.email=${GIT_EMAIL}
git.password=${GIT_PASSWORD}
github.api.url=${GITHUB_API_URL}
deployment.username=${DEPLOYMENT_USERNAME}
deployment.password=${DEPLOYMENT_PASSWORD}
deployment.api-key=${DEPLOYMENT_API_KEY}
deployment.maven-central.stagingProfileId=${STAGING_PROFILE_ID}
deployment.settings-xml=${SETTINGS_XML}
jira.username=${JIRA_USERNAME}
jira.password=${JIRA_PASSWORD}
jira.url=${JIRA_URL}
gpg.keyname=9A2C7A98E457C53D
gpg.passphrase=${PASSPHRASE}
gpg.secretKeyring=${GNUPGHOME}/secring.gpg
gpg.executable=/usr/bin/gpg
sagan.key=
maven.home=${MAVEN_HOME}
maven.console-logger=false
maven.repo.local=${RELEASE_TOOLS_CACHE}
io.workDir=dist
io.logs=${LOGS_DIR}

View File

@@ -15,7 +15,7 @@ github.team=christophstrobl,gregturn,jxblum,mp911de,odrotbohm,schauder,meisterme
# GPG setup
gpg.executable=/usr/local/bin/gpg2
# gpg.keyname
# gpg.password
# gpg.passphrase
# JIRA
jira.api-url=https://jira.spring.io
# GitHub

View File

@@ -10,7 +10,7 @@ jira.username=dummy
jira.password=dummy
jira.api-url=http://localhost:8888
git.username=dummy
git.password=dummy
git.passphrase=dummy
git.email=dummy@dummy.com
git.author=dummy
github.api-url=http://localhost:8888