Polishing.

Refactor rc-open/close to return a StagingRepository. Include open/close steps in the actual release to avoid additional steps and to retain the repository identifier.

Simplify command line conditionals, avoid log parsing and return log contents directly.

Reformat Jenkins files.
This commit is contained in:
Mark Paluch
2022-08-29 11:01:33 +02:00
parent b0ccb313aa
commit 6f5494c1ca
17 changed files with 414 additions and 172 deletions

90
Jenkinsfile vendored
View File

@@ -1,7 +1,7 @@
def p = [:]
node {
checkout scm
p = readProperties interpolate: true, file: 'ci/release.properties'
checkout scm
p = readProperties interpolate: true, file: 'ci/release.properties'
}
pipeline {
@@ -14,9 +14,9 @@ pipeline {
stages {
stage('Ship It') {
when {
branch 'release'
}
when {
branch 'release'
}
agent {
docker {
image 'springci/spring-data-release-tools:0.1'
@@ -24,56 +24,56 @@ pipeline {
}
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')
}
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']}"
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!")
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?")
input("SMOKE TEST: Did the smoke tests for ${p['release.version']} pass?")
sh "ci/conclude.bash ${p['release.version']}"
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`")
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)")
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']}"
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!")
}
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!")
}
}
}
}

View File

@@ -7,22 +7,22 @@ pipeline {
}
stages {
stage('Bake the Spring Data release tools into a container') {
when {
branch 'container'
}
agent {
label 'data'
}
stage('Build the Spring Data release tools 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()
}
}
}
steps {
script {
def image = docker.build("springci/spring-data-release-tools:0.1", "ci")
docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
image.push()
}
}
}
}
}

View File

@@ -21,6 +21,7 @@ maven.parallelize=true
deployment.username=
deployment.password=
deployment.api-key=
deployment.maven-central.staging-profile-id=
# GPG
gpg.keyname=

View File

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

View File

@@ -21,7 +21,12 @@ import lombok.SneakyThrows;
import java.io.File;
import java.io.FileInputStream;
import java.util.*;
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.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
@@ -30,10 +35,12 @@ import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
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;
@@ -100,13 +107,6 @@ 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())) {

View File

@@ -25,8 +25,16 @@ 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.*;
import org.springframework.data.release.deployment.StagingRepository;
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.utils.Logger;
import org.springframework.plugin.core.PluginRegistry;
import org.springframework.stereotype.Component;
@@ -112,8 +120,18 @@ public class BuildOperations {
*/
public List<DeploymentInformation> performRelease(TrainIteration iteration) {
ModuleIteration module = iteration.getModule(Projects.BUILD);
BuildSystem orchestrator = buildSystems.getRequiredPluginFor(module.getProject());
StagingRepository stagingRepository = iteration.getIteration().isPublic() ? orchestrator.open()
: StagingRepository.EMPTY;
BuildExecutor.Summary<DeploymentInformation> summary = executor.doWithBuildSystemOrdered(iteration,
(buildSystem, moduleIteration) -> performRelease(moduleIteration));
(buildSystem, moduleIteration) -> buildSystem.deploy(moduleIteration, stagingRepository));
if (stagingRepository.isPresent()) {
orchestrator.close(stagingRepository);
}
logger.log(iteration, "Release: %s", summary);
@@ -175,16 +193,28 @@ public class BuildOperations {
/**
* Opens a repository to stage artifacts for this {@link ModuleIteration}.
*
* @param module must not be {@literal null}.
* @param iteration must not be {@literal null}.
*/
public void open() {
buildSystems.getRequiredPluginFor(Projects.BUILD) //
.withJavaVersion(executor.detectJavaVersion(Projects.BUILD)).open();
public void open(ModuleIteration iteration) {
doWithBuildSystem(iteration, (buildSystem, moduleIteration) -> buildSystem.open());
}
public void close() {
buildSystems.getRequiredPluginFor(Projects.BUILD) //
.withJavaVersion(executor.detectJavaVersion(Projects.BUILD)).close();
/**
* Closes a repository to stage artifacts for this {@link ModuleIteration}.
*
* @param iteration must not be {@literal null}.
* @param stagingRepository must not be {@literal null}.
*/
public void close(ModuleIteration iteration, StagingRepository stagingRepository) {
Assert.notNull(stagingRepository, "StagingRepository must not be null");
Assert.isTrue(stagingRepository.isPresent(), "StagingRepository must be present");
doWithBuildSystem(iteration, (buildSystem, moduleIteration) -> {
buildSystem.close(stagingRepository);
return null;
});
}
/**

View File

@@ -16,7 +16,12 @@
package org.springframework.data.release.build;
import org.springframework.data.release.deployment.DeploymentInformation;
import org.springframework.data.release.model.*;
import org.springframework.data.release.deployment.StagingRepository;
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.plugin.core.Plugin;
/**
@@ -47,9 +52,21 @@ interface BuildSystem extends Plugin<Project> {
/**
* Open a remote repository for staging artifacts.
*/
void open();
StagingRepository open();
void close();
/**
* Close a remote repository for staging artifacts.
*/
void close(StagingRepository stagingRepository);
/**
* Deploy artifacts for the given {@link ModuleIteration} using {@link DeploymentInformation}.
*
* @param module must not be {@literal null}.
* @param stagingRepository must not be {@literal null}.
* @return
*/
DeploymentInformation deploy(ModuleIteration module, StagingRepository stagingRepository);
/**
* Deploy artifacts for the given {@link ModuleIteration} and return the {@link DeploymentInformation}.

View File

@@ -23,6 +23,7 @@ import lombok.Value;
import java.util.*;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -66,12 +67,24 @@ class CommandLine {
* Returns a new {@link CommandLine} with the given {@link Argument} added in case the given {@link BooleanSupplier}
* evaluates to {@literal true}.
*
* @param argument must not be {@literal null}.
* @param condition must not be {@literal null}.
* @param argument must not be {@literal null}.
* @return
*/
public CommandLine conditionalAnd(Argument argument, BooleanSupplier condition) {
return condition.getAsBoolean() ? and(argument) : this;
public CommandLine andIf(boolean condition, Argument argument) {
return condition ? and(argument) : this;
}
/**
* Returns a new {@link CommandLine} with the given {@link Argument} added in case the given {@link BooleanSupplier}
* evaluates to {@literal true}.
*
* @param condition must not be {@literal null}.
* @param argument must not be {@literal null}.
* @return
*/
public CommandLine andIf(boolean condition, Supplier<Argument> argument) {
return condition ? and(argument.get()) : this;
}
/**

View File

@@ -16,18 +16,21 @@
package org.springframework.data.release.build;
import static org.springframework.data.release.build.CommandLine.Argument.*;
import static org.springframework.data.release.model.Projects.BOM;
import static org.springframework.data.release.model.Projects.BUILD;
import static org.springframework.data.release.build.CommandLine.Goal.*;
import static org.springframework.data.release.model.Projects.*;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import java.io.*;
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.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;
@@ -37,24 +40,34 @@ 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;
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.StagingRepository;
import org.springframework.data.release.io.Workspace;
import org.springframework.data.release.model.*;
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.utils.Logger;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.xmlbeam.ProjectionFactory;
import org.xmlbeam.XBProjector;
import org.xmlbeam.dom.DOMAccess;
import org.xmlbeam.io.XBStreamInput;
import org.xmlbeam.io.StreamInput;
/**
* @author Oliver Gierke
@@ -78,8 +91,6 @@ class MavenBuildSystem implements BuildSystem {
Environment env;
static String stagingRepositoryId = null;
static final String REPO_OPENING_TAG = "<repository>";
static final String REPO_CLOSING_TAG = "</repository>";
@@ -243,7 +254,7 @@ class MavenBuildSystem implements BuildSystem {
Project project = module.getProject();
UpdateInformation information = UpdateInformation.of(module.getTrainIteration(), phase);
CommandLine goals = CommandLine.of(Goal.goal("versions:set"), Goal.goal("versions:commit"));
CommandLine goals = CommandLine.of(goal("versions:set"), goal("versions:commit"));
if (BOM.equals(project)) {
@@ -276,54 +287,50 @@ class MavenBuildSystem implements BuildSystem {
}
/**
* Perform a {@literal nexus-staging:rc-open} and extract the stagingProfileId from the results.
* Perform a {@literal nexus-staging:rc-open} and extract the {@code stagingProfileId} from the results.
*/
@Override
public void open() {
public StagingRepository 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 + "'"));
Assert.notNull(properties.getMavenCentral(), "Maven Central properties must not be nu,,");
Assert.hasText(properties.getMavenCentral().getStagingProfileId(), "Staging Profile Identifier must not be empty");
mvn.execute(BUILD, arguments);
CommandLine arguments = CommandLine.of(goal("nexus-staging:rc-open"), //
profile("central"), //
arg("stagingProfileId").withValue(properties.getMavenCentral().getStagingProfileId()), //
arg("openedRepositoryMessageFormat").withValue("'" + REPO_OPENING_TAG + "%s" + REPO_CLOSING_TAG + "'"))
.andIf(!ObjectUtils.isEmpty(properties.getSettingsXml()), () -> settingsXml(properties.getSettingsXml()));
String rcOpenLogfile = "mvn-" + BUILD.getName() + "-nexus-staging.rc-open.log";
MavenRuntime.MavenInvocationResult invocationResult = mvn.execute(BUILD, arguments);
logger.log(BUILD, "Searching " + this.workspace.getLogsDirectory().getAbsolutePath() + " for " + rcOpenLogfile);
List<String> rcOpenLogContents = invocationResult.getLog();
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!"));
String 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("");
List<String> rcOpenLogContents = Files.readAllLines(rcOpenLogfilePath);
logger.log(BUILD, "Opened staging repository with Id: " + stagingRepositoryId);
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);
}
return StagingRepository.of(stagingRepositoryId);
}
/**
* Perform a {@literal nexus-staging:rc-close}.
*/
@Override
public void close() {
public void close(StagingRepository stagingRepository) {
CommandLine arguments = CommandLine.of(Goal.goal("nexus-staging:rc-close"), //
Assert.notNull(stagingRepository, "StagingRepository must not be null");
Assert.isTrue(stagingRepository.isPresent(), "StagingRepository must be present");
CommandLine arguments = CommandLine.of(goal("nexus-staging:rc-close"), //
profile("central"), //
of("-s " + properties.getSettingsXml()), //
arg("stagingRepositoryId").withValue(stagingRepositoryId));
arg("stagingRepositoryId").withValue(stagingRepository.getId()))
.andIf(!ObjectUtils.isEmpty(properties.getSettingsXml()), () -> settingsXml(properties.getSettingsXml()));
mvn.execute(BUILD, arguments);
}
@@ -339,13 +346,34 @@ class MavenBuildSystem implements BuildSystem {
DeploymentInformation information = new DefaultDeploymentInformation(module, properties);
deployToArtifactory(module, information);
deployToMavenCentral(module);
deploy(module, information);
return information;
}
@Override
public DeploymentInformation deploy(ModuleIteration module, StagingRepository stagingRepository) {
Assert.notNull(module, "Module must not be null!");
Assert.notNull(stagingRepository, "StagingRepository must not be null!");
DeploymentInformation information = new DefaultDeploymentInformation(module, properties, stagingRepository);
deploy(module, information);
return information;
}
private void deploy(ModuleIteration module, DeploymentInformation information) {
Assert.notNull(module, "Module must not be null!");
Assert.notNull(information, "DeploymentInformation must not be null!");
deployToArtifactory(module, information);
deployToMavenCentral(module, information);
}
/*
* (non-Javadoc)
* @see org.springframework.data.release.build.BuildSystem#triggerBuild(org.springframework.data.release.model.ModuleIteration)
@@ -354,7 +382,7 @@ class MavenBuildSystem implements BuildSystem {
public <M extends ProjectAware> M triggerBuild(M module) {
CommandLine arguments = CommandLine.of(Goal.CLEAN, Goal.INSTALL)//
.conditionalAnd(SKIP_TESTS, () -> module.getProject().skipTests());
.andIf(module.getProject().skipTests(), SKIP_TESTS);
mvn.execute(module.getProject(), arguments);
@@ -394,13 +422,19 @@ class MavenBuildSystem implements BuildSystem {
profile("central"), //
SKIP_TESTS, //
arg("gpg.executable").withValue(gpg.getExecutable()), //
arg("gpg.passphrase").withValue(gpg.getPassphrase()), //
arg("gpg.secretKeyring").withValue(gpg.getSecretKeyring()));
arg("gpg.keyname").withValue(gpg.getKeyname()), //
arg("gpg.passphrase").withValue(gpg.getPassphrase())) //
.andIf(gpg.hasSecretKeyring(), () -> arg("gpg.secretKeyring").withValue(gpg.getSecretKeyring()));
mvn.execute(BUILD, arguments);
mvn.execute(BUILD, CommandLine.of(Goal.goal("nexus-staging:rc-list-profiles"), //
mvn.execute(BUILD, CommandLine.of(goal("nexus-staging:rc-list-profiles"), //
profile("central")));
Assert.notNull(properties.getMavenCentral(),
"Maven Central properties are not set (deployment.maven-central.staging-profile-id=…)");
Assert.hasText(properties.getMavenCentral().getStagingProfileId(),
"Staging Profile Id is not set (deployment.maven-central.staging-profile-id=…)");
}
/**
@@ -439,10 +473,12 @@ class MavenBuildSystem implements BuildSystem {
* that has to be publicly released.
*
* @param module must not be {@literal null}.
* @param deploymentInformation must not be {@literal null}.
*/
private void deployToMavenCentral(ModuleIteration module) {
private void deployToMavenCentral(ModuleIteration module, DeploymentInformation deploymentInformation) {
Assert.notNull(module, "Module iteration must not be null!");
Assert.notNull(deploymentInformation, "DeploymentInformation iteration must not be null!");
if (!module.getIteration().isPublic()) {
@@ -455,13 +491,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.passphrase").withValue(gpg.getPassphrase()), //
arg("stagingRepositoryId").withValue(stagingRepositoryId)) //
.conditionalAnd(arg("gpg.secretKeyring").withValue(gpg.getSecretKeyring()),
() -> env.acceptsProfiles(Profiles.of("jenkins")));
arg("gpg.passphrase").withValue(gpg.getPassphrase())) //
.andIf(!ObjectUtils.isEmpty(properties.getSettingsXml()), settingsXml(properties.getSettingsXml()))
.andIf(deploymentInformation.getStagingRepositoryId().isPresent(),
() -> arg("stagingRepositoryId").withValue(deploymentInformation.getStagingRepositoryId()))
.andIf(gpg.hasSecretKeyring(), () -> arg("gpg.secretKeyring").withValue(gpg.getSecretKeyring()));
mvn.execute(module.getProject(), arguments);
}
@@ -493,7 +529,7 @@ class MavenBuildSystem implements BuildSystem {
static <T extends Pom> byte[] doWithProjection(XBProjector projector, InputStream stream, Class<T> type,
Consumer<T> callback) throws IOException {
XBStreamInput io = projector.io().stream(stream);
StreamInput io = projector.io().stream(stream);
T pom = io.read(type);
callback.accept(pom);

View File

@@ -18,11 +18,21 @@ package org.springframework.data.release.build;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.maven.shared.invoker.*;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.release.io.JavaRuntimes;
import org.springframework.data.release.io.Workspace;
@@ -70,11 +80,7 @@ class MavenRuntime {
return new MavenRuntime(workspace, logger, properties, javaVersion);
}
public MavenProperties getProperties() {
return this.properties;
}
public void execute(Project project, CommandLine arguments) {
public MavenInvocationResult execute(Project project, CommandLine arguments) {
logger.log(project, "📦 Executing mvn %s", arguments.toString());
@@ -108,10 +114,14 @@ class MavenRuntime {
if (result.getExitCode() != 0) {
logger.warn(project, "🙈 Failed execution mvn %s", arguments.toString());
throw new IllegalStateException("🙈 Failed execution mvn " + arguments.toString(),
result.getExecutionException());
throw new IllegalStateException("🙈 Failed execution mvn " + arguments, result.getExecutionException());
}
logger.log(project, "🆗 Successful execution mvn %s", arguments.toString());
MavenInvocationResult invocationResult = new MavenInvocationResult();
invocationResult.getLog().addAll(mavenLogger.getLines());
return invocationResult;
} catch (Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
@@ -133,6 +143,15 @@ class MavenRuntime {
return new FileLogger(log, project, this.workspace.getLogsDirectory(), goals);
}
public static class MavenInvocationResult {
private final List<String> log = new ArrayList<>();
public List<String> getLog() {
return log;
}
}
/**
* Maven Logging Forwarder.
*/
@@ -141,6 +160,8 @@ class MavenRuntime {
void info(String message);
void warn(String message);
List<String> getLines();
}
@RequiredArgsConstructor
@@ -148,32 +169,44 @@ class MavenRuntime {
private final org.slf4j.Logger logger;
private final String logPrefix;
private final List<String> contents;
SlfLogger(org.slf4j.Logger logger, Project project) {
this.logger = logger;
this.logPrefix = StringUtils.padRight(project.getName(), 10);
this.contents = new ArrayList<>();
}
@Override
public void info(String message) {
logger.info(logPrefix + ": " + message);
String msg = logPrefix + ": " + message;
contents.add(msg);
logger.info(msg);
}
@Override
public void warn(String message) {
logger.warn(logPrefix + ": " + message);
String msg = logPrefix + ": " + message;
contents.add(msg);
logger.warn(msg);
}
@Override
public void close() throws IOException {
// no-op
}
@Override
public List<String> getLines() {
return contents;
}
}
static class FileLogger implements MavenLogger {
private final PrintWriter printWriter;
private final FileOutputStream outputStream;
private final List<String> contents = new ArrayList<>();
FileLogger(org.slf4j.Logger logger, Project project, File logsDirectory, List<CommandLine.Goal> goals) {
@@ -199,11 +232,13 @@ class MavenRuntime {
@Override
public void info(String message) {
printWriter.println(message);
contents.add(message);
}
@Override
public void warn(String message) {
printWriter.println(message);
contents.add(message);
}
@Override
@@ -211,6 +246,11 @@ class MavenRuntime {
printWriter.close();
outputStream.close();
}
@Override
public List<String> getLines() {
return contents;
}
}
}

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.data.release.cli;
import static org.springframework.data.release.model.Projects.COMMONS;
import static org.springframework.data.release.model.Projects.*;
import lombok.AccessLevel;
import lombok.NonNull;
@@ -27,11 +27,19 @@ import org.springframework.data.release.TimedCommand;
import org.springframework.data.release.build.BuildOperations;
import org.springframework.data.release.deployment.DeploymentInformation;
import org.springframework.data.release.deployment.DeploymentOperations;
import org.springframework.data.release.deployment.StagingRepository;
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.*;
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.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;
import org.springframework.util.Assert;
@@ -108,15 +116,16 @@ class ReleaseCommands extends TimedCommand {
public void repositoryOpen(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
if (iteration.getIteration().isPublic()) {
build.open();
build.open(iteration.getModule(Projects.BUILD));
}
}
@CliCommand(value = "repository close")
public void repositoryClose(@CliOption(key = "", mandatory = true) TrainIteration iteration) {
public void repositoryClose(@CliOption(key = "", mandatory = true) TrainIteration iteration,
@CliOption(key = "stagingRepositoryId", mandatory = true) String stagingRepositoryId) {
if (iteration.getIteration().isPublic()) {
build.close();
build.close(iteration.getModule(Projects.BUILD), StagingRepository.of(stagingRepositoryId));
}
}

View File

@@ -42,9 +42,26 @@ public class DefaultDeploymentInformation implements DeploymentInformation {
private final @Getter @NonNull ModuleIteration module;
private final @NonNull DeploymentProperties properties;
private final @Getter String buildNumber;
private final @Getter StagingRepository stagingRepositoryId;
public DefaultDeploymentInformation(ModuleIteration module, DeploymentProperties properties) {
this(module, properties, String.valueOf(LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)));
this(module, properties, StagingRepository.EMPTY);
}
public DefaultDeploymentInformation(ModuleIteration module, DeploymentProperties properties,
String stagingRepositoryId) {
this(module, properties, String.valueOf(LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)),
StagingRepository.of(stagingRepositoryId));
}
public DefaultDeploymentInformation(ModuleIteration module, DeploymentProperties properties,
StagingRepository stagingRepository) {
this(module, properties, String.valueOf(LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)), stagingRepository);
}
@Override
public DeploymentInformation withModule(ModuleIteration module) {
return new DefaultDeploymentInformation(module, properties, buildNumber, stagingRepositoryId);
}
/*

View File

@@ -21,48 +21,58 @@ import org.springframework.data.release.model.ModuleIteration;
/**
* @author Oliver Gierke
* @author Mark Paluch
*/
public interface DeploymentInformation {
/**
* Returns the name of the build.
*
*
* @return will never be {@literal null} or empty.
*/
String getBuildName();
/**
* Returns a unique build number for this particular deployment.
*
*
* @return will never be {@literal null} or empty.
*/
String getBuildNumber();
/**
* Returns the full URL to be used as deployment target.
*
*
* @return will never be {@literal null} or empty.
*/
String getDeploymentTargetUrl();
/**
* Returns the name of the repository to deploy to.
*
*
* @return will never be {@literal null} or empty.
*/
String getTargetRepository();
/**
* Staging repository identifier.
*
* @return
*/
StagingRepository getStagingRepositoryId();
/**
* Returns the {@link ModuleIteration} the deployment information was created for.
*
*
* @return
*/
ModuleIteration getModule();
/**
* Returns a {@link Map} to expand a URI template to access the build information.
*
*
* @return
*/
Map<String, Object> getBuildInfoParameters();
DeploymentInformation withModule(ModuleIteration module);
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2022 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.deployment;
import lombok.Value;
import org.springframework.util.ObjectUtils;
/**
* @author Mark Paluch
*/
@Value(staticConstructor = "of")
public class StagingRepository {
public static final StagingRepository EMPTY = StagingRepository.of("");
String id;
public boolean isEmpty() {
return ObjectUtils.isEmpty(id);
}
public boolean isPresent() {
return !isEmpty();
}
@Override
public String toString() {
if (isPresent()) {
return id;
}
return "(empty)";
}
}

View File

@@ -34,8 +34,12 @@ import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.apache.commons.io.FileUtils;
import org.eclipse.jgit.api.*;
import org.eclipse.jgit.api.AddCommand;
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CommitCommand;
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;
@@ -47,15 +51,29 @@ 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.*;
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.issues.TicketReference;
import org.springframework.data.release.issues.TicketStatus;
import org.springframework.data.release.model.*;
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.utils.ExecutionUtils;
import org.springframework.data.release.utils.Logger;
import org.springframework.data.util.Pair;
@@ -1101,7 +1119,7 @@ public class GitOperations {
for (CredentialItem item : items) {
if (item instanceof CharArrayType) {
((CharArrayType) item).setValueNoCopy(gpg.getPassphrase().toCharArray());
((CharArrayType) item).setValueNoCopy(gpg.getPassphrase().toString().toCharArray());
return true;
}

View File

@@ -19,6 +19,7 @@ import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
@@ -32,10 +33,14 @@ public class Gpg {
private String keyname;
private String executable;
private String passphrase;
private Password passphrase;
private String secretKeyring;
public boolean isGpgAvailable() {
return this.passphrase != null && StringUtils.hasText(secretKeyring);
return this.passphrase != null && StringUtils.hasText(keyname);
}
public boolean hasSecretKeyring() {
return !ObjectUtils.isEmpty(getSecretKeyring());
}
}

View File

@@ -15,7 +15,7 @@ jira.username=${JIRA_USERNAME}
jira.password=${JIRA_PASSWORD}
jira.url=${JIRA_URL}
gpg.keyname=9A2C7A98E457C53D
gpg.keyname=${GPG_KEYNAME}
gpg.passphrase=${PASSPHRASE}
gpg.secretKeyring=${GNUPGHOME}/secring.gpg
gpg.executable=/usr/bin/gpg