diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 739aa79..4c7e34d 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 { @@ -75,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 @@ -90,6 +100,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..5b878f2 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,123 @@ 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 | -| 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` always points at the stable variant; the -snapshot image is never tagged `latest`. +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 + +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: +- 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. + +### 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 GitHub Actions workflows live in `.github/workflows/`: @@ -161,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`; 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`: `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 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