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
gpg.keyname= gpg.keyname=
gpg.password= gpg.passphrase=
gpg.executable=/usr/local/bin/gpg2 gpg.executable=/usr/local/bin/gpg2
# A GitHub token with user:email, read:user and read:org scopes. # 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.api-key` - The Artifactory API key to use for artifact promotion.
- `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.passphrase` - The password of your GPG key.
- `gpg.executable` - Path to your GPG executable, typically `/usr/local/MacGPG2/bin/gpg2` - `gpg.executable` - Path to your GPG executable, typically `/usr/local/MacGPG2/bin/gpg2`
or `/usr/local/bin/gpg`. or `/usr/local/bin/gpg`.
- `sagan.key` - Sagan authentication token. Must be a valid GitHub token. Can be the same - `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.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.util.Arrays; import java.util.*;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
@@ -39,7 +34,6 @@ import java.util.stream.Collectors;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.springframework.data.release.infra.InfrastructureOperations; import org.springframework.data.release.infra.InfrastructureOperations;
import org.springframework.data.release.io.Workspace; import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.model.JavaVersion; import org.springframework.data.release.model.JavaVersion;
@@ -106,6 +100,13 @@ class BuildExecutor {
skip.forEach(it -> results.put(it, CompletableFuture.completedFuture(null))); skip.forEach(it -> results.put(it, CompletableFuture.completedFuture(null)));
for (M moduleIteration : iteration) {
if (skip.contains(moduleIteration.getProject())) {
continue;
}
}
for (M moduleIteration : iteration) { for (M moduleIteration : iteration) {
if (skip.contains(moduleIteration.getProject())) { if (skip.contains(moduleIteration.getProject())) {
@@ -134,6 +135,7 @@ class BuildExecutor {
} }
CompletableFuture<T> result = run(moduleIteration, function); CompletableFuture<T> result = run(moduleIteration, function);
results.put(moduleIteration.getProject(), result); results.put(moduleIteration.getProject(), result);
} }

View File

@@ -25,15 +25,8 @@ import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.assertj.core.util.VisibleForTesting; import org.assertj.core.util.VisibleForTesting;
import org.springframework.data.release.deployment.DeploymentInformation; import org.springframework.data.release.deployment.DeploymentInformation;
import org.springframework.data.release.model.Module; import org.springframework.data.release.model.*;
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; import org.springframework.data.release.utils.Logger;
import org.springframework.plugin.core.PluginRegistry; import org.springframework.plugin.core.PluginRegistry;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -179,6 +172,21 @@ public class BuildOperations {
return properties.getLocalRepository().toPath(); 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. * 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); return function.apply(buildSystem.withJavaVersion(executor.detectJavaVersion(module.getProject())), module);
} }
} }

View File

@@ -16,11 +16,7 @@
package org.springframework.data.release.build; package org.springframework.data.release.build;
import org.springframework.data.release.deployment.DeploymentInformation; import org.springframework.data.release.deployment.DeploymentInformation;
import org.springframework.data.release.model.JavaVersion; import org.springframework.data.release.model.*;
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.plugin.core.Plugin; import org.springframework.plugin.core.Plugin;
/** /**
@@ -48,6 +44,13 @@ interface BuildSystem extends Plugin<Project> {
*/ */
ModuleIteration prepareVersion(ModuleIteration module, Phase phase); 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}. * 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.RequiredArgsConstructor;
import lombok.Value; import lombok.Value;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -181,6 +177,10 @@ class CommandLine {
return Argument.of("-X"); return Argument.of("-X");
} }
public static Argument settingsXml(String path) {
return Argument.of("-s " + path);
}
public Argument withValue(Object value) { public Argument withValue(Object value) {
return new Argument(name, ArgumentValue.of(value)); return new Argument(name, ArgumentValue.of(value));
} }

View File

@@ -16,21 +16,18 @@
package org.springframework.data.release.build; package org.springframework.data.release.build;
import static org.springframework.data.release.build.CommandLine.Argument.*; 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.BOM;
import static org.springframework.data.release.model.Projects.*; import static org.springframework.data.release.model.Projects.BUILD;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
import java.io.BufferedInputStream; import java.io.*;
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.nio.charset.StandardCharsets; 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.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -40,8 +37,9 @@ import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamResult;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.springframework.core.annotation.Order; 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.Argument;
import org.springframework.data.release.build.CommandLine.Goal; import org.springframework.data.release.build.CommandLine.Goal;
import org.springframework.data.release.build.Pom.Artifact; 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.DeploymentInformation;
import org.springframework.data.release.deployment.DeploymentProperties; import org.springframework.data.release.deployment.DeploymentProperties;
import org.springframework.data.release.io.Workspace; import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.model.ArtifactVersion; import org.springframework.data.release.model.*;
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.utils.Logger; import org.springframework.data.release.utils.Logger;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.xmlbeam.ProjectionFactory; import org.xmlbeam.ProjectionFactory;
import org.xmlbeam.XBProjector; import org.xmlbeam.XBProjector;
import org.xmlbeam.dom.DOMAccess; import org.xmlbeam.dom.DOMAccess;
@@ -69,6 +59,7 @@ import org.xmlbeam.io.XBStreamInput;
/** /**
* @author Oliver Gierke * @author Oliver Gierke
* @author Mark Paluch * @author Mark Paluch
* @author Greg Turnquist
*/ */
@Component @Component
@Order(100) @Order(100)
@@ -85,10 +76,17 @@ class MavenBuildSystem implements BuildSystem {
DeploymentProperties properties; DeploymentProperties properties;
Gpg gpg; Gpg gpg;
Environment env;
static String stagingRepositoryId = null;
static final String REPO_OPENING_TAG = "<repository>";
static final String REPO_CLOSING_TAG = "</repository>";
@Override @Override
public BuildSystem withJavaVersion(JavaVersion javaVersion) { public BuildSystem withJavaVersion(JavaVersion javaVersion) {
return new MavenBuildSystem(workspace, projectionFactory, logger, mvn.withJavaVersion(javaVersion), properties, return new MavenBuildSystem(workspace, projectionFactory, logger, mvn.withJavaVersion(javaVersion), properties, gpg,
gpg); env);
} }
/* /*
@@ -245,7 +243,7 @@ class MavenBuildSystem implements BuildSystem {
Project project = module.getProject(); Project project = module.getProject();
UpdateInformation information = UpdateInformation.of(module.getTrainIteration(), phase); 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)) { if (BOM.equals(project)) {
@@ -277,6 +275,59 @@ class MavenBuildSystem implements BuildSystem {
return module; 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) * (non-Javadoc)
* @see org.springframework.data.release.build.BuildSystem#deploy(org.springframework.data.release.model.ModuleIteration) * @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); DeploymentInformation information = new DefaultDeploymentInformation(module, properties);
deployToArtifactory(module, information); deployToArtifactory(module, information);
deployToMavenCentral(module); deployToMavenCentral(module);
return information; return information;
@@ -342,8 +394,8 @@ class MavenBuildSystem implements BuildSystem {
profile("central"), // profile("central"), //
SKIP_TESTS, // SKIP_TESTS, //
arg("gpg.executable").withValue(gpg.getExecutable()), // arg("gpg.executable").withValue(gpg.getExecutable()), //
arg("gpg.keyname").withValue(gpg.getKeyname()), // arg("gpg.passphrase").withValue(gpg.getPassphrase()), //
arg("gpg.password").withValue(gpg.getPassword())); arg("gpg.secretKeyring").withValue(gpg.getSecretKeyring()));
mvn.execute(BUILD, arguments); mvn.execute(BUILD, arguments);
@@ -403,9 +455,13 @@ class MavenBuildSystem implements BuildSystem {
CommandLine arguments = CommandLine.of(Goal.CLEAN, Goal.DEPLOY, // CommandLine arguments = CommandLine.of(Goal.CLEAN, Goal.DEPLOY, //
profile("ci,release,central"), // profile("ci,release,central"), //
SKIP_TESTS, // SKIP_TESTS, //
settingsXml(properties.getSettingsXml()), //
arg("gpg.executable").withValue(gpg.getExecutable()), // arg("gpg.executable").withValue(gpg.getExecutable()), //
arg("gpg.keyname").withValue(gpg.getKeyname()), // 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); mvn.execute(module.getProject(), arguments);
} }

View File

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

View File

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

View File

@@ -62,8 +62,12 @@ public class DeploymentProperties {
*/ */
private String distributionRepository; private String distributionRepository;
private String settingsXml;
private String repositoryPrefix = ""; private String repositoryPrefix = "";
private MavenCentral mavenCentral;
public String getStagingRepository() { public String getStagingRepository() {
return repositoryPrefix.concat(stagingRepository); 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 java.util.stream.StreamSupport;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.eclipse.jgit.api.AddCommand; import org.eclipse.jgit.api.*;
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CommitCommand;
import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode; 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.ResetCommand.ResetType;
import org.eclipse.jgit.api.errors.EmptyCommitException; import org.eclipse.jgit.api.errors.EmptyCommitException;
import org.eclipse.jgit.api.errors.RefNotFoundException; 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.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder; 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.CharArrayType;
import org.eclipse.jgit.transport.CredentialItem.InformationalMessage; 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.io.Workspace;
import org.springframework.data.release.issues.IssueTracker; import org.springframework.data.release.issues.IssueTracker;
import org.springframework.data.release.issues.Ticket; import org.springframework.data.release.issues.Ticket;
import org.springframework.data.release.issues.TicketReference; import org.springframework.data.release.issues.TicketReference;
import org.springframework.data.release.issues.TicketStatus; import org.springframework.data.release.issues.TicketStatus;
import org.springframework.data.release.model.ArtifactVersion; import org.springframework.data.release.model.*;
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.utils.ExecutionUtils; import org.springframework.data.release.utils.ExecutionUtils;
import org.springframework.data.release.utils.Logger; import org.springframework.data.release.utils.Logger;
import org.springframework.data.util.Pair; import org.springframework.data.util.Pair;
@@ -710,7 +692,9 @@ public class GitOperations {
.setAll(all); .setAll(all);
if (gpg.isGpgAvailable()) { if (gpg.isGpgAvailable()) {
commitCommand.setSign(true).setSigningKey(gpg.getKeyname()) commitCommand //
.setSign(true) //
.setSigningKey(gpg.getKeyname()) //
.setCredentialsProvider(new GpgPassphraseProvider(gpg)); .setCredentialsProvider(new GpgPassphraseProvider(gpg));
} else { } else {
commitCommand.setSign(false); commitCommand.setSign(false);
@@ -1101,6 +1085,7 @@ public class GitOperations {
} }
private boolean matchesKey(CredentialItem[] items) { private boolean matchesKey(CredentialItem[] items) {
return Arrays.stream(items).filter(InformationalMessage.class::isInstance) // return Arrays.stream(items).filter(InformationalMessage.class::isInstance) //
.map(CredentialItem::getPromptText) // .map(CredentialItem::getPromptText) //
.map(it -> it.toLowerCase(Locale.US)) // .map(it -> it.toLowerCase(Locale.US)) //
@@ -1116,7 +1101,7 @@ public class GitOperations {
for (CredentialItem item : items) { for (CredentialItem item : items) {
if (item instanceof CharArrayType) { if (item instanceof CharArrayType) {
((CharArrayType) item).setValueNoCopy(gpg.getPassword().toString().toCharArray()); ((CharArrayType) item).setValueNoCopy(gpg.getPassphrase().toCharArray());
return true; return true;
} }

View File

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

View File

@@ -30,11 +30,12 @@ import org.springframework.util.StringUtils;
@ConfigurationProperties(prefix = "gpg") @ConfigurationProperties(prefix = "gpg")
public class Gpg { public class Gpg {
private String keyname, executable; private String keyname;
private String executable;
private Password password; private String passphrase;
private String secretKeyring;
public boolean isGpgAvailable() { 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(ENVERS))
.filterModules(module -> !module.getProject().equals(R2DBC)) .filterModules(module -> !module.getProject().equals(R2DBC))
.filterModules(module -> !module.getProject().equals(JDBC)) // filter "old" JDBC without R2DBC submodule .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 // Trains

View File

@@ -267,7 +267,7 @@ public class Train implements Streamable<Module> {
@ToString @ToString
public static class Iterations implements Iterable<Iteration> { 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); SR9, SR10, SR11, SR12, SR13, SR14, SR15, SR16, SR17, SR18, SR19, SR20, SR21, SR22, SR23, SR24);
private final List<Iteration> iterations; 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 setup
gpg.executable=/usr/local/bin/gpg2 gpg.executable=/usr/local/bin/gpg2
# gpg.keyname # gpg.keyname
# gpg.password # gpg.passphrase
# JIRA # JIRA
jira.api-url=https://jira.spring.io jira.api-url=https://jira.spring.io
# GitHub # GitHub

View File

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