Copilot commented on code in PR #7123: URL: https://github.com/apache/incubator-kie/pull/7123#discussion_r4223227255
########## .ci/jenkins/Jenkinsfile.103xplus.promote: ########## @@ -0,0 +1,256 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +import org.jenkinsci.plugins.workflow.libs.Library +@Library('jenkins-pipeline-shared-libraries')_ + +import org.kie.jenkins.MavenCommand + +deployProperties = [:] +pipelineProperties = [:] + +pipeline { + agent { + docker { + image env.AGENT_DOCKER_BUILDER_IMAGE + args env.AGENT_DOCKER_BUILDER_ARGS + label util.avoidFaultyNodes() + } + } + + options { + timestamps() + timeout(time: 180, unit: 'MINUTES') + } + + environment { + DROOLS_CI_EMAIL_TO = credentials("${JENKINS_EMAIL_CREDS_ID}") + } + + stages { + stage('Initialization') { + steps { + script { + cleanWs() + + if (params.DISPLAY_NAME != '') { + currentBuild.displayName = params.DISPLAY_NAME + } + + readDeployProperties() + + assert getProjectVersion() + assert getBuildBranch() == util.getReleaseBranchFromVersion(getProjectVersion()) + } + } + } + + stage('Merge deploy PR and tag') { + steps { + script { + dir(getRepoName()) { + approveAndMergePR(getDeployPrLink()) + checkoutRepo() + tagLatest() + } + } + } + } + + stage('Create release') { + steps { + script { + dir(getRepoName()) { + checkoutRepo() + if (githubscm.isReleaseExist(getGitTag(), getGitAuthorCredsId())) { + githubscm.deleteRelease(getGitTag(), getGitAuthorPushCredsId()) + } + githubscm.createReleaseWithGeneratedReleaseNotes(getGitTag(), getBuildBranch(), githubscm.getPreviousTagFromVersion(getGitTag()), getGitAuthorPushCredsId()) + githubscm.updateReleaseBody(getGitTag(), getGitAuthorPushCredsId()) + } + } + } + } + + stage('Upload drools binaries and documentation') { + when { + expression { return isMainStream() } + } + steps { + script { + configFileProvider([configFile(fileId: env.MAVEN_SETTINGS_CONFIG_FILE_ID, variable: 'MAVEN_SETTINGS_FILE')]) { + getMavenCommand() + .withOptions(env.DROOLS_BUILD_MVN_OPTS ? [ env.DROOLS_BUILD_MVN_OPTS ] : []) + .withOptions(env.BUILD_MVN_OPTS_CURRENT ? [ env.BUILD_MVN_OPTS_CURRENT ] : []) + .inDirectory(getRepoName()) + .skipTests(true) + .withProperty('full') + .withSettingsXmlFile(MAVEN_SETTINGS_FILE) + .run('clean install') + } + uploadFileMgmt(getRepoName()) + } + } + } + } + + post { + unsuccessful { + sendNotification() + } + cleanup { + script { + util.cleanNode() + } + } + } +} + +void sendNotification() { + if (params.SEND_NOTIFICATION) { + mailer.sendMarkdownTestSummaryNotification('Promote', "[${getBuildBranch()}] Drools", [env.DROOLS_CI_EMAIL_TO]) + } else { + echo 'No notification sent per configuration' + } +} + +////////////////////////////////////////////////////////////////////////////// +// Deployment properties +////////////////////////////////////////////////////////////////////////////// + +void readDeployProperties() { + String deployUrl = params.DEPLOY_BUILD_URL + if (deployUrl != '') { + if (!deployUrl.endsWith('/')) { + deployUrl += '/' + } + sh "wget ${deployUrl}artifact/${PROPERTIES_FILE_NAME} -O ${PROPERTIES_FILE_NAME}" + deployProperties = readProperties file: PROPERTIES_FILE_NAME + // echo all properties + echo deployProperties.collect { entry -> "${entry.key}=${entry.value}" }.join('\n') + } +} + +boolean hasDeployProperty(String key) { + return deployProperties[key] != null +} + +String getDeployProperty(String key) { + if (hasDeployProperty(key)) { + return deployProperties[key] + } + return '' +} + +String getParamOrDeployProperty(String paramKey, String deployPropertyKey) { + if (params[paramKey] != '') { + return params[paramKey] + } + return getDeployProperty(deployPropertyKey) +} + +////////////////////////////////////////////////////////////////////////////// +// Getter / Setter +////////////////////////////////////////////////////////////////////////////// + +String getRepoName() { + return env.REPO_NAME +} + +String getProjectVersion() { + return getParamOrDeployProperty('PROJECT_VERSION', 'project.version') +} + +String getGitTag() { + return params.GIT_TAG != '' ? params.GIT_TAG : getProjectVersion() +} + +String getBuildBranch() { + return params.BUILD_BRANCH_NAME +} + +String getGitAuthor() { + return env.GIT_AUTHOR +} + +String getGitAuthorCredsId() { + return env.GIT_AUTHOR_CREDS_ID +} + +String getGitAuthorPushCredsId() { + return env.GIT_AUTHOR_PUSH_CREDS_ID +} + +String getDeployPrLink() { + return getDeployProperty("${getRepoName()}.pr.link") +} + +////////////////////////////////////////////////////////////////////////////// +// Git +////////////////////////////////////////////////////////////////////////////// + +void checkoutRepo() { + deleteDir() + checkout(githubscm.resolveRepository(getRepoName(), getGitAuthor(), getBuildBranch(), false, getGitAuthorCredsId())) + // need to manually checkout branch since on a detached branch after checkout command + sh "git checkout ${getBuildBranch()}" +} + +void approveAndMergePR(String prLink) { + if (prLink?.trim()) { + githubscm.approvePR(prLink, getGitAuthorPushCredsId()) + githubscm.mergePR(prLink, getGitAuthorPushCredsId()) + } +} + +void tagLatest() { + if (getGitTag()) { + githubscm.tagLocalAndRemoteRepository('origin', getGitTag(), getGitAuthorPushCredsId(), env.BUILD_TAG, true) Review Comment: `checkoutRepo()` selects `getBuildBranch()`, so this creates the final tag at the branch tip rather than the approved RC commit. The new RC script deliberately keeps the release-version commit only under its RC tag, leaving the development branch unchanged. Promotion can therefore tag snapshot POMs or later, unvoted changes. Accept the winning RC tag and delegate final-tag creation to `05-tag-release.sh`; do not keep a second branch-tip tagging path. ########## .ci/jenkins/Jenkinsfile.103xplus.deploy: ########## @@ -0,0 +1,327 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +import org.jenkinsci.plugins.workflow.libs.Library +@Library('jenkins-pipeline-shared-libraries')_ + +import org.kie.jenkins.MavenCommand + +deployProperties = [:] + +pipeline { + agent { + docker { + image env.AGENT_DOCKER_BUILDER_IMAGE + args env.AGENT_DOCKER_BUILDER_ARGS + label util.avoidFaultyNodes() + } + } + + options { + timestamps() + timeout(time: 180, unit: 'MINUTES') + } + + environment { + DROOLS_CI_EMAIL_TO = credentials("${JENKINS_EMAIL_CREDS_ID}") + + PR_BRANCH_HASH = "${util.generateHash(10)}" + } + + stages { + stage('Initialize') { + steps { + script { + cleanWs(disableDeferredWipeout: true) + + if (params.DISPLAY_NAME) { + currentBuild.displayName = params.DISPLAY_NAME + } + + if (isRelease() || isCreatePr()) { + // Verify version is set + assert getProjectVersion() + + if (isRelease()) { + // Verify if on right release branch + assert getBuildBranch() == util.getReleaseBranchFromVersion(getProjectVersion()) + } + } + + dir(getRepoName()) { + checkoutRepo() + } + } + } + post { + success { + script { + setDeployPropertyIfNeeded('git.branch', getBuildBranch()) + setDeployPropertyIfNeeded('git.author', getGitAuthor()) + setDeployPropertyIfNeeded('project.version', getProjectVersion()) + setDeployPropertyIfNeeded('release', isRelease()) + } + } + } + } + + stage('Prepare for PR') { + when { + expression { return isCreatePr() } + } + steps { + script { + dir(getRepoName()) { + if (githubscm.isBranchExist('origin', getPRBranch())) { + githubscm.removeRemoteBranch('origin', getPRBranch(), getGitAuthorPushCredsId()) + } + githubscm.createBranch(getPRBranch()) + } + } + } + } + + stage('Update project version') { + when { + expression { return getProjectVersion() } + } + steps { + script { + configFileProvider([configFile(fileId: env.MAVEN_SETTINGS_CONFIG_FILE_ID, variable: 'MAVEN_SETTINGS_FILE')]) { + maven.mvnVersionsSet( + getMavenCommand().withSettingsXmlFile(MAVEN_SETTINGS_FILE), + getProjectVersion(), + !isRelease() + ) + } + } + } + } + + stage('Build & Deploy repo') { + steps { + script { + withCredentials([usernamePassword(credentialsId: env.MAVEN_REPO_CREDS_ID, usernameVariable: 'REPOSITORY_USER', passwordVariable: 'REPOSITORY_TOKEN')]) { + def installOrDeploy + if (shouldDeployToRepository()) { + installOrDeploy = "deploy -DdeployAtEnd -Dapache.repository.username=${REPOSITORY_USER} -Dapache.repository.password=${REPOSITORY_TOKEN} -DretryFailedDeploymentCount=5" + } else { + installOrDeploy = 'install' + } + def mavenCommand = getMavenCommand() + .withOptions(env.DROOLS_BUILD_MVN_OPTS ? [ env.DROOLS_BUILD_MVN_OPTS ] : []) + .withOptions(env.BUILD_MVN_OPTS_CURRENT ? [ env.BUILD_MVN_OPTS_CURRENT ] : []) + .withProperty('maven.test.failure.ignore', true) + .skipTests(params.SKIP_TESTS) + + if (isRelease()) { + releaseUtils.gpgImportKeyFromStringWithoutPassword(getReleaseGpgSignKeyCredsId()) + mavenCommand + .withProfiles(['apache-release']) + .withProperty('only.reproducible') + } + + configFileProvider([configFile(fileId: env.MAVEN_SETTINGS_CONFIG_FILE_ID, variable: 'MAVEN_SETTINGS_FILE')]) { + mavenCommand.withSettingsXmlFile(MAVEN_SETTINGS_FILE).run("clean $installOrDeploy") + } + } + } + } + post { + always { + script { + saveReports() + util.archiveConsoleLog() + } + } + } + } + + stage('Create PR') { + when { + expression { return isCreatePr() } + } + steps { + script { + dir(getRepoName()) { + if (githubscm.isThereAnyChanges()) { + commitAndCreatePR() + } else { + println '[WARN] no changes to commit' + } + } + } + } + post { + success { + script { + setDeployPropertyIfNeeded("${getRepoName()}.pr.source.uri", "https://github.com/${getGitAuthor()}/${getRepoName()}") + setDeployPropertyIfNeeded("${getRepoName()}.pr.source.ref", getPRBranch()) + setDeployPropertyIfNeeded("${getRepoName()}.pr.target.uri", "https://github.com/${getGitAuthor()}/${getRepoName()}") + setDeployPropertyIfNeeded("${getRepoName()}.pr.target.ref", getBuildBranch()) + } + } + } + } + + stage('Commit and Create Tag') { + when { + expression { return isRelease() } + } + steps { + script { + dir(getRepoName()) { + if (githubscm.isThereAnyChanges()) { + def commitMsg = "[${getBuildBranch()}] Update version to ${getProjectVersion()}" + githubscm.setUserConfigFromCreds(getGitAuthorPushCredsId()) + githubscm.commitChanges(commitMsg, { + githubscm.findAndStageNotIgnoredFiles('pom.xml') + githubscm.findAndStageNotIgnoredFiles('antora.yml') + }) + } else { + println '[WARN] no changes to commit' + } + githubscm.tagRepository(getGitTagName()) + githubscm.pushRemoteTag('origin', getGitTagName(), getGitAuthorPushCredsId()) Review Comment: Issue #7140 requires Jenkins to call the local scripts instead of embedding release logic, but this new deploy pipeline still updates versions, builds, signs, commits, and pushes tags through Groovy/shared-library calls. It never invokes the local release workflow, leaving two separate release implementations. Delegate release mode to `release-all.sh` and standalone version updates to `01-update-version.sh`, while preserving credential setup and the non-release/PR-generation modes. ########## .ci/jenkins/Jenkinsfile.103xplus.promote: ########## @@ -0,0 +1,256 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +import org.jenkinsci.plugins.workflow.libs.Library +@Library('jenkins-pipeline-shared-libraries')_ + +import org.kie.jenkins.MavenCommand + +deployProperties = [:] +pipelineProperties = [:] + +pipeline { + agent { + docker { + image env.AGENT_DOCKER_BUILDER_IMAGE + args env.AGENT_DOCKER_BUILDER_ARGS + label util.avoidFaultyNodes() + } + } + + options { + timestamps() + timeout(time: 180, unit: 'MINUTES') + } + + environment { + DROOLS_CI_EMAIL_TO = credentials("${JENKINS_EMAIL_CREDS_ID}") + } + + stages { + stage('Initialization') { + steps { + script { + cleanWs() + + if (params.DISPLAY_NAME != '') { + currentBuild.displayName = params.DISPLAY_NAME + } + + readDeployProperties() + + assert getProjectVersion() + assert getBuildBranch() == util.getReleaseBranchFromVersion(getProjectVersion()) + } + } + } + + stage('Merge deploy PR and tag') { + steps { + script { + dir(getRepoName()) { + approveAndMergePR(getDeployPrLink()) + checkoutRepo() + tagLatest() + } + } + } + } + + stage('Create release') { + steps { + script { + dir(getRepoName()) { + checkoutRepo() + if (githubscm.isReleaseExist(getGitTag(), getGitAuthorCredsId())) { + githubscm.deleteRelease(getGitTag(), getGitAuthorPushCredsId()) + } + githubscm.createReleaseWithGeneratedReleaseNotes(getGitTag(), getBuildBranch(), githubscm.getPreviousTagFromVersion(getGitTag()), getGitAuthorPushCredsId()) + githubscm.updateReleaseBody(getGitTag(), getGitAuthorPushCredsId()) + } + } + } + } + + stage('Upload drools binaries and documentation') { + when { + expression { return isMainStream() } + } + steps { + script { + configFileProvider([configFile(fileId: env.MAVEN_SETTINGS_CONFIG_FILE_ID, variable: 'MAVEN_SETTINGS_FILE')]) { + getMavenCommand() + .withOptions(env.DROOLS_BUILD_MVN_OPTS ? [ env.DROOLS_BUILD_MVN_OPTS ] : []) + .withOptions(env.BUILD_MVN_OPTS_CURRENT ? [ env.BUILD_MVN_OPTS_CURRENT ] : []) + .inDirectory(getRepoName()) + .skipTests(true) + .withProperty('full') + .withSettingsXmlFile(MAVEN_SETTINGS_FILE) + .run('clean install') + } + uploadFileMgmt(getRepoName()) + } + } + } + } + + post { + unsuccessful { + sendNotification() + } + cleanup { + script { + util.cleanNode() + } + } + } +} + +void sendNotification() { + if (params.SEND_NOTIFICATION) { + mailer.sendMarkdownTestSummaryNotification('Promote', "[${getBuildBranch()}] Drools", [env.DROOLS_CI_EMAIL_TO]) + } else { + echo 'No notification sent per configuration' + } +} + +////////////////////////////////////////////////////////////////////////////// +// Deployment properties +////////////////////////////////////////////////////////////////////////////// + +void readDeployProperties() { + String deployUrl = params.DEPLOY_BUILD_URL + if (deployUrl != '') { + if (!deployUrl.endsWith('/')) { + deployUrl += '/' + } + sh "wget ${deployUrl}artifact/${PROPERTIES_FILE_NAME} -O ${PROPERTIES_FILE_NAME}" + deployProperties = readProperties file: PROPERTIES_FILE_NAME + // echo all properties + echo deployProperties.collect { entry -> "${entry.key}=${entry.value}" }.join('\n') + } +} + +boolean hasDeployProperty(String key) { + return deployProperties[key] != null +} + +String getDeployProperty(String key) { + if (hasDeployProperty(key)) { + return deployProperties[key] + } + return '' +} + +String getParamOrDeployProperty(String paramKey, String deployPropertyKey) { + if (params[paramKey] != '') { + return params[paramKey] + } + return getDeployProperty(deployPropertyKey) +} + +////////////////////////////////////////////////////////////////////////////// +// Getter / Setter +////////////////////////////////////////////////////////////////////////////// + +String getRepoName() { + return env.REPO_NAME +} + +String getProjectVersion() { + return getParamOrDeployProperty('PROJECT_VERSION', 'project.version') +} + +String getGitTag() { + return params.GIT_TAG != '' ? params.GIT_TAG : getProjectVersion() +} + +String getBuildBranch() { + return params.BUILD_BRANCH_NAME +} + +String getGitAuthor() { + return env.GIT_AUTHOR +} + +String getGitAuthorCredsId() { + return env.GIT_AUTHOR_CREDS_ID +} + +String getGitAuthorPushCredsId() { + return env.GIT_AUTHOR_PUSH_CREDS_ID +} + +String getDeployPrLink() { + return getDeployProperty("${getRepoName()}.pr.link") +} + +////////////////////////////////////////////////////////////////////////////// +// Git +////////////////////////////////////////////////////////////////////////////// + +void checkoutRepo() { + deleteDir() + checkout(githubscm.resolveRepository(getRepoName(), getGitAuthor(), getBuildBranch(), false, getGitAuthorCredsId())) + // need to manually checkout branch since on a detached branch after checkout command + sh "git checkout ${getBuildBranch()}" +} + +void approveAndMergePR(String prLink) { + if (prLink?.trim()) { + githubscm.approvePR(prLink, getGitAuthorPushCredsId()) + githubscm.mergePR(prLink, getGitAuthorPushCredsId()) + } +} + +void tagLatest() { + if (getGitTag()) { + githubscm.tagLocalAndRemoteRepository('origin', getGitTag(), getGitAuthorPushCredsId(), env.BUILD_TAG, true) + } +} + +MavenCommand getMavenCommand() { + mvnCmd = new MavenCommand(this, ['-fae', '-ntp']) + .withOptions(env.BUILD_MVN_OPTS ? [ env.BUILD_MVN_OPTS ] : []) + if (env.MAVEN_DEPENDENCIES_REPOSITORY) { + mvnCmd.withDependencyRepositoryInSettings('deps-repo', env.MAVEN_DEPENDENCIES_REPOSITORY) + } + return mvnCmd +} + +void uploadFileMgmt(String directory) { + if (isNotTestingBuild()) { + echo "upload binaries and docs for ${directory}" + dir(directory) { + sshagent(['drools-filemgmt']) { + sh "script/release/upload_filemgmt.sh ${getProjectVersion()}" Review Comment: `script/release/upload_filemgmt.sh` is absent from this checkout. When `DROOLS_STREAM` is `main` and the Git author is `apache`, this command fails after the GitHub release has already been created, leaving distribution publishing incomplete. Supply the upload implementation or invoke an existing publishing mechanism before enabling this stage. ########## script/release/02-rc-commit.sh: ########## @@ -0,0 +1,220 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +set -euo pipefail + +# Creates the "R commit" (release commit) and the corresponding RC git tag. +# +# The R commit contains only the version bump to the exact release version. +# It is made on a short-lived local release branch that is never pushed — only +# the tag is pushed. This mirrors the branch strategy from release.txt: +# +# 10.2.x branch ──── c ──── c ──── [D commit] ──── c ────> ... +# | +# (local release branch) +# └── [R commit] ──> tag: 10.3.0-rc1 +# +# Usage: +# ./script/release/02-rc-commit.sh <version> [--tag <tag-name>] [--push] [--dry-run] +# ./script/release/02-rc-commit.sh --version <release-version> --tag <tag-name> +# +# Examples: +# ./script/release/02-rc-commit.sh 10.3.0 --tag 10.3.0-rc1 +# ./script/release/02-rc-commit.sh --version 10.3.0 --tag 10.3.0-rc2 --push +# +# Options: +# <version> | --version <ver> Exact release version, e.g. 10.3.0 +# --tag | --rc-tag <tag> Git tag name, e.g. 10.3.0-rc1 (defaults to <version>-rc1) +# --rc Accepted for parity +# --push | --push-tag Push the tag to origin (default: local only) +# --dry-run Print what would happen without making any changes + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +RELEASE_VERSION="" +TAG_NAME="" +PUSH=false +DRY_RUN=false + +while [[ $# -gt 0 ]]; do + case $1 in + --version) + RELEASE_VERSION="${2:-}" + shift 2 + ;; + --tag|--rc-tag) + TAG_NAME="${2:-}" + shift 2 + ;; + --rc) + shift + ;; + --push|--push-tag) + PUSH=true + shift + ;; + --dry-run) + DRY_RUN=true + shift + ;; + *) + if [[ -z "${RELEASE_VERSION}" && ! "$1" =~ ^- ]]; then + RELEASE_VERSION="$1" + shift + else + echo "Unknown option: $1" + echo "Usage: $0 [<version> | --version <version>] [--tag <tag>] [--push] [--dry-run]" + exit 1 + fi + ;; + esac +done + +if [[ -z "${RELEASE_VERSION}" ]]; then + echo "ERROR: Version is required." + echo "Usage: $0 10.3.0 [--tag 10.3.0-rc1] [--push] [--dry-run]" + exit 1 +fi + +if [[ -z "${TAG_NAME}" ]]; then + TAG_NAME="${RELEASE_VERSION}-rc1" +fi + +# Derive the expected development branch name (e.g. 10.3.0 → 10.3.x). +STREAM_BRANCH="$(echo "${RELEASE_VERSION}" | sed 's/^\([0-9]*\.[0-9]*\)\..*/\1.x/')" +RELEASE_BRANCH="release/${TAG_NAME}" + +cd "${REPO_ROOT}" + +CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)" + +echo "========================================" +echo "Apache KIE repo — RC commit + tag" +echo "Release version : ${RELEASE_VERSION}" +echo "Tag : ${TAG_NAME}" +echo "Release branch : ${RELEASE_BRANCH} (local only)" +echo "Push tag : ${PUSH}" +echo "Dry run : ${DRY_RUN}" +echo "Current branch : ${CURRENT_BRANCH}" +echo "========================================" + +# Warn if we're not on the expected stream branch (not a fatal error — someone +# may legitimately be on a different commit). +if [[ "${CURRENT_BRANCH}" != "${STREAM_BRANCH}" ]]; then + echo "" + echo "WARNING: Current branch '${CURRENT_BRANCH}' is not the expected stream" + echo " branch '${STREAM_BRANCH}'. Proceed with caution." + echo "" +fi + +# Check there are no uncommitted changes before we do anything. +if ! git diff --quiet || ! git diff --cached --quiet; then Review Comment: These checks ignore untracked files, but the later `git add -A` includes them in the release commit. An unrelated, non-ignored local file can therefore be committed and pushed with the RC tag despite passing the clean-tree guard. Include untracked files in this check before creating the release branch. ########## .ci/jenkins/project/Jenkinsfile.103xplus.release: ########## @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +import org.jenkinsci.plugins.workflow.libs.Library +@Library('jenkins-pipeline-shared-libraries')_ + +pipeline { + agent { + label util.avoidFaultyNodes('ubuntu') + } + + options { + timestamps() + timeout(time: 360, unit: 'MINUTES') + } + + environment { + DROOLS_CI_EMAIL_TO = credentials("${JENKINS_EMAIL_CREDS_ID}") + } + + parameters { + string(name: 'RELEASE_VERSION', defaultValue: '', description: 'Exact release version, e.g. 10.3.0', trim: true) + string(name: 'TAG_NAME', defaultValue: '', description: 'RC or Release tag name, e.g. 10.3.0-rc1', trim: true) + booleanParam(name: 'SKIP_TESTS', defaultValue: true, description: 'Skip running tests during reactor build') + booleanParam(name: 'DEPLOY', defaultValue: true, description: 'Deploy artifacts to Apache Nexus staging') + string(name: 'STAGING_URL', defaultValue: '', description: 'Custom Nexus staging URL (optional)') + booleanParam(name: 'PUSH_TAG', defaultValue: true, description: 'Push the git tag to origin') + string(name: 'EXTRA_MAVEN_OPTS', defaultValue: '', description: 'Extra Maven CLI options to pass to build.sh') + booleanParam(name: 'DRY_RUN', defaultValue: false, description: 'Dry run without making remote git or staging changes') + booleanParam(name: 'SEND_NOTIFICATION', defaultValue: false, description: 'Send email notification on failure') + } + + stages { + stage('Initialize & Validate') { + steps { + script { + assert params.RELEASE_VERSION : 'RELEASE_VERSION parameter is mandatory' + assert params.TAG_NAME : 'TAG_NAME parameter is mandatory' + currentBuild.displayName = "Release ${params.RELEASE_VERSION} (${params.TAG_NAME})" + } + } + } + + stage('Execute Release Scripts') { + steps { + script { + def releaseCmd = "./script/release/release-all.sh --version ${params.RELEASE_VERSION} --tag ${params.TAG_NAME}" + + if (params.SKIP_TESTS) { + releaseCmd += ' --skip-tests' + } + if (params.DEPLOY) { + releaseCmd += ' --deploy' + } + if (params.STAGING_URL?.trim()) { + releaseCmd += " --staging-url \"${params.STAGING_URL.trim()}\"" + } + if (params.PUSH_TAG) { + releaseCmd += ' --push-tag' + } + if (params.EXTRA_MAVEN_OPTS?.trim()) { + releaseCmd += " --maven-opts \"${params.EXTRA_MAVEN_OPTS.trim()}\"" + } + if (params.DRY_RUN) { + releaseCmd += ' --dry-run' + } + + sh """#!/usr/bin/env bash + set -euo pipefail + chmod +x script/release/*.sh + ${releaseCmd} Review Comment: This invokes the scripts without Maven settings, a signing-key setup, Git author configuration, or authentication for their raw `git push`. Only the notification email is bound. On an agent without preconfigured release credentials, the default `DEPLOY=true` / `PUSH_TAG=true` workflow cannot complete. Bind the managed Maven settings as `MAVEN_SETTINGS`, provision the GPG key and any required passphrase, and configure Git identity and push authentication around this invocation, preserving the setup used by the existing deploy pipeline. ########## .ci/jenkins/Jenkinsfile.103xplus.deploy: ########## @@ -0,0 +1,327 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +import org.jenkinsci.plugins.workflow.libs.Library +@Library('jenkins-pipeline-shared-libraries')_ + +import org.kie.jenkins.MavenCommand + +deployProperties = [:] + +pipeline { + agent { + docker { + image env.AGENT_DOCKER_BUILDER_IMAGE + args env.AGENT_DOCKER_BUILDER_ARGS + label util.avoidFaultyNodes() + } + } + + options { + timestamps() + timeout(time: 180, unit: 'MINUTES') + } + + environment { + DROOLS_CI_EMAIL_TO = credentials("${JENKINS_EMAIL_CREDS_ID}") + + PR_BRANCH_HASH = "${util.generateHash(10)}" + } + + stages { + stage('Initialize') { + steps { + script { + cleanWs(disableDeferredWipeout: true) + + if (params.DISPLAY_NAME) { + currentBuild.displayName = params.DISPLAY_NAME + } + + if (isRelease() || isCreatePr()) { + // Verify version is set + assert getProjectVersion() + + if (isRelease()) { + // Verify if on right release branch + assert getBuildBranch() == util.getReleaseBranchFromVersion(getProjectVersion()) + } + } + + dir(getRepoName()) { + checkoutRepo() + } + } + } + post { + success { + script { + setDeployPropertyIfNeeded('git.branch', getBuildBranch()) + setDeployPropertyIfNeeded('git.author', getGitAuthor()) + setDeployPropertyIfNeeded('project.version', getProjectVersion()) + setDeployPropertyIfNeeded('release', isRelease()) + } + } + } + } + + stage('Prepare for PR') { + when { + expression { return isCreatePr() } + } + steps { + script { + dir(getRepoName()) { + if (githubscm.isBranchExist('origin', getPRBranch())) { + githubscm.removeRemoteBranch('origin', getPRBranch(), getGitAuthorPushCredsId()) + } + githubscm.createBranch(getPRBranch()) + } + } + } + } + + stage('Update project version') { + when { + expression { return getProjectVersion() } + } + steps { + script { + configFileProvider([configFile(fileId: env.MAVEN_SETTINGS_CONFIG_FILE_ID, variable: 'MAVEN_SETTINGS_FILE')]) { + maven.mvnVersionsSet( + getMavenCommand().withSettingsXmlFile(MAVEN_SETTINGS_FILE), + getProjectVersion(), + !isRelease() + ) + } + } + } + } + + stage('Build & Deploy repo') { + steps { + script { + withCredentials([usernamePassword(credentialsId: env.MAVEN_REPO_CREDS_ID, usernameVariable: 'REPOSITORY_USER', passwordVariable: 'REPOSITORY_TOKEN')]) { + def installOrDeploy + if (shouldDeployToRepository()) { + installOrDeploy = "deploy -DdeployAtEnd -Dapache.repository.username=${REPOSITORY_USER} -Dapache.repository.password=${REPOSITORY_TOKEN} -DretryFailedDeploymentCount=5" + } else { + installOrDeploy = 'install' + } + def mavenCommand = getMavenCommand() + .withOptions(env.DROOLS_BUILD_MVN_OPTS ? [ env.DROOLS_BUILD_MVN_OPTS ] : []) + .withOptions(env.BUILD_MVN_OPTS_CURRENT ? [ env.BUILD_MVN_OPTS_CURRENT ] : []) + .withProperty('maven.test.failure.ignore', true) + .skipTests(params.SKIP_TESTS) + + if (isRelease()) { + releaseUtils.gpgImportKeyFromStringWithoutPassword(getReleaseGpgSignKeyCredsId()) + mavenCommand + .withProfiles(['apache-release']) + .withProperty('only.reproducible') + } + + configFileProvider([configFile(fileId: env.MAVEN_SETTINGS_CONFIG_FILE_ID, variable: 'MAVEN_SETTINGS_FILE')]) { + mavenCommand.withSettingsXmlFile(MAVEN_SETTINGS_FILE).run("clean $installOrDeploy") + } + } + } + } + post { + always { + script { + saveReports() + util.archiveConsoleLog() + } + } + } + } + + stage('Create PR') { + when { + expression { return isCreatePr() } + } + steps { + script { + dir(getRepoName()) { + if (githubscm.isThereAnyChanges()) { + commitAndCreatePR() + } else { + println '[WARN] no changes to commit' + } + } + } + } + post { + success { + script { + setDeployPropertyIfNeeded("${getRepoName()}.pr.source.uri", "https://github.com/${getGitAuthor()}/${getRepoName()}") + setDeployPropertyIfNeeded("${getRepoName()}.pr.source.ref", getPRBranch()) + setDeployPropertyIfNeeded("${getRepoName()}.pr.target.uri", "https://github.com/${getGitAuthor()}/${getRepoName()}") + setDeployPropertyIfNeeded("${getRepoName()}.pr.target.ref", getBuildBranch()) + } + } + } + } + + stage('Commit and Create Tag') { + when { + expression { return isRelease() } + } + steps { + script { + dir(getRepoName()) { + if (githubscm.isThereAnyChanges()) { + def commitMsg = "[${getBuildBranch()}] Update version to ${getProjectVersion()}" + githubscm.setUserConfigFromCreds(getGitAuthorPushCredsId()) + githubscm.commitChanges(commitMsg, { + githubscm.findAndStageNotIgnoredFiles('pom.xml') + githubscm.findAndStageNotIgnoredFiles('antora.yml') + }) + } else { + println '[WARN] no changes to commit' + } + githubscm.tagRepository(getGitTagName()) + githubscm.pushRemoteTag('origin', getGitTagName(), getGitAuthorPushCredsId()) + } + } + } + } + } + + post { + always { + script { + setDeployPropertyIfNeeded("${getRepoName()}.deploy.build.run", true) + saveDeployProperties() + } + } + unsuccessful { + sendNotification() + } + cleanup { + cleanWs(notFailBuild: true, deleteDirs: true, patterns: [[pattern: 'deploy.properties', type: 'EXCLUDE']]) + } + } +} + +void saveReports() { + junit testResults: '**/target/surefire-reports/**/*.xml, **/target/failsafe-reports/**/*.xml, **/target/invoker-reports/**/TEST-*.xml', allowEmptyResults: true +} + +void checkoutRepo() { + deleteDir() + checkout(githubscm.resolveRepository(getRepoName(), getGitAuthor(), getBuildBranch(), false, getGitAuthorCredsId())) + // need to manually checkout branch since on a detached branch after checkout command + sh "git checkout ${getBuildBranch()}" +} + +void commitAndCreatePR() { + def commitMsg = "[${getBuildBranch()}] Update version to ${getProjectVersion()}" + def prBody = "Generated by build ${BUILD_TAG}: ${BUILD_URL}.\nPlease do not merge, it should be merged automatically." + githubscm.setUserConfigFromCreds(getGitAuthorPushCredsId()) + githubscm.commitChanges(commitMsg, { + githubscm.findAndStageNotIgnoredFiles('pom.xml') + githubscm.findAndStageNotIgnoredFiles('antora.yml') + }) + githubscm.pushObject('origin', getPRBranch(), getGitAuthorPushCredsId()) + deployProperties["${getRepoName()}.pr.link"] = githubscm.createPRWithLabels(commitMsg, prBody, getBuildBranch(), ['skip-ci'] as String[], getGitAuthorCredsId()) +} + +void sendNotification() { + if (params.SEND_NOTIFICATION) { + mailer.sendMarkdownTestSummaryNotification('Deploy', "[${getBuildBranch()}] Drools", [env.DROOLS_CI_EMAIL_TO]) + } else { + echo 'No notification sent per configuration' + } +} + +////////////////////////////////////////////////////////////////////////////// +// Deployment properties +////////////////////////////////////////////////////////////////////////////// + +void setDeployPropertyIfNeeded(String key, def value) { + if (value != null && value != '') { + deployProperties[key] = value + } +} + +void saveDeployProperties(String propertiesFileName = 'deploy.properties') { Review Comment: With the existing job configuration, the producer and consumer use different artifact names. `.ci/jenkins/dsl/jobs.groovy` sets `PROPERTIES_FILE_NAME` to `deployment.properties`, and the new promote pipeline fetches that name, but this method archives `deploy.properties`. Promotion through `DEPLOY_BUILD_URL` therefore requests a missing artifact. Use the configured filename when writing and archiving the properties. ########## script/release/04-deploy-to-staging.sh: ########## @@ -0,0 +1,164 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +set -euo pipefail + +# Deploys the locally-built JARs (already in ~/.m2) to a Nexus staging +# repository for Apache release voting. +# +# By default this script runs in DRY RUN mode and will NOT push anything +# remotely. Pass --deploy to actually upload to Nexus. +# +# Usage: +# ./script/release/deploy-to-staging.sh --tag <rc-tag> [--deploy] [--staging-url <url>] +# +# Examples: +# # dry run (safe — prints the Maven command that would be run) +# ./script/release/deploy-to-staging.sh --tag 10.3.0-rc1 +# +# # actually deploy to Apache Nexus staging +# ./script/release/deploy-to-staging.sh --tag 10.3.0-rc1 --deploy +# +# # deploy to a custom staging URL +# ./script/release/deploy-to-staging.sh --tag 10.3.0-rc1 --deploy \ +# --staging-url https://repository.apache.org/service/local/staging/deploy/maven2 +# +# Environment variables consumed when --deploy is active: +# MAVEN_SETTINGS Path to a settings.xml with Nexus credentials (required). +# The settings file must define a server with id "apache.releases.https" +# (or the id set via --server-id) carrying the deployer credentials. +# MAVEN_GPG_PASSPHRASE GPG passphrase for signing (required for Apache releases). + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +TAG_NAME="" +DEPLOY=false +STAGING_URL="https://repository.apache.org/service/local/staging/deploy/maven2" +SERVER_ID="apache.releases.https" + +while [[ $# -gt 0 ]]; do + case $1 in + --tag) + TAG_NAME="${2:-}" + shift 2 + ;; + --deploy) + DEPLOY=true + shift + ;; + --staging-url) + STAGING_URL="${2:-}" + shift 2 + ;; + --server-id) + SERVER_ID="${2:-}" + shift 2 + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 --tag <rc-tag> [--deploy] [--staging-url <url>] [--server-id <id>]" + exit 1 + ;; + esac +done + +if [[ -z "${TAG_NAME}" ]]; then + echo "ERROR: --tag is required." + echo "Usage: $0 --tag 10.3.0-rc1 [--deploy]" + exit 1 +fi + +cd "${REPO_ROOT}" + +# ── Resolve the version from the tag name ──────────────────────────────────── +# Tag format: <version>-rc<N> (e.g. 10.3.0-rc1) +RELEASE_VERSION="$(echo "${TAG_NAME}" | sed 's/-rc[0-9]*$//')" + +echo "========================================" +echo "Apache KIE repo — deploy to staging" +echo "Tag : ${TAG_NAME}" +echo "Release version : ${RELEASE_VERSION}" +echo "Staging URL : ${STAGING_URL}" +echo "Server ID : ${SERVER_ID}" +echo "Deploy (real) : ${DEPLOY}" +echo "========================================" + +# ── Verify we are on / can resolve the RC tag ──────────────────────────────── +if ! git rev-parse "${TAG_NAME}" &>/dev/null; then + echo "" + echo "ERROR: Git tag '${TAG_NAME}' not found in this repository." + echo " Run 02-rc-commit.sh first, or check out the tag manually." + exit 1 +fi + +# ── Assemble Maven flags ───────────────────────────────────────────────────── + +DEPLOY_MVN_FLAGS=( + "-DskipTests" + "-Dfull" + "-DaltDeploymentRepository=${SERVER_ID}::default::${STAGING_URL}" +) + +if [[ -n "${MAVEN_SETTINGS:-}" ]]; then + DEPLOY_MVN_FLAGS+=("-s" "${MAVEN_SETTINGS}") +fi + +if [[ -n "${MAVEN_GPG_PASSPHRASE:-}" ]]; then + DEPLOY_MVN_FLAGS+=("-Dgpg.passphrase=${MAVEN_GPG_PASSPHRASE}") Review Comment: When `MAVEN_GPG_PASSPHRASE` is set, this adds its plaintext value to Maven's arguments. The default dry run prints the entire array at line 133, exposing the signing secret in captured console output; a real deployment also exposes it through process arguments. Use the inherited GPG plugin's supported environment or protected-settings mechanism instead of a plaintext command-line property, and keep the secret out of displayed commands. ########## script/release/04-deploy-to-staging.sh: ########## @@ -0,0 +1,164 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# + +set -euo pipefail + +# Deploys the locally-built JARs (already in ~/.m2) to a Nexus staging +# repository for Apache release voting. +# +# By default this script runs in DRY RUN mode and will NOT push anything +# remotely. Pass --deploy to actually upload to Nexus. +# +# Usage: +# ./script/release/deploy-to-staging.sh --tag <rc-tag> [--deploy] [--staging-url <url>] +# +# Examples: +# # dry run (safe — prints the Maven command that would be run) +# ./script/release/deploy-to-staging.sh --tag 10.3.0-rc1 +# +# # actually deploy to Apache Nexus staging +# ./script/release/deploy-to-staging.sh --tag 10.3.0-rc1 --deploy +# +# # deploy to a custom staging URL +# ./script/release/deploy-to-staging.sh --tag 10.3.0-rc1 --deploy \ +# --staging-url https://repository.apache.org/service/local/staging/deploy/maven2 +# +# Environment variables consumed when --deploy is active: +# MAVEN_SETTINGS Path to a settings.xml with Nexus credentials (required). +# The settings file must define a server with id "apache.releases.https" +# (or the id set via --server-id) carrying the deployer credentials. +# MAVEN_GPG_PASSPHRASE GPG passphrase for signing (required for Apache releases). + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +TAG_NAME="" +DEPLOY=false +STAGING_URL="https://repository.apache.org/service/local/staging/deploy/maven2" +SERVER_ID="apache.releases.https" + +while [[ $# -gt 0 ]]; do + case $1 in + --tag) + TAG_NAME="${2:-}" + shift 2 + ;; + --deploy) + DEPLOY=true + shift + ;; + --staging-url) + STAGING_URL="${2:-}" + shift 2 + ;; + --server-id) + SERVER_ID="${2:-}" + shift 2 + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 --tag <rc-tag> [--deploy] [--staging-url <url>] [--server-id <id>]" + exit 1 + ;; + esac +done + +if [[ -z "${TAG_NAME}" ]]; then + echo "ERROR: --tag is required." + echo "Usage: $0 --tag 10.3.0-rc1 [--deploy]" + exit 1 +fi + +cd "${REPO_ROOT}" + +# ── Resolve the version from the tag name ──────────────────────────────────── +# Tag format: <version>-rc<N> (e.g. 10.3.0-rc1) +RELEASE_VERSION="$(echo "${TAG_NAME}" | sed 's/-rc[0-9]*$//')" + +echo "========================================" +echo "Apache KIE repo — deploy to staging" +echo "Tag : ${TAG_NAME}" +echo "Release version : ${RELEASE_VERSION}" +echo "Staging URL : ${STAGING_URL}" +echo "Server ID : ${SERVER_ID}" +echo "Deploy (real) : ${DEPLOY}" +echo "========================================" + +# ── Verify we are on / can resolve the RC tag ──────────────────────────────── +if ! git rev-parse "${TAG_NAME}" &>/dev/null; then + echo "" + echo "ERROR: Git tag '${TAG_NAME}' not found in this repository." + echo " Run 02-rc-commit.sh first, or check out the tag manually." + exit 1 +fi + +# ── Assemble Maven flags ───────────────────────────────────────────────────── + +DEPLOY_MVN_FLAGS=( + "-DskipTests" + "-Dfull" Review Comment: The Maven command never activates `apache-release`, which the existing deploy pipeline enables for artifact signing. Setting `gpg.passphrase` alone does not run the signing goal, so the default script stages unsigned artifacts instead of the signed release artifacts it promises. Activate the release profile here; signing-key provisioning must also be supplied by the caller. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
