From f4ecda2b2d6b2d92a375ce2331e7768f6c9f352c Mon Sep 17 00:00:00 2001 From: Jesus Osuna Date: Fri, 17 Jul 2026 21:24:20 -0400 Subject: [PATCH 1/4] Migrate publishing from Maven Central to PackageCloud Part of RUN-4570: Maven Central publishing limits resolution, applying the same design validated on rundeck-plugins/sshj-plugin (PR #136), rundeck-plugins/http-step (PR #60), and rundeck-plugins/ansible-plugin (PR #456). - Remove nexusPublish plugin/nexusPublishing block (Sonatype-specific, dead once release.yml stops calling publishToSonatype). - Point the PackageCloud maven repo at the configurable PKGCLD_REPO_URL (trimmed, guarded so plain builds/CI without the var don't fail at configuration time) instead of the hardcoded rundeckpro-test URL left over from an earlier manual test. - Drop GPG signing: PackageCloud's Maven endpoint rejects the checksum Gradle auto-generates for .asc signature files (422 Unprocessable Entity), and neither the OSS rundeck WAR nor rundeckpro-enterprise carry .asc signatures on PackageCloud either - only RPM/DEB packages are signed there natively. - release.yml: replace the Sonatype publish step with PackageCloud. --- .github/workflows/release.yml | 11 +++++------ build.gradle | 11 ----------- gradle/publishing.gradle | 36 +++++++++-------------------------- 3 files changed, 14 insertions(+), 44 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4d07d53..86f02c8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,6 +9,8 @@ jobs: build: name: Publish Release runs-on: ubuntu-latest + env: + PKGCLD_REPO_URL: ${{ vars.PKGCLD_REPO_URL }} steps: - name: Checkout code uses: actions/checkout@v4 @@ -33,10 +35,7 @@ jobs: build/libs/openssh-node-execution-${{ steps.get_version.outputs.VERSION }}.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Publish to Maven Central - run: ./gradlew -PsigningKey=${SIGNING_KEY_B64} -PsigningPassword=${SIGNING_PASSWORD} -PsonatypeUsername=${SONATYPE_USERNAME} -PsonatypePassword=${SONATYPE_PASSWORD} publishToSonatype closeAndReleaseSonatypeStagingRepository + - name: Publish to PackageCloud + run: ./gradlew publishAllPublicationsToPackageCloudRepository env: - SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} - SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} - SIGNING_KEY_B64: ${{ secrets.SIGNING_KEY_B64 }} - SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} + PKGCLD_WRITE_TOKEN: ${{ secrets.PKGCLD_WRITE_TOKEN }} diff --git a/build.gradle b/build.gradle index 164d9b1..311ee4f 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,6 @@ buildscript { plugins { id 'java' id 'pl.allegro.tech.build.axion-release' version '1.17.2' - id 'io.github.gradle-nexus.publish-plugin' version '2.0.0' } repositories { @@ -116,16 +115,6 @@ tasks.named('pluginZip').configure { } } -nexusPublishing { - packageGroup = 'org.rundeck.plugins' - repositories { - sonatype { - nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) - snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) - } - } -} - apply from: "${rootDir}/gradle/publishing.gradle" defaultTasks 'clean', 'build', 'pluginZip' diff --git a/gradle/publishing.gradle b/gradle/publishing.gradle index a100a4f..73d7947 100644 --- a/gradle/publishing.gradle +++ b/gradle/publishing.gradle @@ -1,19 +1,13 @@ /** - * Zip-based Rundeck plugin publication for Maven Central (artifact uploaded as type jar). + * Zip-based Rundeck plugin publication (artifact uploaded as type jar). * * Requires before this file: * - ext.publishName, ext.githubSlug, ext.developers * - task pluginZip - * - * ./gradlew \ - * -PsigningKey="base64 encoded gpg key" \ - * -PsigningPassword="password for key" \ - * -PsonatypeUsername="sonatype token user" \ - * -PsonatypePassword="sonatype token password" \ - * publishToSonatype closeAndReleaseSonatypeStagingRepository */ apply plugin: 'maven-publish' -apply plugin: 'signing' + +def pkgcldRepoUrl = System.getenv("PKGCLD_REPO_URL")?.trim() publishing { publications { @@ -59,32 +53,20 @@ publishing { } } repositories { - def pkgcldWriteToken = System.getenv("PKGCLD_WRITE_TOKEN") ?: project.findProperty("pkgcldWriteToken") - if (pkgcldWriteToken) { + // Only register the PackageCloud repo when its URL is configured, so plain + // builds/tests (which don't set PKGCLD_REPO_URL) don't fail at configuration time. + if (pkgcldRepoUrl) { maven { - name = "PackageCloudTest" - url = uri("https://packagecloud.io/pagerduty/rundeckpro-test/maven2") + name = "PackageCloud" + url = uri(pkgcldRepoUrl) authentication { header(HttpHeaderAuthentication) } credentials(HttpHeaderCredentials) { name = "Authorization" - value = "Bearer " + pkgcldWriteToken + value = "Bearer " + (System.getenv("PKGCLD_WRITE_TOKEN") ?: project.findProperty("pkgcldWriteToken")) } } } } } - -def base64Decode = { String prop -> - project.findProperty(prop) ? - new String(Base64.getDecoder().decode(project.findProperty(prop).toString())).trim() : - null -} - -if (project.hasProperty('signingKey') && project.hasProperty('signingPassword')) { - signing { - useInMemoryPgpKeys(base64Decode("signingKey"), project.signingPassword) - sign(publishing.publications) - } -} From bda79139884d1585dd120cd2f08278c385587f13 Mon Sep 17 00:00:00 2001 From: Jesus Osuna Date: Fri, 17 Jul 2026 21:41:02 -0400 Subject: [PATCH 2/4] Fail fast in release.yml if PackageCloud env vars are missing Addresses GitHub Copilot review feedback on the PackageCloud migration PRs: without this, a missing PKGCLD_REPO_URL makes the publish task not exist at all (confusing "task not found"), and a missing PKGCLD_WRITE_TOKEN with the URL present produces a "Bearer null" Authorization header that PackageCloud rejects with a non-obvious 401/422. Validate both upfront in the one step that actually needs them, instead of leaving the Gradle-side guard (which exists to keep unrelated CI like gradle.yml working without these vars) to surface the failure indirectly. --- .github/workflows/release.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 86f02c8..ed1ecbc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,6 +36,11 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Publish to PackageCloud - run: ./gradlew publishAllPublicationsToPackageCloudRepository + run: | + if [ -z "$PKGCLD_REPO_URL" ] || [ -z "$PKGCLD_WRITE_TOKEN" ]; then + echo "::error::PKGCLD_REPO_URL and PKGCLD_WRITE_TOKEN must both be set to publish to PackageCloud" + exit 1 + fi + ./gradlew publishAllPublicationsToPackageCloudRepository env: PKGCLD_WRITE_TOKEN: ${{ secrets.PKGCLD_WRITE_TOKEN }} From fb24480d45f0c77eceac4ed1a1ef4b30a99a8996 Mon Sep 17 00:00:00 2001 From: Jesus Osuna Date: Mon, 20 Jul 2026 13:20:44 -0400 Subject: [PATCH 3/4] Fall back to rundeck-plugins PackageCloud URL, simplify token check Addresses the remaining GitHub Copilot review feedback: default pkgcldRepoUrl to the real, canonical rundeck-plugins PackageCloud URL instead of relying on PKGCLD_REPO_URL always being set. This removes the need for the "only register if URL present" guard entirely, since uri() never receives null - registering a Maven repository doesn't make any network call until something actually resolves/publishes through it, so there's no cost to always registering it. With the URL guaranteed non-null, release.yml's fail-fast check only needs to validate PKGCLD_WRITE_TOKEN (which still has no safe public default, since it's a secret). --- .github/workflows/release.yml | 4 ++-- gradle/publishing.gradle | 24 ++++++++++-------------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ed1ecbc..fa7c788 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,8 +37,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Publish to PackageCloud run: | - if [ -z "$PKGCLD_REPO_URL" ] || [ -z "$PKGCLD_WRITE_TOKEN" ]; then - echo "::error::PKGCLD_REPO_URL and PKGCLD_WRITE_TOKEN must both be set to publish to PackageCloud" + if [ -z "$PKGCLD_WRITE_TOKEN" ]; then + echo "::error::PKGCLD_WRITE_TOKEN must be set to publish to PackageCloud" exit 1 fi ./gradlew publishAllPublicationsToPackageCloudRepository diff --git a/gradle/publishing.gradle b/gradle/publishing.gradle index 73d7947..c90b6b6 100644 --- a/gradle/publishing.gradle +++ b/gradle/publishing.gradle @@ -7,7 +7,7 @@ */ apply plugin: 'maven-publish' -def pkgcldRepoUrl = System.getenv("PKGCLD_REPO_URL")?.trim() +def pkgcldRepoUrl = (System.getenv("PKGCLD_REPO_URL") ?: "https://packagecloud.io/pagerduty/rundeck-plugins/maven2").trim() publishing { publications { @@ -53,19 +53,15 @@ publishing { } } repositories { - // Only register the PackageCloud repo when its URL is configured, so plain - // builds/tests (which don't set PKGCLD_REPO_URL) don't fail at configuration time. - if (pkgcldRepoUrl) { - maven { - name = "PackageCloud" - url = uri(pkgcldRepoUrl) - authentication { - header(HttpHeaderAuthentication) - } - credentials(HttpHeaderCredentials) { - name = "Authorization" - value = "Bearer " + (System.getenv("PKGCLD_WRITE_TOKEN") ?: project.findProperty("pkgcldWriteToken")) - } + maven { + name = "PackageCloud" + url = uri(pkgcldRepoUrl) + authentication { + header(HttpHeaderAuthentication) + } + credentials(HttpHeaderCredentials) { + name = "Authorization" + value = "Bearer " + (System.getenv("PKGCLD_WRITE_TOKEN") ?: project.findProperty("pkgcldWriteToken")) } } } From 078e3d387a2bab92d3621a5401847c1c379e06f1 Mon Sep 17 00:00:00 2001 From: Jesus Osuna Date: Mon, 20 Jul 2026 17:56:01 -0400 Subject: [PATCH 4/4] Sign and upload GPG signatures to PackageCloud manually Validated end-to-end on rundeck-plugins/sshj-plugin (merged): signing via sign(publishing.publications) makes Gradle auto-generate a checksum for every publication artifact including the .asc files themselves, and PackageCloud's Maven endpoint 422s on the checksum-of-a-signature-file. Sidestep this entirely by signing the already-built artifacts with the gpg CLI directly and uploading just the .asc files via curl, bypassing Gradle's publish/checksum machinery for this part. Reuses the existing SIGNING_KEY_B64/SIGNING_PASSWORD secrets (now pointing at a renewed, non-expired key). --- .github/workflows/release.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fa7c788..721e62b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,3 +44,31 @@ jobs: ./gradlew publishAllPublicationsToPackageCloudRepository env: PKGCLD_WRITE_TOKEN: ${{ secrets.PKGCLD_WRITE_TOKEN }} + - name: Sign and upload GPG signatures to PackageCloud + run: | + set -e + VERSION="${{ steps.get_version.outputs.VERSION }}" + BASE_URL="${PKGCLD_REPO_URL:-https://packagecloud.io/pagerduty/rundeck-plugins/maven2}/org/rundeck/plugins/openssh-node-execution/${VERSION}" + + echo "$SIGNING_KEY_B64" | base64 -d | gpg --batch --yes --import + KEY_ID=$(gpg --list-secret-keys --with-colons | awk -F: '/^sec/ {print $5; exit}') + + sign_and_upload() { + local FILE="$1" + local REMOTE_NAME="$2" + gpg --batch --yes --pinentry-mode loopback --passphrase "$SIGNING_PASSWORD" --default-key "$KEY_ID" --detach-sign --armor "$FILE" + curl -sf -H "Authorization: Bearer ${PKGCLD_WRITE_TOKEN}" \ + -X PUT --data-binary "@${FILE}.asc" \ + "${BASE_URL}/${REMOTE_NAME}.asc" + } + + # rootProject.name is "openssh-exec-plugin" (a stray build/libs/openssh-exec-plugin-*.jar + # also gets produced by the java plugin, but it's not what's published). The actual + # published artifact is the pluginZip output, named after the Maven artifactId + # ("openssh-node-execution") with a ".jar" extension override. + sign_and_upload "build/libs/openssh-node-execution-${VERSION}.zip" "openssh-node-execution-${VERSION}.jar" + sign_and_upload "build/publications/mavenZip/pom-default.xml" "openssh-node-execution-${VERSION}.pom" + env: + SIGNING_KEY_B64: ${{ secrets.SIGNING_KEY_B64 }} + SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} + PKGCLD_WRITE_TOKEN: ${{ secrets.PKGCLD_WRITE_TOKEN }}