This is an automated email from the ASF dual-hosted git repository. royteeuwen pushed a commit to branch feature/sling-cli-release-automation in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git
commit 4ebd9fae5f3150c4953e6176a44fb1cebaa2daf1 Author: Roy Teeuwen <[email protected]> AuthorDate: Sat May 30 08:09:57 2026 +0200 release docs: prerequisites, README workflow, release skill Document the full release flow (manual Maven steps + CLI steps) and the prerequisites. In particular, correct the GPG passphrase setup for maven-gpg-plugin 3.x: the passphrase is read from a server whose id is gpg.passphraseServerId (default "gpg.passphrase") and decrypted via settings-security.xml, not from a <gpg.passphrase> profile property. Add a self-contained sling-release skill that automates the process. --- README.md | 204 ++++++++++++++++++++++++++++++++++++++---- skills/sling-release/SKILL.md | 180 +++++++++++++++++++++++++++++++++++++ 2 files changed, 368 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 3334d40..8f17c15 100644 --- a/README.md +++ b/README.md @@ -9,17 +9,108 @@ This module is part of the [Apache Sling](https://sling.apache.org) project. This module provides a command-line tool which automates various Sling development tasks. The tool is packaged as a docker image. -## Configuration - -To make various credentials and configurations available to the docker image it is recommended to use a docker env file. -A sample file is stored at `docker-env.sample`. Copy this file to `docker-env` and fill in your own information. +## Prerequisites + +Before releasing you need the following in place. These are split between the **Maven side** (used by +`mvn release:prepare`/`release:perform` in the project being released) and the **CLI side** (used by +this Docker tool). + +### 1. ASF credentials for the CLI (`docker-env`) + +To make credentials available to the docker image, use a docker env file. A sample file is stored at +`docker-env.sample`; copy it to `docker-env` and fill in your own information. At minimum it must +provide your ASF credentials (these are read by the CLI for Nexus, JIRA, Whimsy and the mailing +lists): + + ASF_USERNAME=your-apache-id + ASF_PASSWORD=your-apache-password + +### 2. GPG signing key + +* Generate a code-signing key and publish it: `gpg --send-keys <KEY_ID>` to `keys.openpgp.org`, and + add its public block to the Sling `KEYS` file at + <https://dist.apache.org/repos/dist/release/sling/KEYS> (PMC members can commit there directly). +* Note the key id (e.g. `0A1B2C3D4E5F6789`) — it goes into `settings.xml` below. + +### 3. Maven `~/.m2/settings.xml` + +Recent Apache/Sling parent POMs use **maven-gpg-plugin 3.x**, which changed how the GPG passphrase is +supplied. The old approach of a `<gpg.passphrase>` property in the `apache-release` profile is **no +longer used** — the plugin now reads the passphrase from a **server** whose id is given by +`gpg.passphraseServerId` (default: `gpg.passphrase`) and decrypts it via `settings-security.xml`. + +```xml +<settings> + <servers> + <!-- ASF Nexus credentials (encrypted, see settings-security.xml) --> + <server> + <id>apache.snapshots.https</id> + <username>your-apache-id</username> + <password>{ENCRYPTED}</password> + </server> + <server> + <id>apache.releases.https</id> + <username>your-apache-id</username> + <password>{ENCRYPTED}</password> + </server> + + <!-- maven-gpg-plugin 3.x reads the passphrase from THIS server id (gpg.passphraseServerId + defaults to "gpg.passphrase") and decrypts it via settings-security.xml. --> + <server> + <id>gpg.passphrase</id> + <passphrase>{ENCRYPTED}</passphrase> + </server> + </servers> + <profiles> + <profile> + <id>apache-release</id> + <properties> + <apache.availid>your-apache-id</apache.availid> + <!-- required so gpg does not try to open an interactive pinentry dialog --> + <gpg.pinentryMode>loopback</gpg.pinentryMode> + <!-- the signing key id from step 2 --> + <gpg.keyname>YOUR_KEY_ID</gpg.keyname> + <!-- SMTP host used by the parent POM's announcement tooling --> + <smtp.host>smtp.gmail.com</smtp.host> + </properties> + </profile> + </profiles> +</settings> +``` + +Encrypt the passwords/passphrase with `mvn --encrypt-password` (server passwords) and store the master +password in `~/.m2/settings-security.xml`: + +```xml +<settingsSecurity> + <master>{ENCRYPTED_MASTER_PASSWORD}</master> +</settingsSecurity> +``` + +## Building + +The Docker image (`apache/sling-cli:latest`) is produced by the `docker:build` goal of the +[fabric8 docker-maven-plugin](https://dmp.fabric8.io/). The image bundles the project jar via the +generated `*-app.slingfeature` descriptor; the `slingfeature-maven-plugin` resolves the project's +own bundle from the reactor, so the image always contains the jar built in the same invocation. + +A single command builds (and tests) the project and the image: + + mvn clean package docker:build + +No prior `mvn install` is needed. The `docker:build` execution is also bound to the `package` +phase, so the CI build (`mvn package`) builds the image too. + +To confirm the image contains the expected commands: + + docker run --env-file=./docker-env apache/sling-cli release help ## Launching -The image is built using `mvn package`. Afterwards it may be run with +After building, run the image with: docker run --env-file=./docker-env apache/sling-cli - + This invocation produces a list of available commands. ## Commands @@ -39,21 +130,102 @@ To select a non-default execution mode provide the mode as an argument to the co Note that for running commands in the `INTERACTIVE` mode you need to run the Docker container in interactive mode with a pseudo-tty attached (e.g. `docker run -it ...`). -Listing active releases +### Release workflow + +The full release lifecycle has **manual Maven steps** (run in the project being released) and **CLI +steps** (run via this Docker tool). The CLI commands take the numeric staging repository id via +`-r`/`--repository` (e.g. `3103` for `orgapachesling-3103`). Append `-x AUTO` to actually perform an +action (the default mode is `DRY_RUN`). + +#### Manual steps in the project being released (Maven) + +These are **not** part of this tool — run them in the module you are releasing, with the prerequisites +from the section above configured: + +1. Make sure the parent POM is up to date and the build is green (`mvn clean verify`). +2. Dry-run the release and check that only `<version>`/`<scm>` change: + + mvn release:prepare -DdryRun=true + mvn release:clean + +3. Deploy a snapshot and confirm `META-INF/LICENSE` and `META-INF/NOTICE` are in the jar: + + mvn deploy + +4. Prepare and perform the release (creates the tag, bumps to the next SNAPSHOT, signs and stages the + artifacts to Nexus): + + mvn release:prepare + mvn release:perform + + Note the staging repository id from the `release:perform` output (a line like + `…/orgapachesling-1087`). If it scrolls past, `release list` (below) shows open repos too. + +#### CLI steps (this tool) + +After `release:perform` has staged the artifacts, drive the rest with the CLI: + +0. **Find the staging repository id** if you did not capture it. `release list` shows every staging + repo with its `[open]`/`[closed]` state and description; a freshly staged one is `[open]`: + + docker run --env-file=./docker-env apache/sling-cli release list + +1. **Close** the staging repository. The description is derived automatically from the staged POM's + `<name>` + `<version>` (e.g. _Apache Sling Feature Model Launcher 1.3.6_) by browsing the + repository content, so it works even though an open repository is not yet in the Lucene index: + + docker run --env-file=./docker-env apache/sling-cli release close-staging --repository=$STAGING_REPOSITORY_ID --execution-mode=AUTO + +2. **Verify** the artifacts' signatures, hashes and CI status: + + docker run --env-file=./docker-env apache/sling-cli release verify --repository=$STAGING_REPOSITORY_ID + +3. **Generate the vote email**: + + docker run --env-file=./docker-env apache/sling-cli release prepare-email --repository=$STAGING_REPOSITORY_ID --execution-mode=AUTO + +4. After the 72h vote, **tally the votes** and generate the result email. PMC membership is detected + automatically from your ASF id: if you are a PMC member the email says you will copy the release to + the dist directory yourself; otherwise it asks a PMC member to perform the dist upload: + + docker run --env-file=./docker-env apache/sling-cli release tally-votes --repository=$STAGING_REPOSITORY_ID --execution-mode=AUTO + +5. **Finalize** the release (post successful vote). This runs, in order: promote to Maven Central, + create the next Jira version, release the current Jira version, and update the Apache Reporter: + + docker run --env-file=./docker-env apache/sling-cli release finalize --repository=$STAGING_REPOSITORY_ID --execution-mode=AUTO + + When the current user is detected as a PMC member, `finalize` additionally publishes to + `dist.apache.org` (requires `subversion`, which is bundled in the image). The previous version to + remove from `dist/release` is deduced automatically from the directory contents, so no extra flag + is needed: + + docker run --env-file=./docker-env apache/sling-cli release finalize --repository=$STAGING_REPOSITORY_ID --execution-mode=AUTO - docker run --env-file=./docker-env apache/sling-cli release list + PMC membership is determined from your ASF id (via Whimsy). A non-PMC committer's `finalize` skips + the dist upload and the `tally-votes` result email asks a PMC member to perform it. -Generating a release vote email +If the vote does not pass, **drop** the staging repository: - docker run --env-file=./docker-env apache/sling-cli release prepare-email --repository=$STAGING_REPOSITORY_ID - -Generating a release vote result email + docker run --env-file=./docker-env apache/sling-cli release drop --repository=$STAGING_REPOSITORY_ID --execution-mode=AUTO - docker run --env-file=./docker-env apache/sling-cli release tally-votes --repository=$STAGING_REPOSITORY_ID - -Generating the website update (only diff for now) +### Command reference - docker run --env-file=docker-env apache/sling-cli release update-local-site --repository=$STAGING_REPOSITORY_ID +| Command | Description | +|---------|-------------| +| `release list` | List closed staging repositories | +| `release close-staging -r <id>` | Close an open staging repo, setting the description from the staged POM | +| `release verify -r <id>` | Download and verify artifact signatures, hashes and CI status | +| `release prepare-email -r <id>` | Generate (and send) the `[VOTE]` email | +| `release tally-votes -r <id>` | Count votes and generate the `[RESULT]` email (PMC membership auto-detected; non-PMC email asks a PMC member to do the dist upload) | +| `release promote -r <id>` | Promote a closed staging repo to Maven Central | +| `release update-dist -r <id>` | Move artifacts to `dist.apache.org` (PMC only); previous version auto-deduced, override with `--previous-version <v>` | +| `release finalize -r <id>` | Promote + Jira + Reporter in one step; also updates `dist.apache.org` when you are a PMC member | +| `release drop -r <id>` | Drop a staging repository (failed vote / cleanup) | +| `release create-new-jira-version -r <id>` | Create the next Jira version and move unresolved issues | +| `release release-jira-version -r <id>` | Mark the Jira version as released and close fixed issues | +| `release update-reporter -r <id>` | Register the release with the Apache Reporter System | +| `release update-local-site -r <id>` | Generate the website update (diff only for now) | ## Assumptions diff --git a/skills/sling-release/SKILL.md b/skills/sling-release/SKILL.md new file mode 100644 index 0000000..5d0252d --- /dev/null +++ b/skills/sling-release/SKILL.md @@ -0,0 +1,180 @@ +--- +name: sling-release +description: Automates releasing an Apache Sling project end-to-end using Maven and the sling-cli Docker tool. Use when the user wants to release a Sling project, start a release, perform any step of the Apache Sling release process, or asks about releasing/voting/promoting a Sling module. +tools: Bash, Read, Edit, Write +--- + +This skill releases an Apache Sling module end-to-end by combining **Maven** steps (run in the +project being released) with the **`apache/sling-cli`** Docker tool (this repo) for the +Nexus/vote/finalize steps. + +## Overview + +Three phases: +1. **Stage** — Maven prepares the release and stages signed artifacts to Nexus. +2. **Vote** — close the staging repo, verify, send the `[VOTE]` email, wait 72h, tally. +3. **Finalize** — after a passing vote: promote to Maven Central, update JIRA + Reporter, and (for + PMC members) publish to `dist.apache.org`. + +## Prerequisites (verify before starting) + +1. **Docker** is available, and the `apache/sling-cli` image is built/pulled. +2. **`~/.apache-committer`** (or any env file) with ASF credentials, passed to the container: + ``` + ASF_USERNAME=your-apache-id + ASF_PASSWORD=your-apache-password + ``` +3. **GPG signing key** published to `keys.openpgp.org` and added to the Sling `KEYS` file + (<https://dist.apache.org/repos/dist/release/sling/KEYS>). +4. **`~/.m2/settings.xml`** configured for maven-gpg-plugin **3.x** (used by recent parent POMs). The + passphrase is read from a **server** whose id is `gpg.passphrase` (the `gpg.passphraseServerId` + default) and decrypted via `settings-security.xml` — **not** from a `<gpg.passphrase>` profile + property (that older approach is passed to gpg literally and fails): + ```xml + <servers> + <server><id>apache.snapshots.https</id><username>ID</username><password>{ENC}</password></server> + <server><id>apache.releases.https</id><username>ID</username><password>{ENC}</password></server> + <server><id>gpg.passphrase</id><passphrase>{ENC}</passphrase></server> + </servers> + <profiles><profile><id>apache-release</id><properties> + <gpg.pinentryMode>loopback</gpg.pinentryMode> + <gpg.keyname>YOUR_KEY_ID</gpg.keyname> + <smtp.host>smtp.gmail.com</smtp.host> + </properties></profile></profiles> + ``` +5. **`~/.m2/settings-security.xml`** with the `<master>` password used to decrypt the above. + +If any prerequisite is missing, stop and tell the user how to set it up before proceeding. + +## Running the CLI + +Use the Docker image directly. A `sling-cli` shell alias may exist but usually includes `-it` (TTY), +which **fails in non-interactive contexts** like Claude Code — so do not rely on it: + +```bash +docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli release <command> [options] +``` + +- Default mode is `DRY_RUN` (shows what would happen). Add `-x AUTO` to execute, `-x INTERACTIVE` to confirm each step. +- Always run a **DRY_RUN first** for any command that sends mail or mutates Nexus/dist, show the user the output, then re-run with `-x AUTO`. +- `~` does not expand inside quoted docker args — use `$HOME`. + +## Determining the Java version (Maven steps) + +Build with the Java version required by the project's Sling parent POM (recent parents require Java +17). If `mvnXX` wrappers are absent, set `JAVA_HOME` explicitly to an installed JDK +(`/usr/libexec/java_home -V` to list). + +## Phase 1: Stage (Maven, in the project being released) + +1. **Parent POM current?** Confirm it's up to date and `mvn clean verify` is green. +2. **Dry-run prepare**, then clean. Verify only `<version>`/`<scm>` differ in `pom.xml.tag`: + ```bash + mvn release:prepare -DdryRun=true && mvn release:clean + ``` +3. **Deploy snapshot**; confirm `META-INF/LICENSE` + `META-INF/NOTICE` are inside the jar: + ```bash + mvn deploy + ``` +4. **Prepare + perform** (creates the tag, bumps to next SNAPSHOT, signs + stages to Nexus): + ```bash + mvn release:prepare + mvn release:perform + ``` + Capture the staging repo id from a `…/orgapachesling-NNNN` line. If it scrolled off, find it with + `release list` (below). + +## Phase 2: Vote + +### 2.0 — Find the staging repo id (if not captured) +```bash +docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli release list +``` +Lists every staging repo with its `[open]`/`[closed]` state. A freshly staged repo is `[open]`; the +numeric suffix of `orgapachesling-NNNN` is the `REPO_ID`. + +### 2.1 — Close the staging repository +```bash +docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli release close-staging -r <REPO_ID> -x AUTO +``` +The description is auto-derived from the staged POM's `<name> <version>` (so it is correct even for an +open repo not yet in the Lucene index). After closing, the repo shows as `[closed]`. + +### 2.2 — Verify artifacts +```bash +docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli release verify -r <REPO_ID> +``` +Confirm PGP signatures, SHA-1, MD5, and CI status all pass before voting. + +### 2.3 — Send the vote email (dry-run first!) +```bash +docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli release prepare-email -r <REPO_ID> # review +docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli release prepare-email -r <REPO_ID> -x AUTO # send +``` + +### 2.4 — Wait 72h, then tally +At least 72 hours on [email protected] and ≥3 binding (PMC) +1 votes are required. +```bash +docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli release tally-votes -r <REPO_ID> # review +docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli release tally-votes -r <REPO_ID> -x AUTO # send [RESULT] +``` +**PMC membership is auto-detected from your ASF id** — no flag needed. If you are a PMC member the +result email says you will copy the release to the dist directory yourself; otherwise it asks a PMC +member to do the dist upload. + +### If the vote FAILS +```bash +docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli release drop -r <REPO_ID> -x AUTO +git push --delete origin <TAG_NAME> # e.g. org.apache.sling.feature.launcher-1.3.4 +git tag -d <TAG_NAME> +# restart Phase 1 with a new version +``` + +## Phase 3: Finalize (after a passing vote) + +```bash +docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli release finalize -r <REPO_ID> -x AUTO +``` +Runs in order: promote to Maven Central → (if you are a PMC member) update `dist.apache.org` → +create next JIRA version & move unresolved issues → mark JIRA version released → update Apache +Reporter. + +- **PMC detection is automatic.** A PMC member's run also publishes to `dist.apache.org`; the previous + version to remove is **deduced from the dist/release directory**, so no `--previous-version` is + needed (override with `--previous-version X.Y.Z` only if necessary). A non-PMC run skips the dist + step (a PMC member handles it, as requested in the tally-votes email). + +### Update the website (optional) +```bash +docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli release update-local-site -r <REPO_ID> +``` +Review the diff, then commit & push the sling-site changes separately. + +## Quick reference + +`<cli>` = `docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli` + +| Command | When | +|---------|------| +| `<cli> release list` | List staging repos with `[open]`/`[closed]` state + description | +| `<cli> release close-staging -r <ID> -x AUTO` | After `mvn release:perform` | +| `<cli> release verify -r <ID>` | Before sending the vote email | +| `<cli> release prepare-email -r <ID> -x AUTO` | Send the `[VOTE]` email | +| `<cli> release tally-votes -r <ID> -x AUTO` | After 72h with ≥3 binding votes (PMC auto-detected) | +| `<cli> release finalize -r <ID> -x AUTO` | All post-vote steps (dist included automatically for PMC) | +| `<cli> release promote -r <ID> -x AUTO` | Promote to Maven Central (individual step) | +| `<cli> release update-dist -r <ID> -x AUTO` | Update dist.apache.org (PMC only; prev version auto-deduced) | +| `<cli> release drop -r <ID> -x AUTO` | Drop staging (failed vote / cleanup) | +| `<cli> release create-new-jira-version -r <ID> -x AUTO` | Create next JIRA version | +| `<cli> release release-jira-version -r <ID> -x AUTO` | Mark JIRA version released | +| `<cli> release update-reporter -r <ID> -x AUTO` | Update Apache Reporter | +| `<cli> release update-local-site -r <ID>` | Update JBake website content | + +## Execution notes + +When this skill is invoked: +1. Confirm which phase the user is starting from (Stage / Vote / Finalize / Cancel) and the `REPO_ID` if past Phase 1. +2. Verify the prerequisites above before any mutating step. +3. For mail/Nexus/dist commands, run DRY_RUN first, show output, then `-x AUTO`. +4. On failure, diagnose (often a missing prereq or wrong `REPO_ID`) and suggest a fix before retrying. +5. Do not touch git state beyond what the documented steps require.
