diff --git a/.ci/ci-image/entrypoint.sh b/.ci/ci-image/entrypoint.sh new file mode 100755 index 000000000000..835472d83b91 --- /dev/null +++ b/.ci/ci-image/entrypoint.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# cgroup v2: enable nesting +if [ -f /sys/fs/cgroup/cgroup.controllers ]; then + echo "in cgroupv2 branch" + # move the processes from the root group to the /init group, + # otherwise writing subtree_control fails with EBUSY. + # An error during moving non-existent process (i.e., "cat") is ignored. + sudo mkdir -p /sys/fs/cgroup/init + sudo bash -c "xargs -rn1 < /sys/fs/cgroup/cgroup.procs > /sys/fs/cgroup/init/cgroup.procs || :" + # enable controllers + sudo bash -c "sed -e 's/ / +/g' -e 's/^/+/' < /sys/fs/cgroup/cgroup.controllers > /sys/fs/cgroup/cgroup.subtree_control" +fi + +# Start docker and print logs +start-docker.sh + +# process the command passed, after docker is started +if [ $# -gt 0 ]; then + exec "$@" +fi \ No newline at end of file diff --git a/.ci/ci-image/start-docker.sh b/.ci/ci-image/start-docker.sh new file mode 100755 index 000000000000..930eb5537752 --- /dev/null +++ b/.ci/ci-image/start-docker.sh @@ -0,0 +1,8 @@ +#!/bin/bash +source /opt/bash-utils/logger.sh + +INFO "Starting supervisor" +sudo bash -c "/usr/bin/supervisord >> /dev/null 2>&1" & + +INFO "Starting docker" +bash -c "source wait-for-docker.sh > /dev/null && sudo chown root:docker /var/run/docker.sock" & diff --git a/.ci/ci-image/wait-for-docker.sh b/.ci/ci-image/wait-for-docker.sh new file mode 100755 index 000000000000..ee636f9c3ced --- /dev/null +++ b/.ci/ci-image/wait-for-docker.sh @@ -0,0 +1,4 @@ +#!/bin/bash +source wait-for-process.sh + +wait_for_process dockerd \ No newline at end of file diff --git a/.ci/ci-image/wait-for-process.sh b/.ci/ci-image/wait-for-process.sh new file mode 100755 index 000000000000..49d4035e8d46 --- /dev/null +++ b/.ci/ci-image/wait-for-process.sh @@ -0,0 +1,19 @@ +#!/bin/bash +source /opt/bash-utils/logger.sh + +function wait_for_process () { + local max_time_wait=30 + local process_name="$1" + local waited_sec=0 + while ! pgrep "$process_name" >/dev/null && ((waited_sec < max_time_wait)); do + INFO "Process $process_name is not running yet. Retrying in 1 seconds" + INFO "Waited $waited_sec seconds of $max_time_wait seconds" + sleep 1 + ((waited_sec=waited_sec+1)) + if ((waited_sec >= max_time_wait)); then + return 1 + fi + done + INFO "Process $process_name successfully started." + return 0 +} \ No newline at end of file diff --git a/.ci/incubator-kie-ci-build.Dockerfile b/.ci/incubator-kie-ci-build.Dockerfile new file mode 100644 index 000000000000..7a0200b12a6f --- /dev/null +++ b/.ci/incubator-kie-ci-build.Dockerfile @@ -0,0 +1,229 @@ +# +# 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. +# + +# CI builder image for incubator-kie (Drools) Jenkins jobs. +# Same content as apache-nodes/Dockerfile.kogito-ci-build (incubator-kie-kogito-pipelines, main-latest), +# moved here so it can be published per stream by .ci/jenkins/Jenkinsfile.ci-image-build, e.g. +# docker.io/apache/incubator-kie-kogito-ci-build:10.3.x-latest +# +# Docker-in-Docker: the daemon is started by ENTRYPOINT entrypoint.sh (-> start-docker.sh), +# so the container must run privileged (--privileged --group-add docker). +# +# Keep SDKMAN_MAVEN in sync with in kie-parent/pom.xml +# (enforced by maven-enforcer-plugin, rule requireMavenVersion). +FROM cruizba/ubuntu-dind:jammy-26.1.4 + +SHELL ["/bin/bash", "-c"] + +ARG SDKMAN_JAVA="17.0.11-tem" +ARG SDKMAN_MAVEN="3.9.16" +ARG PYTHON_MAJOR_VERSION="3" +ARG PYTHON_MAJOR_MINOR_VERSION="3.11" +ARG DOCKER_VERSION="24.0.7" + +# set locale to C.UTF-8 +ENV LANG='C.UTF-8' + +# set TZ to America/New_York +ENV TZ='America/New_York' + +RUN apt update && apt upgrade -y && apt install -y \ +# skdman deps (BEGIN) +git \ +findutils \ +tar \ +gzip \ +unzip \ +zip \ +# skdman deps (END) +# python3 (BEGIN) +python${PYTHON_MAJOR_MINOR_VERSION} \ +python${PYTHON_MAJOR_MINOR_VERSION}-dev \ +python${PYTHON_MAJOR_VERSION}-pip \ +python${PYTHON_MAJOR_VERSION}-gssapi \ +krb5-multidev \ +# python3 (END) +# system (BEGIN) +netcat \ +libvshadow-utils \ +sudo \ +wget \ +software-properties-common \ +# system (END) +# drools (BEGIN) +fontconfig \ +# drools (END) +# kogito python integration (BEGIN) +libglvnd0 \ +# kogito python integration (END) +# Cypress dependencies install (BEGIN) +xvfb \ +libgtk2.0-dev \ +libgtk-3-dev \ +libnotify-dev \ +libnss3-tools \ +libxss1 \ +libasound2 \ +# Cypress dependencies install (END) +# kogito-images (BEGIN) +jq \ +skopeo \ +subversion \ +# kogito-images (END) +&& apt clean + +RUN groupadd -g 910 nonrootuser && useradd -u 910 -g 910 -s /bin/bash -m nonrootuser && \ + echo "nonrootuser ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers + +# Docker +RUN wget -O docker.tgz https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz && \ + tar --extract --file docker.tgz --strip-components 1 --directory /usr/local/bin/ && \ + rm -rf docker.tgz && \ + groupadd docker && \ + usermod -aG docker nonrootuser && \ + newgrp docker + +USER nonrootuser + +# Install pip of given version (BEGIN) +RUN curl -sS https://bootstrap.pypa.io/get-pip.py | python${PYTHON_MAJOR_MINOR_VERSION} +# Install pip of given version (END) + +RUN sudo update-alternatives --install /usr/local/bin/python python $(which python${PYTHON_MAJOR_MINOR_VERSION}) 1 && \ + sudo update-alternatives --install /usr/local/bin/python3 python3 $(which python${PYTHON_MAJOR_MINOR_VERSION}) 1 && \ + sudo update-alternatives --install /usr/local/bin/pip pip ~/.local/bin/pip${PYTHON_MAJOR_MINOR_VERSION} -1 && \ + sudo update-alternatives --install /usr/local/bin/pip${PYTHON_MAJOR_VERSION} pip${PYTHON_MAJOR_VERSION} ~/.local/bin/pip${PYTHON_MAJOR_MINOR_VERSION} -1 && \ + sudo update-alternatives --install /usr/local/bin/pip${PYTHON_MAJOR_MINOR_VERSION} pip${PYTHON_MAJOR_MINOR_VERSION} ~/.local/bin/pip${PYTHON_MAJOR_MINOR_VERSION} -1 + +RUN curl -s "https://get.sdkman.io" | bash && \ + source "$HOME/.sdkman/bin/sdkman-init.sh" && \ + sdk install java ${SDKMAN_JAVA} && \ + sudo update-alternatives --install /usr/local/bin/java java $(which java) 1 && \ + sdk install maven ${SDKMAN_MAVEN} && \ + sudo update-alternatives --install /usr/local/bin/mvn mvn $(which mvn) 1 && \ + sdk flush + +RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash && \ + export NVM_DIR="$HOME/.nvm" && \ + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \ + [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" && \ + source ~/.bashrc && nvm install "v16.20.0" && \ + sudo update-alternatives --install /usr/local/bin/node node $(which node) 1 && \ + sudo update-alternatives --install /usr/local/bin/npm npm $(which npm) 1 + +RUN wget https://go.dev/dl/go1.21.6.linux-amd64.tar.gz -P /tmp && \ + sudo mkdir /opt/golang && \ + sudo tar -C /opt/golang -xzf /tmp/go*.tar.gz && \ + sudo chown -R nonrootuser:nonrootuser /opt/golang/go && \ + sudo chmod -R 755 /opt/golang/go && \ + echo 'export GOPATH=${HOME}/go' | sudo tee /etc/profile.d/go.sh && \ + echo 'export PATH=${PATH}:/opt/golang/go/bin:${GOPATH}/bin' | sudo tee -a /etc/profile.d/go.sh && \ + echo "source /etc/profile.d/go.sh" >> $HOME/.bashrc && \ + rm -rf /tmp/go* && \ + sudo update-alternatives --install /usr/local/bin/go go /opt/golang/go/bin/go 1 && \ + go install golang.org/x/tools/cmd/goimports@latest && \ + sudo update-alternatives --install /usr/local/bin/goimports goimports ~/go/bin/goimports 1 + +# Install hub CLI (used for GitHub api operations) +RUN wget https://github.com/mislav/hub/releases/download/v2.14.2/hub-linux-amd64-2.14.2.tgz -O /tmp/hub.tgz && \ + (if ([ ! -d /opt/hub ]);then sudo mkdir /opt/hub;else rm -rf /opt/hub/*; fi;) && \ + sudo tar -C /opt/hub -xzf /tmp/hub.tgz --strip-components=1 && \ + sudo chown -R nonrootuser:nonrootuser /opt/hub/bin/hub && \ + sudo chmod -R 755 /opt/hub/bin/hub && \ + rm -rf /tmp/hub.tgz && \ + sudo update-alternatives --install /usr/local/bin/hub hub /opt/hub/bin/hub 1 + +# Cekit +RUN pip${PYTHON_MAJOR_MINOR_VERSION} install cekit==4.11.0 behave lxml docker docker-squash elementPath pyyaml ruamel.yaml python-dateutil Jinja2 pykwalify colorlog click && \ + sudo update-alternatives --install /usr/local/bin/cekit cekit ~/.local/bin/cekit 1 && \ + sudo update-alternatives --install /usr/local/bin/docker-squash docker-squash ~/.local/bin/docker-squash 1 +RUN wget https://github.com/openshift/source-to-image/releases/download/v1.3.8/source-to-image-v1.3.8-980ca195-linux-amd64.tar.gz -P /tmp && \ + tmp_dir=$(mktemp -d) && \ + tar -C ${tmp_dir} -xzvf /tmp/source-to-image-v1.3.8-980ca195-linux-amd64.tar.gz && \ + ls ${tmp_dir} && \ + sudo mv ${tmp_dir}/s2i /usr/local/bin/ && \ + rm -rf ${tmp_dir} /tmp/source-to-image/* + +# gh cli +RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \ + && sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ + && sudo apt update \ + && sudo apt install gh -y + +# Install kubectl +RUN wget https://dl.k8s.io/release/$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl -P /tmp && \ + chmod +x /tmp/kubectl && \ + sudo mv /tmp/kubectl /usr/local/bin/kubectl + +# Install oc client +RUN wget https://mirror.openshift.com/pub/openshift-v4/clients/ocp/latest-4.13/openshift-client-linux.tar.gz -P /tmp && \ + tmp_dir=$(mktemp -d) && \ + tar -C ${tmp_dir} -xvf /tmp/openshift-client-linux.tar.gz && \ + sudo mv ${tmp_dir}/oc /usr/local/bin && \ + rm -rf ${tmp_dir} /tmp/openshift-client* + +# Install ansible +RUN sudo ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \ + echo $TZ | sudo tee /etc/timezone && \ + sudo add-apt-repository --yes --update ppa:ansible/ansible && \ + sudo apt update && \ + sudo apt install -y ansible + +# Convenience script to account for using 'alternatives' in some places +RUN sudo bash -c 'echo -e "#!/bin/bash\nupdate-alternatives \"\$@\"" > /usr/local/bin/alternatives' \ + && sudo chmod +x /usr/local/bin/alternatives + +ENV HOME="/home/nonrootuser" + +ENV JAVA_HOME="${HOME}/.sdkman/candidates/java/current/" +ENV MAVEN_HOME="${HOME}/.sdkman/candidates/maven/current/" +ENV MAVEN_OPTS="-Xms1024m -Xmx12g" +ENV NODE_HOME="${HOME}/.nvm/versions/node/v16.20.0" +ENV NODE_OPTIONS="--max_old_space_size=4096" +ENV GOPATH="${HOME}/go" +ENV GOROOT="/opt/golang/go" +ENV PATH="${PATH}:${GOROOT}/bin:${GOPATH}/bin" + +ENV CONTAINER_ENGINE="docker" +ENV CONTAINER_ENGINE_TLS_OPTIONS="" + +WORKDIR /project/directory + +USER root +COPY .ci/ci-image/start-docker.sh .ci/ci-image/wait-for-docker.sh .ci/ci-image/wait-for-process.sh .ci/ci-image/entrypoint.sh /usr/local/bin/ +RUN chmod +x /usr/local/bin/start-docker.sh \ + /usr/local/bin/entrypoint.sh \ + /usr/local/bin/wait-for-docker.sh \ + /usr/local/bin/wait-for-process.sh +USER nonrootuser + +RUN echo "source wait-for-docker.sh" >> ~/.bashrc + +# Use bash as default instead of dash (BEGIN) +RUN sudo bash -c "echo \"dash dash/sh boolean false\" | debconf-set-selections" +RUN sudo bash -c "DEBIAN_FRONTEND=noninteractive dpkg-reconfigure dash" +# Use bash as default instead of dash (END) + +# Disable quarkus usage analytics (BEGIN) +RUN mkdir ~/.redhat && (echo '{"disabled":true}' > ~/.redhat/io.quarkus.analytics.localconfig) +# Disable quarkus usage analytics (END) + +ENTRYPOINT ["entrypoint.sh"] +CMD ["bash"] diff --git a/.ci/jenkins/Jenkinsfile.103xplus.deploy b/.ci/jenkins/Jenkinsfile.103xplus.deploy new file mode 100644 index 000000000000..a6f0bca54fcd --- /dev/null +++ b/.ci/jenkins/Jenkinsfile.103xplus.deploy @@ -0,0 +1,315 @@ +/* + * 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. + */ + +// 10.3.x+ deploy pipeline — no kogito-pipelines dependency. +// - agent { label 'ubuntu' } + tools block instead of docker { image AGENT_DOCKER_BUILDER_IMAGE } +// - Version update via script/release/01-update-version.sh (no MavenCommand shared library) +// - Build via direct mvn invocation (no MavenCommand shared library) +// - PR creation via 'gh' CLI instead of githubscm shared-library helpers +// - @Library removed entirely + +deployProperties = [:] +prBranchHash = '' + +pipeline { + agent { + label 'ubuntu' + } + + tools { + maven 'maven_3.9.16' + jdk 'jdk_17_latest' + } + + options { + timestamps() + timeout(time: 300, unit: 'MINUTES') + } + + environment { + DROOLS_CI_EMAIL_TO = credentials("${JENKINS_EMAIL_CREDS_ID}") + } + + stages { + stage('Initialize') { + steps { + script { + cleanWs(disableDeferredWipeout: true) + + prBranchHash = sh(script: 'openssl rand -hex 5', returnStdout: true).trim() + + if (params.DISPLAY_NAME) { + currentBuild.displayName = params.DISPLAY_NAME + } + + if (isCreatePr()) { + assert getProjectVersion() : 'PROJECT_VERSION is required when CREATE_PR is true' + } + + dir(getRepoName()) { + checkoutRepo() + } + } + } + post { + success { + script { + setDeployPropertyIfNeeded('git.branch', getBuildBranch()) + setDeployPropertyIfNeeded('git.author', getGitAuthor()) + setDeployPropertyIfNeeded('project.version', getProjectVersion()) + } + } + } + } + + stage('Prepare for PR') { + when { + expression { return isCreatePr() } + } + steps { + script { + dir(getRepoName()) { + withCredentials([usernamePassword(credentialsId: getGitAuthorPushCredsId(), usernameVariable: 'GITHUB_USER', passwordVariable: 'GITHUB_TOKEN')]) { + sh """#!/usr/bin/env bash + set -euo pipefail + git config user.email "${env.GIT_AUTHOR_EMAIL ?: 'dev@kie.apache.org'}" + git config user.name "${env.GIT_AUTHOR ?: 'Apache KIE Release Bot'}" + git config --local credential.helper '!f() { echo username=\$GITHUB_USER; echo password=\$GITHUB_TOKEN; }; f' + if git ls-remote --exit-code origin '${getPRBranch()}'; then + git push origin --delete '${getPRBranch()}' || true + fi + git checkout -b '${getPRBranch()}' + """ + } + } + } + } + } + + stage('Update project version') { + when { + expression { return getProjectVersion() } + } + steps { + script { + dir(getRepoName()) { + configFileProvider([configFile(fileId: env.MAVEN_SETTINGS_CONFIG_FILE_ID, variable: 'MAVEN_SETTINGS_FILE')]) { + sh """#!/usr/bin/env bash + set -euo pipefail + chmod +x script/release/01-update-version.sh + MAVEN_SETTINGS="\${MAVEN_SETTINGS_FILE}" \ + ./script/release/01-update-version.sh '${getProjectVersion()}' + """ + } + } + } + } + } + + stage('Build & Deploy repo') { + steps { + script { + dir(getRepoName()) { + configFileProvider([configFile(fileId: env.MAVEN_SETTINGS_CONFIG_FILE_ID, variable: 'MAVEN_SETTINGS_FILE')]) { + withCredentials([usernamePassword(credentialsId: env.MAVEN_REPO_CREDS_ID, usernameVariable: 'REPOSITORY_USER', passwordVariable: 'REPOSITORY_TOKEN')]) { + def goal + if (shouldDeployToRepository()) { + goal = "deploy -DdeployAtEnd -Dapache.repository.username=\${REPOSITORY_USER} -Dapache.repository.password=\${REPOSITORY_TOKEN} -DretryFailedDeploymentCount=5" + } else { + goal = 'install' + } + + def extraOpts = [ + env.BUILD_MVN_OPTS, + env.DROOLS_BUILD_MVN_OPTS, + env.BUILD_MVN_OPTS_CURRENT + ].findAll { it }.join(' ') + + def skipTestsFlag = params.SKIP_TESTS ? '-DskipTests=true' : '-DskipTests=false' + + sh """#!/usr/bin/env bash + set -euo pipefail + mvn -s "\${MAVEN_SETTINGS_FILE}" -B -ntp -fae \ + -Dfull \ + ${skipTestsFlag} \ + -Dmaven.test.failure.ignore=true \ + ${extraOpts} \ + clean ${goal} + """ + } + } + } + } + } + post { + always { + script { + dir(getRepoName()) { + saveReports() + archiveArtifacts(artifacts: 'build_logs/**', allowEmptyArchive: true) + } + } + } + } + } + + stage('Create PR') { + when { + expression { return isCreatePr() } + } + steps { + script { + dir(getRepoName()) { + withCredentials([usernamePassword(credentialsId: getGitAuthorPushCredsId(), usernameVariable: 'GITHUB_USER', passwordVariable: 'GITHUB_TOKEN')]) { + def prLink = sh(script: """#!/usr/bin/env bash + set -euo pipefail + git config --local credential.helper '!f() { echo username=\$GITHUB_USER; echo password=\$GITHUB_TOKEN; }; f' + + # Stage only pom.xml and antora.yml changes (version bump files) + git add -A -- '*.xml' '*/antora.yml' 2>/dev/null || true + if git diff --cached --quiet; then + echo '[WARN] no changes to commit' >&2 + echo '' + exit 0 + fi + + COMMIT_MSG="[${getBuildBranch()}] Update version to ${getProjectVersion()}" + git commit -m "\${COMMIT_MSG}" + git push origin '${getPRBranch()}' + + PR_BODY="Generated by build ${BUILD_TAG}: ${BUILD_URL}.\\nPlease do not merge, it should be merged automatically." + gh pr create \ + --repo '${getGitAuthor()}/${getRepoName()}' \ + --head '${getPRBranch()}' \ + --base '${getBuildBranch()}' \ + --title "\${COMMIT_MSG}" \ + --body "\${PR_BODY}" \ + --label 'skip-ci' + """, returnStdout: true).trim() + + if (prLink) { + deployProperties["${getRepoName()}.pr.link"] = prLink + } + } + } + } + } + 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()) + } + } + } + } + } + post { + always { + script { + setDeployPropertyIfNeeded("${getRepoName()}.deploy.build.run", true) + def propertiesStr = deployProperties.collect { entry -> "${entry.key}=${entry.value}" }.join('\n') + writeFile(text: propertiesStr, file: PROPERTIES_FILE_NAME) + archiveArtifacts(artifacts: PROPERTIES_FILE_NAME) + } + } + unsuccessful { + sendNotification() + } + cleanup { + cleanWs() + } + } +} + +void saveReports() { + junit testResults: '**/target/surefire-reports/**/*.xml, **/target/failsafe-reports/**/*.xml, **/target/invoker-reports/**/TEST-*.xml', allowEmptyResults: true +} + +void checkoutRepo() { + deleteDir() + checkout([$class: 'GitSCM', + branches: [[name: "*/${getBuildBranch()}"]], + userRemoteConfigs: [[ + url: "https://github.com/${getGitAuthor()}/${getRepoName()}.git", + credentialsId: getGitAuthorCredsId() + ]] + ]) + sh "git checkout ${getBuildBranch()}" +} + +void sendNotification() { + if (params.SEND_NOTIFICATION) { + mail( + to: env.DROOLS_CI_EMAIL_TO, + subject: "[FAILURE] Deploy [${getBuildBranch()}] Drools - Build ${BUILD_TAG}", + body: "Deploy pipeline failed.\n\nBuild URL: ${BUILD_URL}" + ) + } else { + echo 'No notification sent per configuration' + } +} + +boolean shouldDeployToRepository() { + return env.MAVEN_DEPLOY_REPOSITORY || getGitAuthor() == 'apache' +} + +boolean isCreatePr() { + return params.CREATE_PR +} + +boolean isStreamDevVersion() { + return getProjectVersion()?.contains('-SNAPSHOT') ?: false +} + +String getRepoName() { + return env.REPO_NAME +} + +String getGitAuthor() { + return "${GIT_AUTHOR}" +} + +String getBuildBranch() { + return params.BUILD_BRANCH_NAME +} + +String getProjectVersion() { + return params.PROJECT_VERSION +} + +String getPRBranch() { + return params.DROOLS_PR_BRANCH ?: "drools-${getProjectVersion()}_${prBranchHash}" +} + +String getGitAuthorCredsId() { + return env.GIT_AUTHOR_CREDS_ID +} + +String getGitAuthorPushCredsId() { + return env.GIT_AUTHOR_PUSH_CREDS_ID ?: env.GIT_AUTHOR_CREDS_ID +} + +void setDeployPropertyIfNeeded(String key, def value) { + if (value) { + deployProperties[key] = value + } +} diff --git a/.ci/jenkins/Jenkinsfile.103xplus.promote b/.ci/jenkins/Jenkinsfile.103xplus.promote new file mode 100644 index 000000000000..33cdfc578bfc --- /dev/null +++ b/.ci/jenkins/Jenkinsfile.103xplus.promote @@ -0,0 +1,308 @@ +/* + * 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. + */ + +// 10.3.x+ promote pipeline — no kogito-pipelines dependency. +// - agent { label 'ubuntu' } + tools block instead of docker { image AGENT_DOCKER_BUILDER_IMAGE } +// - 'Tag final release' stage delegates to 05-tag-release.sh (no shared library) +// - 'Create release' stage uses 'gh' CLI instead of githubscm shared-library helpers +// - 'Upload drools binaries and documentation' stage uses 03-build.sh directly +// - @Library removed entirely + +deployProperties = [:] + +pipeline { + agent { + label 'ubuntu' + } + + tools { + maven 'maven_3.9.16' + jdk 'jdk_17_latest' + } + + 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() : 'PROJECT_VERSION is required' + // Build branch must match the release version stream (e.g. 10.3.0 -> 10.3.x) + def expectedBranch = getProjectVersion().replaceFirst(/(\d+\.\d+)\.\d+.*/, '$1.x') + assert getBuildBranch() == expectedBranch : \ + "BUILD_BRANCH_NAME '${getBuildBranch()}' does not match version '${getProjectVersion()}' (expected '${expectedBranch}')" + // GIT_TAG must be the approved RC tag; the final tag is derived from it. + assert getRcTag() ==~ /.+-rc[0-9]+/ : \ + "GIT_TAG must be the approved RC tag (e.g. 10.3.0-rc2), got '${getRcTag()}'" + } + } + } + + stage('Merge deploy PR') { + // NOTE: tagLatest() removed here intentionally. + // In the 103xplus flow the final release tag is created by the + // separate "Tag final release" stage below, which delegates to + // 05-tag-release.sh so the tag always points to the approved + // RC commit — NOT the current branch tip. Calling tagLatest() + // here would create a duplicate tag at the wrong commit. + steps { + script { + withCredentials([usernamePassword(credentialsId: getGitAuthorPushCredsId(), usernameVariable: 'GITHUB_USER', passwordVariable: 'GITHUB_TOKEN')]) { + def prLink = getDeployPrLink() + if (prLink?.trim()) { + sh """#!/usr/bin/env bash + set -euo pipefail + git config --local credential.helper '!f() { echo username=\$GITHUB_USER; echo password=\$GITHUB_TOKEN; }; f' + gh pr review '${prLink}' --approve + gh pr merge '${prLink}' --merge --auto + """ + } + dir(getRepoName()) { + checkoutRepo() + } + } + } + } + } + + stage('Tag final release') { + // Delegate final-tag creation to 05-tag-release.sh so the release tag + // always points to the approved RC commit — NOT the current branch tip. + // This matches the documented strategy: the R commit lives under the RC + // tag only; the development branch is never disturbed. + // Runs before 'Create release' so the GitHub release attaches to this + // tag instead of creating one at the branch tip. + steps { + withCredentials([ + usernamePassword(credentialsId: env.GIT_AUTHOR_PUSH_CREDS_ID, usernameVariable: 'GITHUB_USER', passwordVariable: 'GITHUB_TOKEN') + ]) { + script { + dir(getRepoName()) { + sh """#!/usr/bin/env bash + set -euo pipefail + git config user.email "${env.GIT_AUTHOR_EMAIL ?: 'dev@kie.apache.org'}" + git config user.name "${env.GIT_AUTHOR ?: 'Apache KIE Release Bot'}" + git config --local credential.helper '!f() { echo username=\$GITHUB_USER; echo password=\$GITHUB_TOKEN; }; f' + # Fetch the RC tag so the script can resolve it. + git fetch origin "refs/tags/${getRcTag()}:refs/tags/${getRcTag()}" + chmod +x script/release/05-tag-release.sh + ./script/release/05-tag-release.sh --rc-tag "${getRcTag()}" --push + """ + } + } + } + } + } + + stage('Create release') { + steps { + script { + withCredentials([usernamePassword(credentialsId: getGitAuthorPushCredsId(), usernameVariable: 'GITHUB_USER', passwordVariable: 'GITHUB_TOKEN')]) { + sh """#!/usr/bin/env bash + set -euo pipefail + git config --local credential.helper '!f() { echo username=\$GITHUB_USER; echo password=\$GITHUB_TOKEN; }; f' + + # Delete any pre-existing release so we can re-create it cleanly. + if gh release view '${getGitTag()}' --repo '${getGitAuthor()}/${getRepoName()}' &>/dev/null; then + gh release delete '${getGitTag()}' \ + --repo '${getGitAuthor()}/${getRepoName()}' \ + --yes + fi + + # Derive the previous tag for release-note generation. + PREV_TAG="\$(git tag --sort=-version:refname | grep -v '^${getGitTag()}\$' | head -1 || true)" + + gh release create '${getGitTag()}' \ + --repo '${getGitAuthor()}/${getRepoName()}' \ + --target '${getBuildBranch()}' \ + --title '${getGitTag()}' \ + --generate-notes \ + \${PREV_TAG:+--notes-start-tag "\${PREV_TAG}"} + """ + } + } + } + } + + stage('Upload drools binaries and documentation') { + when { + expression { return isMainStream() } + } + steps { + script { + dir(getRepoName()) { + sh """#!/usr/bin/env bash + set -euo pipefail + chmod +x script/release/03-build.sh + ./script/release/03-build.sh --skip-tests + """ + + if (isNotTestingBuild()) { + sshagent(['drools-filemgmt']) { + sh """#!/usr/bin/env bash + set -euo pipefail + chmod +x script/release/upload_filemgmt.sh + ./script/release/upload_filemgmt.sh '${getProjectVersion()}' + """ + } + } else { + echo 'No uploadFileMgmt due to testing build' + } + } + } + } + } + } + post { + unsuccessful { + sendNotification() + } + cleanup { + cleanWs() + } + } +} + +void sendNotification() { + if (params.SEND_NOTIFICATION) { + mail( + to: env.DROOLS_CI_EMAIL_TO, + subject: "[FAILURE] Promote [${getBuildBranch()}] Drools - Build ${BUILD_TAG}", + body: "Promote pipeline failed.\n\nBuild URL: ${BUILD_URL}" + ) + } 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') +} + +// Approved RC tag to promote (e.g. 10.3.0-rc2). Mandatory, validated in Initialization. +String getRcTag() { + return params.GIT_TAG ?: '' +} + +// Final release tag, derived from the RC tag (e.g. 10.3.0-rc2 -> 10.3.0). +String getGitTag() { + return getRcTag().replaceFirst(/-rc[0-9]+$/, '') +} + +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([$class: 'GitSCM', + branches: [[name: "*/${getBuildBranch()}"]], + userRemoteConfigs: [[ + url: "https://github.com/${getGitAuthor()}/${getRepoName()}.git", + credentialsId: getGitAuthorCredsId() + ]] + ]) + sh "git checkout ${getBuildBranch()}" +} + +boolean isNotTestingBuild() { + return getGitAuthor() == 'apache' +} + +boolean isMainStream() { + return env.DROOLS_STREAM == 'main' +} \ No newline at end of file diff --git a/.ci/jenkins/Jenkinsfile.ci-image-build b/.ci/jenkins/Jenkinsfile.ci-image-build new file mode 100644 index 000000000000..630695ce8252 --- /dev/null +++ b/.ci/jenkins/Jenkinsfile.ci-image-build @@ -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. + */ + +// Builds and publishes the CI builder image from .ci/incubator-kie-ci-build.Dockerfile. +// One tag per stream (e.g. main-latest, 10.3.x-latest, 10.4.x-latest): set IMAGE_TAG to the stream name. +// The 10.3.x+ deploy/promote pipelines no longer use this image — they run on +// agent { label 'ubuntu' } with tools { maven 'maven_3.9.16' } directly. +// This pipeline is retained for future streams that may still use the Docker agent path. + +pipeline { + agent { + label 'ubuntu' + } + + parameters { + string(name: 'IMAGE_TAG', defaultValue: "${env.BRANCH_NAME}", description: 'Image tag, one per stream, e.g. 10.3.x-latest', trim: true) + } + + environment { + DOCKER_CONFIG = "${WORKSPACE}/.docker" + IMAGE_REGISTRY = 'docker.io' + IMAGE_ACCOUNT = 'apache' + IMAGE_NAME = 'incubator-kie-kogito-ci-build' + DOCKERHUB_USER_CREDS_ID = 'DOCKERHUB_USER' + DOCKERHUB_TOKEN_CREDS_ID = 'DOCKERHUB_TOKEN' + } + + options { + timestamps() + timeout(time: 120, unit: 'MINUTES') + } + + stages { + stage('Initialize') { + steps { + script { + assert params.IMAGE_TAG : 'IMAGE_TAG parameter is mandatory' + env.FULL_IMAGE = "${env.IMAGE_REGISTRY}/${env.IMAGE_ACCOUNT}/${env.IMAGE_NAME}:${params.IMAGE_TAG}" + currentBuild.displayName = params.IMAGE_TAG + } + cleanWs(deleteDirs: true, disableDeferredWipeout: true) + } + } + + stage('Checkout') { + steps { + dir('kie') { + checkout scm + } + } + } + + stage('Build image') { + steps { + dir('kie') { + sh "docker build -t ${env.FULL_IMAGE} -f .ci/incubator-kie-ci-build.Dockerfile ." + } + } + } + + stage('Test built image') { + steps { + // Test before pushing so a broken image is never published. + // --privileged: the image entrypoint starts the Docker-in-Docker daemon. + sh """#!/bin/bash -el + docker run --rm --privileged --group-add docker ${env.FULL_IMAGE} bash -lc 'wait-for-docker.sh && java -version && mvn --version && git --version && gh --version && docker info > /dev/null && echo DOCKER_OK' + """.trim() + } + } + + stage('Push image to registry') { + steps { + withCredentials([ + string(credentialsId: env.DOCKERHUB_USER_CREDS_ID, variable: 'REGISTRY_USER'), + string(credentialsId: env.DOCKERHUB_TOKEN_CREDS_ID, variable: 'REGISTRY_TOKEN') + ]) { + sh '''#!/bin/bash -e + echo "${REGISTRY_TOKEN}" | docker login -u "${REGISTRY_USER}" --password-stdin "${IMAGE_REGISTRY}" + docker push "${FULL_IMAGE}" + docker logout "${IMAGE_REGISTRY}" + ''' + } + } + } + } + + post { + always { + cleanWs(deleteDirs: true) + } + } +} diff --git a/.ci/jenkins/project/Jenkinsfile.103xplus.release b/.ci/jenkins/project/Jenkinsfile.103xplus.release new file mode 100644 index 000000000000..dd32628d718a --- /dev/null +++ b/.ci/jenkins/project/Jenkinsfile.103xplus.release @@ -0,0 +1,184 @@ +/* + * 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. + */ + +// 10.3.x+ release pipeline — fully independent, no kogito-pipelines / jobs.groovy dependency. +// - No @Library, no DSL-injected env vars, no drools-deploy / drools-promote downstream jobs +// - Create a plain "Pipeline script from SCM" job pointing at this file and run it +// with a few parameters. Everything it needs is a parameter (with a default). +// - Runs script/release/release-all.sh (R commit + RC tag -> build -> Nexus staging) +// +// MANDATORY parameters : RELEASE_VERSION, TAG_NAME +// Real RC run : DEPLOY=true, PUSH_TAG=true, DRY_RUN=false + +pipeline { + agent { + label 'ubuntu' + } + + tools { + maven 'maven_3.9.16' + jdk 'jdk_17_latest' + } + + options { + timestamps() + timeout(time: 360, unit: 'MINUTES') + } + + parameters { + string(name: 'RELEASE_VERSION', defaultValue: '', description: 'MANDATORY. Exact release version, e.g. 10.3.0', trim: true) + string(name: 'TAG_NAME', defaultValue: '', description: 'MANDATORY. RC tag name, e.g. 10.3.0-rc1 (must start with RELEASE_VERSION and be a new tag)', 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 an e-mail when the pipeline finishes (requires NOTIFICATION_EMAIL)') + string(name: 'NOTIFICATION_EMAIL', defaultValue: '', description: 'Optional. Recipient for SEND_NOTIFICATION', trim: true) + + // Jenkins IDs — defaults match the existing KIE Jenkins; override only if they differ. + string(name: 'MAVEN_SETTINGS_CONFIG_FILE_ID', defaultValue: 'kie-release-authn-settings', description: 'Config File Provider ID of the Maven settings.xml (must define server "apache.releases.https")', trim: true) + string(name: 'GPG_KEY_CREDS_ID', defaultValue: 'GPG_KEY_FILE', description: 'Secret FILE credential ID holding the armored GPG private key', trim: true) + string(name: 'GPG_PASSPHRASE_CREDS_ID', defaultValue: '', description: 'Optional. Secret TEXT credential ID holding the GPG key passphrase. Leave empty if the key has no passphrase (KIE release key has none)', trim: true) + string(name: 'GIT_PUSH_CREDS_ID', defaultValue: '84811880-2025-45b6-a44c-2f33bef30ad2', description: 'Username/password credential ID used to push the RC tag to GitHub', trim: true) + string(name: 'GIT_AUTHOR_NAME', defaultValue: 'Apache KIE Release Bot', description: 'git user.name for the R commit', trim: true) + string(name: 'GIT_AUTHOR_EMAIL', defaultValue: 'dev@kie.apache.org', description: 'git user.email for the R commit', trim: true) + } + + stages { + stage('Initialize & Validate') { + steps { + script { + if (!params.RELEASE_VERSION?.trim()) { + error 'RELEASE_VERSION parameter is mandatory (e.g. 10.3.0)' + } + if (!params.TAG_NAME?.trim()) { + error 'TAG_NAME parameter is mandatory (e.g. 10.3.0-rc1)' + } + if (!params.TAG_NAME.startsWith(params.RELEASE_VERSION)) { + error "TAG_NAME '${params.TAG_NAME}' must start with RELEASE_VERSION '${params.RELEASE_VERSION}'" + } + currentBuild.displayName = "Release ${params.RELEASE_VERSION} (${params.TAG_NAME})" + } + } + } + + stage('Execute Release Scripts') { + steps { + // The Maven settings id is a Config File Provider id (not a secret) and is + // exposed as MAVEN_SETTINGS_FILE. GPG key/passphrase and the GitHub push + // credential are real credentials and use withCredentials. + script { + def creds = [ + file(credentialsId: params.GPG_KEY_CREDS_ID, variable: 'GPG_KEY_FILE_PATH'), + usernamePassword(credentialsId: params.GIT_PUSH_CREDS_ID, usernameVariable: 'GITHUB_USER', passwordVariable: 'GITHUB_TOKEN') + ] + // Passphrase is optional: the KIE release signing key has none. + if (params.GPG_PASSPHRASE_CREDS_ID?.trim()) { + creds << string(credentialsId: params.GPG_PASSPHRASE_CREDS_ID, variable: 'MAVEN_GPG_PASSPHRASE') + } + configFileProvider([configFile(fileId: params.MAVEN_SETTINGS_CONFIG_FILE_ID, variable: 'MAVEN_SETTINGS_FILE')]) { + withCredentials(creds) { + // Import the signing key so the apache-release profile can sign + // artifacts. Passphrase is supplied later via MAVEN_GPG_PASSPHRASE. + sh '''#!/usr/bin/env bash + set -euo pipefail + gpg --batch --pinentry-mode=loopback --import "$GPG_KEY_FILE_PATH" + ''' + + // Configure Git identity for the R commit and authenticate the + // raw `git push` in 02-rc-commit.sh. The credential helper reads + // the token from the environment at push time, never from disk. + sh """#!/usr/bin/env bash + set -euo pipefail + git config user.email "${params.GIT_AUTHOR_EMAIL}" + git config user.name "${params.GIT_AUTHOR_NAME}" + git config --local credential.helper '!f() { echo username=\$GITHUB_USER; echo password=\$GITHUB_TOKEN; }; f' + """ + + // Build the argv array safely — each parameter is a + // separate shell word, no string concatenation or eval. + def releaseArgs = [ + './script/release/release-all.sh', + '--version', params.RELEASE_VERSION, + '--tag', params.TAG_NAME + ] + if (params.SKIP_TESTS) releaseArgs << '--skip-tests' + if (params.DEPLOY) releaseArgs << '--deploy' + if (params.STAGING_URL?.trim()) releaseArgs += ['--staging-url', params.STAGING_URL.trim()] + if (params.PUSH_TAG) releaseArgs << '--push-tag' + if (params.EXTRA_MAVEN_OPTS?.trim()) releaseArgs += ['--maven-opts', params.EXTRA_MAVEN_OPTS.trim()] + if (params.DRY_RUN) releaseArgs << '--dry-run' + + // MAVEN_SETTINGS_FILE is injected by configFileProvider above. + // If a passphrase credential was given, MAVEN_GPG_PASSPHRASE is in + // env and read by the GPG plugin directly (not a CLI arg). + withEnv(["MAVEN_SETTINGS=${MAVEN_SETTINGS_FILE}"]) { + sh """#!/usr/bin/env bash + set -euo pipefail + chmod +x script/release/*.sh + ${releaseArgs.collect { "'${it.replace("'", "'\\''")}'" }.join(' ')} + """ + } + } + } + } + } + } + } + + post { + always { + cleanWs() + } + success { + script { + sendNotification() + } + } + unsuccessful { + sendErrorNotification() + } + } +} + +void sendNotification() { + if (params.SEND_NOTIFICATION && params.NOTIFICATION_EMAIL) { + mail( + to: params.NOTIFICATION_EMAIL, + subject: "[SUCCESS] Release ${params.RELEASE_VERSION} (${params.TAG_NAME}) Drools - Build ${BUILD_TAG}", + body: "Release pipeline succeeded.\n\nBuild URL: ${BUILD_URL}" + ) + } else { + echo 'No notification sent per configuration' + } +} + +void sendErrorNotification() { + if (params.SEND_NOTIFICATION && params.NOTIFICATION_EMAIL) { + mail( + to: params.NOTIFICATION_EMAIL, + subject: "[FAILURE] Release ${params.RELEASE_VERSION} (${params.TAG_NAME}) Drools - Build ${BUILD_TAG}", + body: "Release pipeline failed.\n\nBuild URL: ${BUILD_URL}" + ) + } else { + echo 'No notification sent per configuration' + } +} diff --git a/docs/RELEASING.md b/docs/RELEASING.md new file mode 100644 index 000000000000..ab3a80325b19 --- /dev/null +++ b/docs/RELEASING.md @@ -0,0 +1,106 @@ + + +# Apache KIE Release Guide + +The canonical release documentation and single source of truth for the Apache KIE release procedure is maintained on the Apache KIE community website: + +👉 **[Apache KIE Release Procedure](https://kie.apache.org/community/devs/release-procedure)** (hosted in the [`apache/incubator-kie-website`](https://github.com/apache/incubator-kie-website) repository under `docs/community/devs/release-procedure.md`). + +--- + +## Who Runs These Scripts? + +The release scripts in this repository are intended for **Apache KIE Release Managers** — PMC members or designated committers who have: + +- Write access to the `apache/incubator-kie` repository (push tags to `git@github.com:apache/incubator-kie.git`). +- An ASF Nexus account with `kie` staging privileges (username/password in Maven `settings.xml`). +- A GPG signing key registered with the [Apache KEYS file](https://downloads.apache.org/kie/KEYS). + +In normal operation the scripts are invoked by the **Jenkins CI pipeline** (`.ci/jenkins/project/Jenkinsfile.103xplus.release`) using managed credentials. Release managers may also run them locally for testing or emergency releases. + +--- + +## Credentials Required + +| Credential | Where it lives | Used by | +|---|---|---| +| Maven `settings.xml` with Nexus server entry (`apache.releases.https`) | Jenkins → `MAVEN_SETTINGS_CONFIG_FILE_ID`, or `~/.m2/settings.xml` locally | `04-deploy-to-staging.sh` via `MAVEN_SETTINGS` env var | +| GPG passphrase | Jenkins → `GPG_PASSPHRASE_CREDS_ID` (masked), or GPG agent locally | `04-deploy-to-staging.sh` via `MAVEN_GPG_PASSPHRASE` env var (NOT a CLI arg) | +| Git push credentials (SSH key or token) | Jenkins → `GIT_AUTHOR_PUSH_CREDS_ID`, or `~/.ssh/id_rsa` locally | `02-rc-commit.sh` (--push), `05-tag-release.sh` (--push) | + +--- + +## What the Scripts Produce + +Each script is **isolated to this single consolidated repository** — there is no multi-repo coordination needed since Drools, OptaPlanner, Kogito Runtimes, and Kogito Apps all live here. + +| Step | Output | +|---|---| +| `01-update-version.sh` | POM versions bumped in the working tree (not committed) | +| `02-rc-commit.sh` | A local RC git tag (e.g. `10.3.0-rc1`) pointing to the R commit; optionally pushed to `origin` | +| `03-build.sh` | Release JARs installed to `~/.m2/repository` | +| `04-deploy-to-staging.sh` | JARs signed (GPG) and uploaded to the Apache Nexus staging repository | +| `05-tag-release.sh` | Final release git tag (e.g. `10.3.0`) pointing to the same commit as the winning RC | + +--- + +## Repository-Specific Release Tooling + +This repository (`incubator-kie`) provides local-first release scripts located in [`script/release/`](../script/release/): + +- **[`script/release/README.md`](../script/release/README.md)**: Detailed documentation of all scripts, lifecycle definitions, and workflows. +- **`01-update-version.sh`**: (Automation A / D) Updates reactor POM versions across all modules. +- **`02-rc-commit.sh`**: (Automation D.1) Generates the temporary release branch, updates version to release version, tags the RC commit, and deletes the temporary branch. +- **`03-build.sh`**: (Automation D.2) Builds the full Maven reactor and installs artifacts to local `~/.m2/repository`. +- **`04-deploy-to-staging.sh`**: (Automation D.3) Deploys signed artifacts to the Apache Nexus staging repository. +- **`05-tag-release.sh`**: (Automation F) Promotes an approved RC tag to the final release tag after a successful VOTE. +- **`release-all.sh`**: Master orchestration script executing the RC workflow (`02` → `03` → `04`) in sequence. + +### Common Commands + +```bash +# 1. Update version across reactor modules (Automation A or D) +./script/release/01-update-version.sh 10.3.999-SNAPSHOT # stream snapshot +./script/release/01-update-version.sh 999-SNAPSHOT # main stream (image tag → "main") +./script/release/01-update-version.sh 10.3.0 # exact release version + +# 2. Master RC workflow (accepts positional version or --version flag) +./script/release/release-all.sh 10.3.0 --rc --skip-tests +# Full RC with upload + tag push (requires MAVEN_SETTINGS and MAVEN_GPG_PASSPHRASE env vars): +MAVEN_SETTINGS=~/.m2/settings.xml \ + ./script/release/release-all.sh --version 10.3.0 --tag 10.3.0-rc1 --skip-tests --deploy --push-tag + +# 3. Promote approved RC tag to final release tag (Automation F) +./script/release/05-tag-release.sh --rc-tag 10.3.0-rc1 --push +``` + +### Dry-Run Safety + +All scripts that modify git state or push to remote accept `--dry-run`. Safe to run without credentials: + +```bash +./script/release/release-all.sh 10.3.0 --tag 10.3.0-rc1 --deploy --push-tag --dry-run +``` + +For Jenkins pipeline jobs, see: +- `.ci/jenkins/Jenkinsfile.103xplus.deploy` — stream-snapshot version bump and artifact deployment +- `.ci/jenkins/Jenkinsfile.103xplus.promote` — PR merge, GitHub release creation, and final tag promotion +- `.ci/jenkins/project/Jenkinsfile.103xplus.release` — top-level release orchestrator (invokes `release-all.sh`) + diff --git a/script/release/01-update-version.sh b/script/release/01-update-version.sh new file mode 100755 index 000000000000..1cb08f58a48f --- /dev/null +++ b/script/release/01-update-version.sh @@ -0,0 +1,108 @@ +#!/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 + +# Updates the Maven version across the Apache KIE repo (drools + optaplanner + +# kogito-runtimes + kogito-apps are all modules of the same root POM since the +# 10.3.x consolidation). +# +# Usage: +# ./script/release/01-update-version.sh +# +# Examples: +# ./script/release/01-update-version.sh 10.3.999-SNAPSHOT # dev/stream version +# ./script/release/01-update-version.sh 10.3.0 # release version +# +# The script also updates the data-index ephemeral image tag property that lives +# inside kogito-quarkus (was a separate step in the old multi-repo flow). + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +NEW_VERSION="${1:-}" + +if [[ -z "${NEW_VERSION}" ]]; then + echo "Usage: $0 " + echo " e.g. $0 10.3.999-SNAPSHOT" + echo " e.g. $0 10.3.0" + exit 1 +fi + +# Derive the stream name (e.g. 10.3.0 → 10.3.x, 10.3.999-SNAPSHOT → 10.3.x). +# Used for the data-index image tag property. +# Special case: 999-SNAPSHOT is the main-stream version; the image tag is "main". +if [[ "${NEW_VERSION}" == "999-SNAPSHOT" ]]; then + STREAM_NAME="main" +else + STREAM_NAME="$(echo "${NEW_VERSION}" | sed 's/^\([0-9]*\.[0-9]*\)\..*/\1.x/')" +fi + +# Detect the current version from the root POM so we can pass -DoldVersion, +# which skips the full reactor scan and is much faster on a large multi-module +# repo like this one. +CURRENT_VERSION="$(mvn -q help:evaluate -Dexpression=project.version -DforceStdout -f "${REPO_ROOT}/pom.xml" 2>/dev/null)" + +if [[ -z "${CURRENT_VERSION}" ]]; then + echo "ERROR: Could not determine current project version from pom.xml." + echo " Make sure Maven is available and the POM is resolvable." + exit 1 +fi + +echo "========================================" +echo "Apache KIE repo — version update" +echo "Current version : ${CURRENT_VERSION}" +echo "New version : ${NEW_VERSION}" +echo "Stream name : ${STREAM_NAME}" +echo "========================================" + +cd "${REPO_ROOT}" + +echo "" +echo "--- Updating all Maven module versions ---" +mvn versions:set \ + -DoldVersion="${CURRENT_VERSION}" \ + -DnewVersion="${NEW_VERSION}" \ + -DprocessAllModules \ + -DgenerateBackupPoms=false + +# The data-index ephemeral image tag property is controlled via a dedicated +# property inside kogito-quarkus-workflow-common-deployment. Update it to +# track the stream name so nightly image pulls resolve correctly. +DATA_INDEX_MODULE=":kogito-quarkus-workflow-common-deployment" +if mvn -q help:evaluate -Dexpression=project.artifactId -pl "${DATA_INDEX_MODULE}" 2>/dev/null; then + echo "" + echo "--- Updating data-index ephemeral image tag (${STREAM_NAME}) ---" + mvn -pl "${DATA_INDEX_MODULE}" versions:set-property \ + -Dproperty=data-index-ephemeral.image.tagVersion \ + -DnewVersion="${STREAM_NAME}" \ + -DgenerateBackupPoms=false +else + echo "(skipping data-index image tag update — module not found)" +fi + +echo "" +echo "========================================" +echo "Version update complete: ${NEW_VERSION}" +echo "========================================" +echo "" +echo "Next steps:" +echo " git diff # review changes" +echo " git add -A && git commit -m \"chore: update version to ${NEW_VERSION}\"" diff --git a/script/release/02-rc-commit.sh b/script/release/02-rc-commit.sh new file mode 100755 index 000000000000..aff6d8cecf17 --- /dev/null +++ b/script/release/02-rc-commit.sh @@ -0,0 +1,236 @@ +#!/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 [--tag ] [--push] [--dry-run] +# ./script/release/02-rc-commit.sh --version --tag +# +# 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 Exact release version, e.g. 10.3.0 +# --tag | --rc-tag Git tag name, e.g. 10.3.0-rc1 (defaults to -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 ] [--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}" + +# Capture original ref: branch name when attached, commit SHA when detached +# (Jenkins SCM checkouts are typically detached; `git rev-parse --abbrev-ref HEAD` +# returns "HEAD" in that case, which cannot be restored via `git checkout HEAD` +# because the branch is still pointing there. We need the actual commit SHA.) +if git symbolic-ref --quiet HEAD &>/dev/null; then + CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)" +else + CURRENT_BRANCH="$(git rev-parse HEAD)" +fi + +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 (staged, unstaged, or untracked) before +# we do anything. Untracked files matter here because git add -A below will +# include them in the R commit. +if ! git diff --quiet || ! git diff --cached --quiet; then + echo "ERROR: There are uncommitted changes in the working tree." + echo " Commit or stash them before running this script." + exit 1 +fi +if [[ -n "$(git ls-files --others --exclude-standard)" ]]; then + echo "ERROR: There are untracked files in the working tree:" + git ls-files --others --exclude-standard | sed 's/^/ /' + echo " Remove or .gitignore them before running this script." + exit 1 +fi + +if [[ "${DRY_RUN}" == "true" ]]; then + echo "" + echo "[DRY RUN] Would execute:" + echo " git checkout -b ${RELEASE_BRANCH}" + echo " ./script/release/01-update-version.sh ${RELEASE_VERSION}" + echo " git add -A" + echo " git commit -m \"chore: release ${RELEASE_VERSION}\"" + echo " git tag -a ${TAG_NAME} -m \"Release ${TAG_NAME}\"" + if [[ "${PUSH}" == "true" ]]; then + echo " git push origin ${TAG_NAME}" + fi + echo " git checkout ${CURRENT_BRANCH}" + echo " git branch -D ${RELEASE_BRANCH}" + exit 0 +fi + +# ── Guard: stale release branch from a previous interrupted run ────────────── +if git rev-parse --verify "${RELEASE_BRANCH}" &>/dev/null; then + echo "" + echo "ERROR: Branch '${RELEASE_BRANCH}' already exists from a previous" + echo " (possibly interrupted) run." + echo "" + echo "To clean up and start fresh:" + echo " git checkout ${CURRENT_BRANCH}" + echo " git branch -D ${RELEASE_BRANCH}" + if git rev-parse --verify "${TAG_NAME}" &>/dev/null; then + echo " git tag -d ${TAG_NAME}" + fi + exit 1 +fi + +# ── Guard: tag already exists ───────────────────────────────────────────────── +if git rev-parse --verify "${TAG_NAME}" &>/dev/null; then + echo "" + echo "ERROR: Tag '${TAG_NAME}' already exists." + echo " Delete it first if you want to recreate it:" + echo " git tag -d ${TAG_NAME}" + exit 1 +fi + +echo "" +echo "--- Creating local release branch: ${RELEASE_BRANCH} ---" +git checkout -b "${RELEASE_BRANCH}" + +echo "" +echo "--- Updating version to ${RELEASE_VERSION} ---" +"${SCRIPT_DIR}/01-update-version.sh" "${RELEASE_VERSION}" + +echo "" +echo "--- Committing R commit ---" +git add -A +git commit -m "chore: release ${RELEASE_VERSION}" + +echo "" +echo "--- Tagging ${TAG_NAME} ---" +git tag -a "${TAG_NAME}" -m "Release ${TAG_NAME}" + +if [[ "${PUSH}" == "true" ]]; then + echo "" + echo "--- Pushing tag ${TAG_NAME} to origin ---" + git push origin "${TAG_NAME}" +fi + +echo "" +echo "--- Returning to ${CURRENT_BRANCH} ---" +git checkout "${CURRENT_BRANCH}" + +echo "" +echo "--- Deleting local release branch ${RELEASE_BRANCH} ---" +git branch -D "${RELEASE_BRANCH}" + +echo "" +echo "========================================" +echo "RC commit + tag complete." +echo "" +echo "Tag '${TAG_NAME}' points to the R commit (version ${RELEASE_VERSION})." +if [[ "${PUSH}" == "false" ]]; then + echo "" + echo "Tag is LOCAL only. To push when ready:" + echo " git push origin ${TAG_NAME}" +fi +echo "" +echo "Next steps:" +echo " ./script/release/03-build.sh --skip-tests" +echo " ./script/release/04-deploy-to-staging.sh --tag ${TAG_NAME}" +echo "========================================" diff --git a/script/release/03-build.sh b/script/release/03-build.sh new file mode 100755 index 000000000000..f9507e2ab34c --- /dev/null +++ b/script/release/03-build.sh @@ -0,0 +1,100 @@ +#!/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 + +# Builds the Apache KIE repo for a release (skipping tests by default). +# +# Because drools, optaplanner, kogito-runtimes, and kogito-apps now all live in +# the same reactor, a single `mvn clean install` is enough. +# +# Usage: +# ./script/release/03-build.sh [--skip-tests] [--maven-opts ] +# +# Flags: +# --skip-tests Skip all tests (default: tests are run) +# --maven-opts Extra Maven options appended to the command. Split on +# whitespace only; shell-style quoting is NOT honoured, +# so individual options must not contain spaces. +# +# Examples: +# ./script/release/03-build.sh --skip-tests +# ./script/release/03-build.sh --skip-tests --maven-opts "-T 4" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +SKIP_TESTS=false +# Extra Maven opts stored as an array and expanded quoted, so no glob expansion, +# eval, or command substitution happens. Options are split on whitespace only. +EXTRA_MVN_OPTS=() + +while [[ $# -gt 0 ]]; do + case $1 in + --skip-tests) + SKIP_TESTS=true + shift + ;; + --maven-opts) + # Accept a single-argument "extra opts" string and split it on + # whitespace into array elements using read. Quotes are not parsed. + IFS=' ' read -r -a _extra <<< "${2:-}" + EXTRA_MVN_OPTS+=("${_extra[@]}") + shift 2 + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 [--skip-tests] [--maven-opts ]" + exit 1 + ;; + esac +done + +cd "${REPO_ROOT}" + +# ── Assemble Maven flags as a Bash array ────────────────────────────────────── + +MVN_FLAGS=("-Dfull") + +if [[ "${SKIP_TESTS}" == "true" ]]; then + MVN_FLAGS+=("-DskipTests") +fi + +if [[ "${#EXTRA_MVN_OPTS[@]}" -gt 0 ]]; then + MVN_FLAGS+=("${EXTRA_MVN_OPTS[@]}") +fi + +# ── Build ────────────────────────────────────────────────────────────────────── + +echo "========================================" +echo "Apache KIE repo — build" +echo "Skip tests : ${SKIP_TESTS}" +echo "Maven flags : ${MVN_FLAGS[*]}" +echo "========================================" +echo "" + +mvn clean install "${MVN_FLAGS[@]}" + +echo "" +echo "========================================" +echo "Build complete." +echo "" +echo "JARs installed to local Maven repository (~/.m2/repository)." +echo "========================================" diff --git a/script/release/04-deploy-to-staging.sh b/script/release/04-deploy-to-staging.sh new file mode 100755 index 000000000000..ea9f4db35638 --- /dev/null +++ b/script/release/04-deploy-to-staging.sh @@ -0,0 +1,179 @@ +#!/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/04-deploy-to-staging.sh --tag [--deploy] [--staging-url ] +# +# Examples: +# # dry run (safe — prints the Maven command that would be run) +# ./script/release/04-deploy-to-staging.sh --tag 10.3.0-rc1 +# +# # actually deploy to Apache Nexus staging +# ./script/release/04-deploy-to-staging.sh --tag 10.3.0-rc1 --deploy +# +# # deploy to a custom staging URL +# ./script/release/04-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). +# Passed to the GPG agent via the gpg-agent pinentry loopback +# mechanism; NOT exposed on the Maven command line. + +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 [--deploy] [--staging-url ] [--server-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: -rc (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 rc-commit.sh first, or check out the tag manually." + exit 1 +fi + +# ── Assemble Maven flags ───────────────────────────────────────────────────── + +DEPLOY_MVN_FLAGS=( + "-DskipTests" + "-Dfull" + # Activate the apache-release profile so the source/javadoc and GPG-signing + # goals defined there are executed (required for Apache releases). + "-Papache-release" + "-DaltDeploymentRepository=${SERVER_ID}::default::${STAGING_URL}" +) + +if [[ -n "${MAVEN_SETTINGS:-}" ]]; then + DEPLOY_MVN_FLAGS+=("-s" "${MAVEN_SETTINGS}") +fi + +# NOTE: MAVEN_GPG_PASSPHRASE is intentionally NOT added to the Maven command +# line — exposing it there would print it in captured console logs. +# The standard GPG Maven plugin (version 3+) reads the passphrase from the +# environment variable MAVEN_GPG_PASSPHRASE via pinentry-loopback automatically. +# For older plugin versions, configure loopback in ~/.gnupg/gpg-agent.conf: +# allow-loopback-pinentry +# and pass: -Dgpg.useagent=false -Dgpg.loopback=true in MAVEN_SETTINGS. + +echo "" +if [[ "${DEPLOY}" == "false" ]]; then + echo "[DRY RUN] Would run from tag ${TAG_NAME}:" + echo "" + echo " git checkout ${TAG_NAME}" + echo " mvn deploy ${DEPLOY_MVN_FLAGS[*]}" + echo " git checkout -" + echo "" + echo "Pass --deploy to actually upload artifacts." + exit 0 +fi + +# ── Checkout the exact tag commit before deploying ─────────────────────────── +# Save the original ref as the commit SHA when in detached HEAD state (e.g., +# Jenkins SCM checkout). `git rev-parse --abbrev-ref HEAD` returns "HEAD" when +# detached, which cannot be used to restore the checkout afterwards. +if git symbolic-ref --quiet HEAD &>/dev/null; then + ORIG_REF="$(git rev-parse --abbrev-ref HEAD)" +else + ORIG_REF="$(git rev-parse HEAD)" +fi + +echo "--- Checking out tag ${TAG_NAME} ---" +git checkout "${TAG_NAME}" + +echo "" +echo "--- Deploying to Nexus staging (${STAGING_URL}) ---" +mvn deploy "${DEPLOY_MVN_FLAGS[@]}" + +echo "" +echo "--- Returning to ${ORIG_REF} ---" +git checkout "${ORIG_REF}" + +echo "" +echo "========================================" +echo "Deploy to staging complete." +echo "" +echo "Next steps:" +echo " 1. Log in to https://repository.apache.org and close the staging repo." +echo " 2. Share the staging repo URL with the release vote thread." +echo " 3. Once the vote passes, run:" +echo " ./script/release/05-tag-release.sh --rc-tag ${TAG_NAME}" +echo "========================================" diff --git a/script/release/05-tag-release.sh b/script/release/05-tag-release.sh new file mode 100755 index 000000000000..cc0debdbec6b --- /dev/null +++ b/script/release/05-tag-release.sh @@ -0,0 +1,134 @@ +#!/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 + +# Promotes an approved RC tag to the final release tag (AUTOMATION F). +# +# After the community vote passes on an RC (e.g. 10.3.0-rc2), this script +# creates the final release tag pointing to the same commit: +# +# git tag 10.3.0 10.3.0-rc2 +# +# By default the tag is created locally only. Pass --push to publish it. +# +# Usage: +# ./script/release/05-tag-release.sh --rc-tag [--push] [--dry-run] +# +# Examples: +# ./script/release/05-tag-release.sh --rc-tag 10.3.0-rc2 +# ./script/release/05-tag-release.sh --rc-tag 10.3.0-rc2 --push + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +RC_TAG="" +PUSH=false +DRY_RUN=false + +while [[ $# -gt 0 ]]; do + case $1 in + --rc-tag) + RC_TAG="${2:-}" + shift 2 + ;; + --push) + PUSH=true + shift + ;; + --dry-run) + DRY_RUN=true + shift + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 --rc-tag [--push] [--dry-run]" + exit 1 + ;; + esac +done + +if [[ -z "${RC_TAG}" ]]; then + echo "ERROR: --rc-tag is required." + echo "Usage: $0 --rc-tag 10.3.0-rc2 [--push]" + exit 1 +fi + +if [[ ! "${RC_TAG}" =~ -rc[0-9]+$ ]]; then + echo "ERROR: --rc-tag must be an RC tag ending in -rc (e.g. 10.3.0-rc2), got '${RC_TAG}'." + exit 1 +fi + +# Derive the final release tag (strip -rcN suffix). +RELEASE_TAG="$(echo "${RC_TAG}" | sed 's/-rc[0-9]*$//')" + +cd "${REPO_ROOT}" + +# Verify the RC tag exists. +if ! git rev-parse "${RC_TAG}" &>/dev/null; then + echo "ERROR: RC tag '${RC_TAG}' not found." + echo " Make sure you have fetched the tag: git fetch origin ${RC_TAG}" + exit 1 +fi + +RC_COMMIT="$(git rev-parse "${RC_TAG}")" + +echo "========================================" +echo "Apache KIE repo — tag release" +echo "RC tag : ${RC_TAG} (${RC_COMMIT:0:12})" +echo "Release tag : ${RELEASE_TAG}" +echo "Push : ${PUSH}" +echo "Dry run : ${DRY_RUN}" +echo "========================================" + +if [[ "${DRY_RUN}" == "true" ]]; then + echo "" + echo "[DRY RUN] Would execute:" + echo " git tag -a ${RELEASE_TAG} ${RC_TAG} -m \"Release ${RELEASE_TAG}\"" + if [[ "${PUSH}" == "true" ]]; then + echo " git push origin ${RELEASE_TAG}" + fi + exit 0 +fi + +echo "" +echo "--- Creating release tag ${RELEASE_TAG} at ${RC_TAG} ---" +git tag -a "${RELEASE_TAG}" "${RC_TAG}" -m "Release ${RELEASE_TAG}" + +if [[ "${PUSH}" == "true" ]]; then + echo "" + echo "--- Pushing release tag ${RELEASE_TAG} to origin ---" + git push origin "${RELEASE_TAG}" +fi + +echo "" +echo "========================================" +echo "Release tag complete: ${RELEASE_TAG}" +if [[ "${PUSH}" == "false" ]]; then + echo "" + echo "Tag is LOCAL only. To push when ready:" + echo " git push origin ${RELEASE_TAG}" +fi +echo "" +echo "Next steps (AUTOMATION G — promote artifacts):" +echo " 1. Promote the Nexus staging repository to release." +echo " 2. Run svn mv to move dist artifacts from dev/ to release/." +echo " 3. Announce the release on dev@kie.apache.org." +echo "========================================" diff --git a/script/release/README.md b/script/release/README.md new file mode 100644 index 000000000000..e3e71ce6e006 --- /dev/null +++ b/script/release/README.md @@ -0,0 +1,270 @@ + + +# Release scripts + +Local-first release tooling for the Apache KIE repository. +Since the 10.3.x consolidation, **drools, optaplanner, kogito-runtimes, and +kogito-apps are all modules of the same root POM** — so the release process is +now a single-repo, single-command workflow. + +These scripts implement the "local-first" approach: every step runs on your +machine without needing Jenkins. Jenkins can still call the same scripts, but +you no longer depend on it for the mechanics. + +--- + +## Terminology & Lifecycle Stages + +To avoid confusion between different release phases: + +| Term | Stage | Meaning & Scope | +|---|---|---| +| **`update-version`** | Automation A / D | Updates Maven POM versions across all modules in the reactor to stream snapshots (`10.3.999-SNAPSHOT`) or exact release versions (`10.3.0`). | +| **`rc-commit` / `rc-tag`** | Automation D.1 | Creates a temporary branch, bumps to the release version, creates the R-commit, tags the Git RC (`10.3.0-rc1`), and cleans up the temporary branch. | +| **`build`** | Automation D.2 | Compiles the full Maven reactor and installs artifacts to the local `~/.m2/repository`. | +| **`deploy` / `staging`** | Automation D.3 | GPG signs artifacts and deploys them to the Apache Nexus Staging repository for release vote inspection. | +| **`tag-release`** | Automation F | Promotes an approved RC tag (e.g., `10.3.0-rc2`) to the final release tag (`10.3.0`) once the release vote passes. | +| **`promote` / `publish`** | Automation G | Releases the staging repository in Nexus to Maven Central and publishes release distribution files. | + +--- + +## Numbered Scripts + +Scripts are numbered to clearly communicate their intended execution order: + +| Number / Script | Automation Stage | Purpose | +|---|---|---| +| **[`01-update-version.sh`](#01-update-versionsh)** *(alias: `update-version.sh`)* | Automation A / D | Update Maven POM versions + data-index image tag property | +| **[`02-rc-commit.sh`](#02-rc-commitsh)** *(alias: `rc-commit.sh`)* | Automation D.1 | Create temporary branch, R commit, and RC git tag | +| **[`03-build.sh`](#03-buildsh)** *(alias: `build.sh`)* | Automation D.2 | Build the full reactor to local `~/.m2/repository` | +| **[`04-deploy-to-staging.sh`](#04-deploy-to-stagingsh)** *(alias: `deploy-to-staging.sh`)* | Automation D.3 | Deploy signed JARs to Apache Nexus staging repository | +| **[`05-tag-release.sh`](#05-tag-releasesh)** *(alias: `tag-release.sh`)* | Automation F | Promote an approved RC tag → final release tag | +| **[`release-all.sh`](#release-allsh)** | Orchestrator (D) | Orchestrate steps 02 → 03 → 04 in one single command | + +Make the scripts executable once: + +```bash +chmod +x script/release/*.sh +``` + +--- + +## Quick start — full RC candidate locally + +```bash +# 1. Be on the development stream branch +git checkout 10.3.x + +# 2. Run the full RC flow (no remote side-effects — dry run first) +./script/release/release-all.sh \ + --version 10.3.0 --tag 10.3.0-rc1 \ + --skip-tests \ + --dry-run + +# 3. If the dry run looks right, remove --dry-run +./script/release/release-all.sh \ + --version 10.3.0 --tag 10.3.0-rc1 \ + --skip-tests +``` + +This leaves the RC tag locally (`git tag -l 10.3.0-rc1`). Nothing has been +pushed or published. When you are ready to share the RC: + +```bash +# Push the RC tag and deploy to Apache Nexus staging +./script/release/release-all.sh \ + --version 10.3.0 --tag 10.3.0-rc1 \ + --skip-tests --push-tag --deploy +``` + +--- + +## Scripts in detail + +### `01-update-version.sh` + +Updates every Maven module's `` in the reactor and the +`data-index-ephemeral.image.tagVersion` property in +`kogito-quarkus-workflow-common-deployment`. + +```bash +# Switch to a dev (SNAPSHOT) version — D commit (Automation A) +./script/release/01-update-version.sh 10.3.999-SNAPSHOT + +# Switch to the exact release version — R commit (usually automated by 02-rc-commit.sh) +./script/release/01-update-version.sh 10.3.0 +``` + +> **Stream vs release version.** Development branches keep +> `major.minor.999-SNAPSHOT` as their version. The version is only set to +> the exact release version (`10.3.0`) in the short-lived R commit created by +> `02-rc-commit.sh`. + +--- + +### `02-rc-commit.sh` + +Creates the **R commit** (version bump to the exact release version) on a +short-lived *local-only* release branch, then tags that commit. The release +branch is deleted afterwards — only the tag survives. + +```bash +# Local tag only (safe — nothing is pushed) +./script/release/02-rc-commit.sh --version 10.3.0 --tag 10.3.0-rc1 + +# Push the tag to origin when you're ready +./script/release/02-rc-commit.sh --version 10.3.0 --tag 10.3.0-rc1 --push + +# See what would happen without making changes +./script/release/02-rc-commit.sh --version 10.3.0 --tag 10.3.0-rc1 --dry-run +``` + +The script refuses to run if there are any uncommitted changes in the working +tree. + +--- + +### `03-build.sh` + +Builds the full reactor. JARs are installed to your local `~/.m2` repository. + +```bash +# Standard release build (tests skipped) +./script/release/03-build.sh --skip-tests + +# With extra Maven options (e.g. parallelism) +./script/release/03-build.sh --skip-tests --maven-opts "-T 4" +``` + +--- + +### `04-deploy-to-staging.sh` + +Deploys the locally-built JARs to Apache Nexus staging. **Dry run by +default** — the `--deploy` flag is required to actually upload anything. + +```bash +# Dry run — prints the Maven command without executing it +./script/release/04-deploy-to-staging.sh --tag 10.3.0-rc1 + +# Actually deploy to Apache Nexus staging +MAVEN_SETTINGS=/path/to/settings.xml \ +MAVEN_GPG_PASSPHRASE=secret \ +./script/release/04-deploy-to-staging.sh --tag 10.3.0-rc1 --deploy +``` + +After deployment, log in to , close the +staging repository, and share the URL with the release vote thread on +`dev@kie.apache.org`. + +**Required environment variables when `--deploy` is active:** + +| Variable | Description | +|---|---| +| `MAVEN_SETTINGS` | Path to a `settings.xml` with Nexus deployer credentials | +| `MAVEN_GPG_PASSPHRASE` | GPG key passphrase for signing release artifacts | + +--- + +### `05-tag-release.sh` + +Promotes a winning RC tag to the final release tag (AUTOMATION F). + +```bash +# Local tag only +./script/release/05-tag-release.sh --rc-tag 10.3.0-rc2 + +# Push the release tag to origin +./script/release/05-tag-release.sh --rc-tag 10.3.0-rc2 --push +``` + +--- + +### `release-all.sh` + +Orchestrates the full RC cycle in one command. + +```bash +./script/release/release-all.sh \ + --version 10.3.0 \ + --tag 10.3.0-rc1 \ + [--skip-tests] \ + [--maven-opts ""] \ + [--deploy] \ + [--staging-url ] \ + [--push-tag] \ + [--dry-run] +``` + +Steps executed in order: + +1. `02-rc-commit.sh` — R commit + tag +2. `03-build.sh` — full build at the tag commit +3. `04-deploy-to-staging.sh` — Nexus staging upload *(only if `--deploy`)* + +--- + +## Development stream vs release version + +| Situation | Version | Command | +|---|---|---| +| Daily development on `main` | `999-SNAPSHOT` | `update-version.sh 999-SNAPSHOT` | +| Daily development on `10.3.x` | `10.3.999-SNAPSHOT` | `update-version.sh 10.3.999-SNAPSHOT` | +| Creating RC / release | `10.3.0` | Handled automatically by `rc-commit.sh` | + +--- + +## Jenkins integration + +Jenkins calls these exact same scripts. The only difference is that Jenkins +supplies credentials via environment variables: + +```groovy +withCredentials([ + file(credentialsId: 'kie-release-settings', variable: 'MAVEN_SETTINGS'), + string(credentialsId: 'GPG_KEY_PASSPHRASE', variable: 'MAVEN_GPG_PASSPHRASE') +]) { + sh """ + ./script/release/release-all.sh \ + --version \${RELEASE_VERSION} \ + --tag \${GIT_TAG_NAME} \ + --skip-tests --deploy --push-tag + """ +} +``` + +--- + +## Relationship to the 10.2.x multi-repo flow + +In 10.2.x the release involved five separate repositories +(drools → optaplanner → kogito-runtimes → kogito-apps → kie-tools), each with +its own Jenkins job. Now that the first four repos are merged, the steps map +as follows: + +| Old step | New equivalent | +|---|---| +| Trigger Drools release job | `./script/release/release-all.sh` | +| Trigger OptaPlanner release job | *(same command — same reactor)* | +| Trigger Kogito Runtimes release job | *(same command — same reactor)* | +| Trigger Kogito Apps release job | *(same command — same reactor)* | +| `git tag 10.x.0 10.x.0-rcN` in all repos | `./script/release/tag-release.sh` | + +`kie-tools` remains a separate repository and is unaffected by these scripts. diff --git a/script/release/release-all.sh b/script/release/release-all.sh new file mode 100755 index 000000000000..8464c4579aad --- /dev/null +++ b/script/release/release-all.sh @@ -0,0 +1,201 @@ +#!/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 + +# Master orchestrator for the local-first release workflow. +# +# Runs the individual release scripts in the correct order for a full release +# candidate cycle (02-rc-commit.sh -> 03-build.sh -> 04-deploy-to-staging.sh). +# Each step can also be run independently — see script/release/README.md. +# +# Usage: +# ./script/release/release-all.sh [--tag ] [OPTIONS] +# ./script/release/release-all.sh --version [--tag ] [OPTIONS] +# +# Required (one of): +# or --version Exact release version, e.g. 10.3.0 +# +# Optional tags: +# --tag | --rc-tag RC tag name, e.g. 10.3.0-rc1 (defaults to -rc1) +# --rc Explicit flag indicating RC mode +# +# Optional build flags: +# --skip-tests | --skip-build Skip tests during the build +# --maven-opts Extra Maven options forwarded to build.sh +# +# Optional deploy/publish flags: +# --deploy | --publish Deploy JARs to Nexus staging after the build +# --staging-url Nexus staging URL (default: Apache Nexus) +# +# Optional git flags: +# --push | --push-tag Push the RC tag to origin after creating it +# +# Other: +# --dry-run Print what would happen without executing anything +# +# Examples: +# # Positional or flag version +# ./script/release/release-all.sh 10.3.0 --rc --skip-tests +# ./script/release/release-all.sh --version 10.3.0 --tag 10.3.0-rc1 --skip-tests +# +# # Full RC with deploy to Apache Nexus staging and push tag to origin +# ./script/release/release-all.sh 10.3.0 --tag 10.3.0-rc1 --skip-tests --deploy --push-tag + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +RELEASE_VERSION="" +TAG_NAME="" +SKIP_TESTS=false +EXTRA_MVN_OPTS="" +DEPLOY=false +STAGING_URL="" +PUSH_TAG=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 ;; # Accepted for parity with tools repo + --skip-tests|--skip-build) SKIP_TESTS=true; shift ;; + --maven-opts) EXTRA_MVN_OPTS="${2:-}"; shift 2 ;; + --deploy|--publish) DEPLOY=true; shift ;; + --staging-url) STAGING_URL="${2:-}"; shift 2 ;; + --push|--push-tag) PUSH_TAG=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 ] [--tag ] [OPTIONS]" + 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] [OPTIONS]" + exit 1 +fi + +if [[ -z "${TAG_NAME}" ]]; then + TAG_NAME="${RELEASE_VERSION}-rc1" +fi + +cd "${REPO_ROOT}" + +echo "" +echo "==========================================" +echo "Apache KIE repo — release-all" +echo "Version : ${RELEASE_VERSION}" +echo "RC tag : ${TAG_NAME}" +echo "Skip tests : ${SKIP_TESTS}" +echo "Deploy staging : ${DEPLOY}" +echo "Push tag : ${PUSH_TAG}" +echo "Dry run : ${DRY_RUN}" +echo "==========================================" +echo "" + +# ── Helper ─────────────────────────────────────────────────────────────────── + +run_step() { + local step_name="$1" + shift + echo "" + echo "────────────────────────────────────────" + echo "STEP: ${step_name}" + echo "────────────────────────────────────────" + if [[ "${DRY_RUN}" == "true" ]]; then + echo "[DRY RUN] $*" + else + "$@" + fi + echo "✅ ${step_name} — done" +} + +# ── STEP 1 (Automation D.1): R commit + RC tag ─────────────────────────────── + +RC_COMMIT_SCRIPT="${SCRIPT_DIR}/02-rc-commit.sh" + +RC_COMMIT_ARGS=("${RC_COMMIT_SCRIPT}" "--version" "${RELEASE_VERSION}" "--tag" "${TAG_NAME}") +[[ "${PUSH_TAG}" == "true" ]] && RC_COMMIT_ARGS+=("--push") +[[ "${DRY_RUN}" == "true" ]] && RC_COMMIT_ARGS+=("--dry-run") + +run_step "R commit + RC tag (02-rc-commit.sh)" "${RC_COMMIT_ARGS[@]}" + +# ── STEP 2 (Automation D.2): Build ─────────────────────────────────────────── + +BUILD_SCRIPT="${SCRIPT_DIR}/03-build.sh" + +BUILD_ARGS=("${BUILD_SCRIPT}") +[[ "${SKIP_TESTS}" == "true" ]] && BUILD_ARGS+=("--skip-tests") +[[ -n "${EXTRA_MVN_OPTS}" ]] && BUILD_ARGS+=("--maven-opts" "${EXTRA_MVN_OPTS}") + +# The build must run at the RC tag commit. 02-rc-commit.sh leaves HEAD on the +# development branch, so we check out the tag, build, then return. +if [[ "${DRY_RUN}" == "true" ]]; then + run_step "Build @ ${TAG_NAME} (03-build.sh)" echo "[DRY RUN] git checkout ${TAG_NAME} && ${BUILD_ARGS[*]} && git checkout -" +else + echo "" + echo "────────────────────────────────────────" + echo "STEP: Build @ ${TAG_NAME} (03-build.sh)" + echo "────────────────────────────────────────" + git checkout "${TAG_NAME}" + "${BUILD_ARGS[@]}" + git checkout - + echo "✅ Build — done" +fi + +# ── STEP 3 (Automation D.3): Deploy to Nexus staging (optional) ────────────── + +if [[ "${DEPLOY}" == "true" ]]; then + DEPLOY_SCRIPT="${SCRIPT_DIR}/04-deploy-to-staging.sh" + + DEPLOY_ARGS=("${DEPLOY_SCRIPT}" "--tag" "${TAG_NAME}" "--deploy") + [[ -n "${STAGING_URL}" ]] && DEPLOY_ARGS+=("--staging-url" "${STAGING_URL}") + run_step "Deploy to Nexus staging (04-deploy-to-staging.sh)" "${DEPLOY_ARGS[@]}" +fi + +# ── Summary ─────────────────────────────────────────────────────────────────── + +echo "" +echo "==========================================" +echo "release-all complete ✅" +echo "" +echo " RC tag : ${TAG_NAME}" +echo " Version : ${RELEASE_VERSION}" +echo "" +if [[ "${DEPLOY}" == "true" ]]; then + echo "Artifacts are in Nexus staging." + echo "Close the staging repo, then start the vote on dev@kie.apache.org." +else + echo "Artifacts are in your local ~/.m2 repository only." + echo "Run 04-deploy-to-staging.sh --tag ${TAG_NAME} --deploy when ready." +fi +echo "" +echo "When the vote passes, promote the RC to a final release tag:" +echo " ./script/release/05-tag-release.sh --rc-tag ${TAG_NAME} --push" +echo "==========================================" diff --git a/script/release/upload_filemgmt.sh b/script/release/upload_filemgmt.sh new file mode 100755 index 000000000000..50fe339e254c --- /dev/null +++ b/script/release/upload_filemgmt.sh @@ -0,0 +1,127 @@ +#!/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 + +# Uploads Drools release binaries and documentation to the Apache file +# management server (people.apache.org) via SSH/rsync. +# +# Called by Jenkinsfile.103xplus.promote (and the original Jenkinsfile.promote) +# inside an sshagent('drools-filemgmt') block, so SSH authentication is already +# in the agent — no credential parameters needed here. +# +# Usage: +# ./script/release/upload_filemgmt.sh +# +# Examples: +# ./script/release/upload_filemgmt.sh 10.3.0 +# +# Environment variables (all have defaults): +# FILEMGMT_HOST Remote host (default: people.apache.org) +# FILEMGMT_BASE_PATH Remote base path (default: /home/drools/filemgmt) +# DRY_RUN Set to 'true' to print commands without running them + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +RELEASE_VERSION="${1:-}" + +if [[ -z "${RELEASE_VERSION}" ]]; then + echo "ERROR: release version argument is required." + echo "Usage: $0 " + echo " e.g. $0 10.3.0" + exit 1 +fi + +FILEMGMT_HOST="${FILEMGMT_HOST:-people.apache.org}" +FILEMGMT_BASE_PATH="${FILEMGMT_BASE_PATH:-/home/drools/filemgmt}" +DRY_RUN="${DRY_RUN:-false}" + +# Derive major.minor stream (e.g. 10.3.0 → 10.3) +STREAM="$(echo "${RELEASE_VERSION}" | sed 's/^\([0-9]*\.[0-9]*\)\..*/\1/')" + +REMOTE_BINARIES_DIR="${FILEMGMT_BASE_PATH}/downloads_htdocs/drools/release/${STREAM}.x" +REMOTE_DOCS_DIR="${FILEMGMT_BASE_PATH}/docs_htdocs/drools/release/${STREAM}.x" + +echo "========================================" +echo "Apache KIE — upload binaries & docs" +echo "Release version : ${RELEASE_VERSION}" +echo "Stream : ${STREAM}" +echo "Remote host : ${FILEMGMT_HOST}" +echo "Remote binaries : ${REMOTE_BINARIES_DIR}" +echo "Remote docs : ${REMOTE_DOCS_DIR}" +echo "Dry run : ${DRY_RUN}" +echo "========================================" + +cd "${REPO_ROOT}" + +# ── Locate locally-built distribution zips ─────────────────────────────────── +# Maven -Dfull produces zip artifacts under each module's target/ directory. +# We upload everything matching the release version. +BINARY_ZIPS=() +while IFS= read -r -d '' f; do + BINARY_ZIPS+=("${f}") +done < <(find . -path "*/target/*-${RELEASE_VERSION}*.zip" ! -path "*/target/checkout/*" -print0 2>/dev/null) + +if [[ ${#BINARY_ZIPS[@]} -eq 0 ]]; then + echo "WARNING: No distribution zips found for version ${RELEASE_VERSION}." + echo " Run 03-build.sh with -Dfull before uploading." +fi + +# ── Helper: run or echo ─────────────────────────────────────────────────────── +run() { + if [[ "${DRY_RUN}" == "true" ]]; then + echo "[DRY RUN] $*" + else + "$@" + fi +} + +# ── Create remote directories ───────────────────────────────────────────────── +run ssh "${FILEMGMT_HOST}" "mkdir -p '${REMOTE_BINARIES_DIR}/${RELEASE_VERSION}' '${REMOTE_DOCS_DIR}/${RELEASE_VERSION}'" + +# ── Upload distribution zips ────────────────────────────────────────────────── +if [[ ${#BINARY_ZIPS[@]} -gt 0 ]]; then + echo "" + echo "--- Uploading ${#BINARY_ZIPS[@]} distribution zip(s) ---" + for zip in "${BINARY_ZIPS[@]}"; do + echo " ${zip}" + run rsync -avz --progress \ + "${zip}" \ + "${FILEMGMT_HOST}:${REMOTE_BINARIES_DIR}/${RELEASE_VERSION}/" + done +fi + +# ── Upload HTML documentation (if generated) ───────────────────────────────── +DOCS_DIR="target/docs" +if [[ -d "${DOCS_DIR}" ]]; then + echo "" + echo "--- Uploading docs from ${DOCS_DIR} ---" + run rsync -avz --progress --delete \ + "${DOCS_DIR}/" \ + "${FILEMGMT_HOST}:${REMOTE_DOCS_DIR}/${RELEASE_VERSION}/" +else + echo "(skipping docs upload — ${DOCS_DIR} not found)" +fi + +echo "" +echo "========================================" +echo "Upload complete for version ${RELEASE_VERSION}." +echo "========================================"