From dc4a69ccaf7462a1b409a7769227d90d8ce5fe10 Mon Sep 17 00:00:00 2001 From: David Gamez Diaz <1192523+davidgamez@users.noreply.github.com> Date: Thu, 25 Jun 2026 16:48:59 -0400 Subject: [PATCH 1/2] fix package auto-link, better version management --- .github/workflows/docker.yml | 12 ++- .gitignore | 3 + .mvn/extensions.xml | 13 ++++ .mvn/maven-git-versioning-extension.xml | 32 ++++++++ Dockerfile | 8 ++ README.md | 97 ++++++++++++++++++++++--- pom.xml | 8 +- 7 files changed, 159 insertions(+), 14 deletions(-) create mode 100644 .mvn/extensions.xml create mode 100644 .mvn/maven-git-versioning-extension.xml diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 739aa79..162b95e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -46,6 +46,11 @@ jobs: # drift from the actual build. Image name is lower-cased for GHCR. - name: Compute image tags id: tags + env: + # `latest` (and release semantics) only apply to actual releases: + # a pushed `v*` tag or a published release. Plain merges to main build + # a 0.0.0-SNAPSHOT dev image and must never move `latest`. + IS_RELEASE: ${{ startsWith(github.ref, 'refs/tags/v') || github.event_name == 'release' }} run: | set -euo pipefail PROFILE_ARG="" @@ -55,12 +60,16 @@ jobs: API_VERSION=$(mvn -q -N $PROFILE_ARG help:evaluate -Dexpression=project.version -DforceStdout) VALIDATOR_VERSION=$(mvn -q -N $PROFILE_ARG help:evaluate -Dexpression=gtfs-validator.version -DforceStdout) IMAGE=$(echo "${REGISTRY}/${IMAGE_NAME}" | tr '[:upper:]' '[:lower:]') + # Fork-aware OCI source URL (lower-cased) so the package links to the + # repo it was built from, not a hardcoded upstream. + REPO_LC=$(echo "${GITHUB_REPOSITORY}" | tr '[:upper:]' '[:lower:]') + echo "image_source=https://github.com/${REPO_LC}.git" >> "$GITHUB_OUTPUT" # Self-documenting tag: -validator. # The `validator` infix scopes the trailing version to the validator # core, not the API (the two versions evolve independently). DUAL_TAG="${IMAGE}:${API_VERSION}-validator${VALIDATOR_VERSION}" TAGS="${DUAL_TAG}" - if [ "${{ matrix.latest }}" = "true" ]; then + if [ "${{ matrix.latest }}" = "true" ] && [ "$IS_RELEASE" = "true" ]; then TAGS="${TAGS}"$'\n'"${IMAGE}:latest" fi { @@ -90,6 +99,7 @@ jobs: context: . build-args: | MAVEN_PROFILES=${{ matrix.maven_profiles }} + IMAGE_SOURCE=${{ steps.tags.outputs.image_source }} platforms: linux/amd64,linux/arm64 push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.tags.outputs.tags }} diff --git a/.gitignore b/.gitignore index bf91998..008a86d 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,9 @@ replay_pid* target/ HELP.md +# maven-git-versioning-extension generated pom (build artifact) +.git-versioned-pom.xml + # IDE .idea/ *.iml diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml new file mode 100644 index 0000000..65e86d5 --- /dev/null +++ b/.mvn/extensions.xml @@ -0,0 +1,13 @@ + + + + + me.qoomon + maven-git-versioning-extension + 9.12.1 + + diff --git a/.mvn/maven-git-versioning-extension.xml b/.mvn/maven-git-versioning-extension.xml new file mode 100644 index 0000000..f4bed3b --- /dev/null +++ b/.mvn/maven-git-versioning-extension.xml @@ -0,0 +1,32 @@ + + + + + + + + [0-9]+\.[0-9]+\.[0-9]+)]]> + ${ref.version} + + + + + .+ + 0.0.0-SNAPSHOT + + + + + + 0.0.0-SNAPSHOT + + diff --git a/Dockerfile b/Dockerfile index 836d75e..3eefa65 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,6 +21,14 @@ RUN mvn -B -q ${MAVEN_PROFILES:+-P}${MAVEN_PROFILES} clean package -DskipTests # --- Runtime stage ----------------------------------------------------------- FROM eclipse-temurin:17-jre + +# Link the published GHCR package to its source repository so it appears in the +# repo's Packages section (and inherits the repo for permissions/visibility). +# Overridable so forks publish under their own repo: CI passes the lower-cased +# ${{ github.repository }}. The default keeps local builds labelled sensibly. +ARG IMAGE_SOURCE="https://github.com/mobilitydata/gtfs-validator-api.git" +LABEL org.opencontainers.image.source="${IMAGE_SOURCE}" + RUN groupadd -r spring && useradd -r -g spring spring WORKDIR /app diff --git a/README.md b/README.md index 67b692c..41c2c1c 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,17 @@ The API project version is **independent** of the validator core version: | Version | Where | Current | |---------|-------|---------| -| API project version | `pom.xml` | `1.0.0` | +| API project version | git tag `vX.Y.Z` (derived at build time) | see releases | | Validator core (dependency) | `gtfs-validator.version` property | `8.0.1` | | OpenAPI spec version | `docs/GTFSValidatorAPI.yaml` `info.version` | `2.0.0` | +The API project version is **derived from git** by the +[`maven-git-versioning-extension`](https://github.com/qoomon/maven-git-versioning-extension) +(configured in `.mvn/`): a release tag `vX.Y.Z` yields version `X.Y.Z`, while any +other build (commits on `main`, PRs, local checkouts) yields `0.0.0-SNAPSHOT`. The +`` in `pom.xml` is only a placeholder and is never edited for a release — +see [Releasing](#releasing). + The validator core version is reported at runtime by `GET /v2/metadata`. ## Endpoints @@ -132,28 +139,96 @@ docker build --build-arg MAVEN_PROFILES=snapshot -t gtfs-validator-api:snapshot ### Pre-built image CI publishes multi-arch images (`linux/amd64`, `linux/arm64`) to the GitHub -Container Registry on every push to the default branch and on version tags. Two -variants are published: +Container Registry. The image tag encodes both the API version (derived from the +git tag) and the validator core version. Two variants are published per build: -| Variant | Tags | Validator core | -|---------|------|----------------| -| Stable | `latest`, `1.0.0-validator8.0.1` | stable release | +| Variant | Example tag | Validator core | +|---------|-------------|----------------| +| Stable | `1.0.0-validator8.0.1` (+ `latest` on releases) | stable release | | Snapshot | `1.0.0-validator8.0.2-SNAPSHOT` | pre-release SNAPSHOT | The tag format is `-validator`: the `validator` infix scopes the trailing version to the validator **core**, not the API (the two -versions evolve independently). `latest` always points at the stable variant; the -snapshot image is never tagged `latest`. +versions evolve independently). `latest` is only moved by an actual release (a +`vX.Y.Z` tag); merges to `main` publish `0.0.0-SNAPSHOT-validator…` dev images and +never touch `latest`. The snapshot variant is never tagged `latest`. See +[Releasing](#releasing) for how versions are produced. + +#### Using the published images + +The images live at `ghcr.io/mobilitydata/gtfs-validator-api`. They are public, so +no login is required to pull. Browse all available tags on the +[package page](https://github.com/MobilityData/gtfs-validator-api/pkgs/container/gtfs-validator-api). + +**Stable** — recommended for normal use; built against a released validator core: ```bash -# Stable +# `latest` always points at the most recent release docker pull ghcr.io/mobilitydata/gtfs-validator-api:latest docker run --rm -p 8080:8080 ghcr.io/mobilitydata/gtfs-validator-api:latest -# Snapshot (built against the validator core SNAPSHOT) +# …or pin an exact, immutable version +docker run --rm -p 8080:8080 \ + ghcr.io/mobilitydata/gtfs-validator-api:1.0.0-validator8.0.1 +``` + +**Snapshot** — for trying the latest validator core before it is released; built +against a `-SNAPSHOT` of the validator. The tag is re-published as the upstream +snapshot moves, so re-pull to get the newest build: + +```bash docker pull ghcr.io/mobilitydata/gtfs-validator-api:1.0.0-validator8.0.2-SNAPSHOT +docker run --rm -p 8080:8080 \ + ghcr.io/mobilitydata/gtfs-validator-api:1.0.0-validator8.0.2-SNAPSHOT ``` +Once a container is running, the API is available on port `8080` regardless of +which image you chose: + +```bash +# Confirm which validator core the running image uses +curl http://localhost:8080/v2/metadata + +# Validate a feed +curl -X POST http://localhost:8080/v2/validate-upload \ + -F "file=@feed.zip" -F "countryCode=CA" -H "Accept: application/json" +``` + +Pass JVM options via `JAVA_OPTS`, e.g. `-e JAVA_OPTS="-Xmx4g"`, and activate the +structured-logging profile with `-e SPRING_PROFILES_ACTIVE=json`. If you have +pinned the package to **private** visibility, authenticate first: + +```bash +echo "$GITHUB_TOKEN" | docker login ghcr.io -u --password-stdin +``` + +## Releasing + +The API version is **derived from the git tag** at build time by the +[`maven-git-versioning-extension`](https://github.com/qoomon/maven-git-versioning-extension) +(see `.mvn/`); `pom.xml` keeps the placeholder `0.0.0-SNAPSHOT` and is **never** +edited by hand for a release. To cut a release and publish images: + +1. Make sure `main` is green and the desired changes are merged. +2. Create and push a semver tag prefixed with `v`: + + ```bash + git tag v1.0.0 + git push origin v1.0.0 + ``` + + (Or publish a GitHub Release with that tag — either triggers the same flow.) +3. The `docker.yml` workflow then builds both variants and **publishes**: + - stable: `ghcr.io//gtfs-validator-api:1.0.0-validator` **and** `:latest` + - snapshot: `ghcr.io//gtfs-validator-api:1.0.0-validator` + +Notes: +- A plain push/merge to `main` (no tag) builds version `0.0.0-SNAPSHOT` and + publishes `0.0.0-SNAPSHOT-validator…` dev images without moving `latest`. +- Pull requests build both variants to validate the Dockerfile but publish nothing. +- The tag must match `v` + semver (e.g. `v1.2.3`); other tags don't set the version + or trigger a release publish. + ## Continuous integration GitHub Actions workflows live in `.github/workflows/`: @@ -161,7 +236,7 @@ GitHub Actions workflows live in `.github/workflows/`: | Workflow | Trigger | Purpose | |----------|---------|---------| | `build.yml` | push / PR to `main`/`master`, manual | `mvn clean verify` — OpenAPI generation, compile, integration tests and the Spotless code-style check; uploads the built jar. | -| `docker.yml` | push / PR / tag `v*` / release, manual | Builds two image variants (stable and validator-SNAPSHOT) via a matrix, multi-arch. On non-PR events it pushes to `ghcr.io//gtfs-validator-api`; stable gets `latest` + `-validator`, snapshot gets `-validator` only. PRs build both but push neither. | +| `docker.yml` | push / PR / tag `v*` / release, manual | Builds two image variants (stable and validator-SNAPSHOT) via a matrix, multi-arch. On non-PR events it pushes to `ghcr.io//gtfs-validator-api` tagged `-validator`; `latest` is applied only on a `v*` tag/release (stable variant). PRs build both but push neither. See [Releasing](#releasing). | ## Configuration diff --git a/pom.xml b/pom.xml index 0eb08cc..f21590a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,8 +5,12 @@ org.mobilitydata.gtfs-validator gtfs-validator-api - - 1.0.0 + + 0.0.0-SNAPSHOT jar gtfs-validator-api From ce15a2faa134f0365654917c7bca932a14e86319 Mon Sep 17 00:00:00 2001 From: David Gamez Diaz <1192523+davidgamez@users.noreply.github.com> Date: Thu, 25 Jun 2026 17:42:35 -0400 Subject: [PATCH 2/2] enhance documentation and release --- .github/workflows/docker.yml | 5 ++-- README.md | 53 +++++++++++++++++++++++++++--------- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 162b95e..4c7e34d 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -84,8 +84,9 @@ jobs: uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - # Only authenticate and push for events that are not pull requests. - # Pull requests still build the image to validate the Dockerfile. + # Authenticate and push on all non-PR events: merges to main publish API + # snapshot images (version 0.0.0-SNAPSHOT), and `v*` tags / releases publish + # versioned images. Pull requests build only, to validate the Dockerfile. - name: Log in to the Container registry if: github.event_name != 'pull_request' uses: docker/login-action@v4 diff --git a/README.md b/README.md index 41c2c1c..5b878f2 100644 --- a/README.md +++ b/README.md @@ -142,17 +142,18 @@ CI publishes multi-arch images (`linux/amd64`, `linux/arm64`) to the GitHub Container Registry. The image tag encodes both the API version (derived from the git tag) and the validator core version. Two variants are published per build: -| Variant | Example tag | Validator core | -|---------|-------------|----------------| -| Stable | `1.0.0-validator8.0.1` (+ `latest` on releases) | stable release | -| Snapshot | `1.0.0-validator8.0.2-SNAPSHOT` | pre-release SNAPSHOT | +| Variant | Example release tag | Example main-merge tag | Validator core | +|---------|--------------------|------------------------|----------------| +| Stable (`stable-core`) | `1.0.0-validator8.0.1` (+ `latest`) | `0.0.0-SNAPSHOT-validator8.0.1` | stable release | +| Snapshot (`snapshot-core`) | `1.0.0-validator8.0.2-SNAPSHOT` | `0.0.0-SNAPSHOT-validator8.0.2-SNAPSHOT` | pre-release SNAPSHOT | The tag format is `-validator`: the `validator` infix scopes the trailing version to the validator **core**, not the API (the two -versions evolve independently). `latest` is only moved by an actual release (a -`vX.Y.Z` tag); merges to `main` publish `0.0.0-SNAPSHOT-validator…` dev images and -never touch `latest`. The snapshot variant is never tagged `latest`. See -[Releasing](#releasing) for how versions are produced. +versions evolve independently). Both variants are published on every merge to +`main` (as API snapshots, version `0.0.0-SNAPSHOT`) and on every release (versioned +`X.Y.Z`); the stable variant additionally gets `latest` on releases only. The +snapshot variant is never tagged `latest`. See [Releasing](#releasing) for how +versions are produced. #### Using the published images @@ -223,11 +224,37 @@ edited by hand for a release. To cut a release and publish images: - snapshot: `ghcr.io//gtfs-validator-api:1.0.0-validator` Notes: -- A plain push/merge to `main` (no tag) builds version `0.0.0-SNAPSHOT` and - publishes `0.0.0-SNAPSHOT-validator…` dev images without moving `latest`. +- Every merge to `main` publishes both variants as **API snapshots** (version + `0.0.0-SNAPSHOT`, e.g. `0.0.0-SNAPSHOT-validator8.0.1`), without moving `latest`. +- A release (`v*` tag / GitHub Release) publishes both variants **versioned** + (`X.Y.Z-…`); the stable variant also updates `latest`. - Pull requests build both variants to validate the Dockerfile but publish nothing. -- The tag must match `v` + semver (e.g. `v1.2.3`); other tags don't set the version - or trigger a release publish. +- The tag must match `v` + semver (e.g. `v1.2.3`); other tags don't set the version. + +### What gets published when + +Image tags follow `-validator`. The **API version** +comes from git (a release tag, or `0.0.0-SNAPSHOT` otherwise) and the **validator +core version** comes from the build variant. These are independent: a `main` build +is an *API snapshot*, which is not the same thing as the validator-core snapshot. + +**On merge to `main`** the API version resolves to `0.0.0-SNAPSHOT`, so both variants +publish as API snapshots (and `latest` is not moved): + +| Variant | Tag | +|---------|-----| +| stable-core | `0.0.0-SNAPSHOT-validator8.0.1` | +| snapshot-core | `0.0.0-SNAPSHOT-validator8.0.2-SNAPSHOT` | + +**On release** (`vX.Y.Z` tag / GitHub Release) the API version resolves to `X.Y.Z`: + +| Variant | Tag | +|---------|-----| +| stable-core | `X.Y.Z-validator8.0.1` **+ `latest`** | +| snapshot-core | `X.Y.Z-validator8.0.2-SNAPSHOT` | + +**On pull requests** both variants build (to validate the Dockerfile) but nothing is +published. ## Continuous integration @@ -236,7 +263,7 @@ GitHub Actions workflows live in `.github/workflows/`: | Workflow | Trigger | Purpose | |----------|---------|---------| | `build.yml` | push / PR to `main`/`master`, manual | `mvn clean verify` — OpenAPI generation, compile, integration tests and the Spotless code-style check; uploads the built jar. | -| `docker.yml` | push / PR / tag `v*` / release, manual | Builds two image variants (stable and validator-SNAPSHOT) via a matrix, multi-arch. On non-PR events it pushes to `ghcr.io//gtfs-validator-api` tagged `-validator`; `latest` is applied only on a `v*` tag/release (stable variant). PRs build both but push neither. See [Releasing](#releasing). | +| `docker.yml` | push / PR / tag `v*` / release, manual | Builds two image variants (stable and validator-SNAPSHOT) via a matrix, multi-arch. On non-PR events it pushes to `ghcr.io//gtfs-validator-api` tagged `-validator`: `main` merges publish API snapshots (`0.0.0-SNAPSHOT-…`), releases publish versioned images with `latest` on the stable variant. PRs build both but push neither. See [Releasing](#releasing). | ## Configuration