Add matchable metadata to public list RSS feed items #1037
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Docker Image | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| bootstrap_ghcr: | |
| description: "Use GHCR_TOKEN once to create the renamed floppy package" | |
| required: false | |
| default: false | |
| type: boolean | |
| push: | |
| branches: | |
| - "release" | |
| - "latest" | |
| tags: | |
| - "v*" | |
| pull_request: | |
| branches: | |
| - "latest" | |
| # A push to latest must not be overtaken by an older build that started later | |
| # because runners were queued. Keep one publish per ref in flight and let the | |
| # newest pending run replace intermediate commits without cancelling the run | |
| # currently publishing. | |
| concurrency: | |
| group: docker-image-${{ github.ref }} | |
| cancel-in-progress: false | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: dannyvfilms/floppy | |
| LEGACY_IMAGE_NAME: dannyvfilms/yamtrack | |
| jobs: | |
| test: | |
| name: Smoke built image | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| env: | |
| SMOKE_APP: floppy-smoke-app-${{ github.run_id }}-${{ github.run_attempt }} | |
| SMOKE_IMAGE: floppy:ci-smoke | |
| SMOKE_NETWORK: floppy-smoke-${{ github.run_id }}-${{ github.run_attempt }} | |
| SMOKE_REDIS: floppy-smoke-redis-${{ github.run_id }}-${{ github.run_attempt }} | |
| SMOKE_SHA: ${{ github.sha }} | |
| SMOKE_VOLUME: floppy-smoke-db-${{ github.run_id }}-${{ github.run_attempt }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| - name: Setup Docker buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Build test image | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| load: true | |
| tags: ${{ env.SMOKE_IMAGE }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| provenance: false | |
| platforms: linux/amd64 | |
| build-args: | | |
| VERSION=ci-smoke | |
| COMMIT_SHA=${{ env.SMOKE_SHA }} | |
| - name: Run and restart test image | |
| run: | | |
| set -euo pipefail | |
| wait_for_redis() { | |
| for _ in $(seq 1 30); do | |
| if timeout 10 docker exec "$SMOKE_REDIS" redis-cli ping | grep -qx PONG; then | |
| return 0 | |
| fi | |
| sleep 2 | |
| done | |
| echo "Redis did not become ready" >&2 | |
| return 1 | |
| } | |
| wait_for_health() { | |
| for _ in $(seq 1 60); do | |
| state=$(docker inspect --format '{{.State.Status}}' "$SMOKE_APP") | |
| health=$(docker inspect --format '{{.State.Health.Status}}' "$SMOKE_APP") | |
| if [ "$health" = healthy ]; then | |
| return 0 | |
| fi | |
| if [ "$state" = exited ] || [ "$state" = dead ]; then | |
| echo "Floppy exited before becoming healthy" >&2 | |
| return 1 | |
| fi | |
| sleep 5 | |
| done | |
| echo "Floppy did not become healthy" >&2 | |
| return 1 | |
| } | |
| assert_runtime() { | |
| curl -fsS --max-time 10 http://127.0.0.1:8000/health/ >/dev/null | |
| login_html=$(curl -fsS --max-time 10 http://127.0.0.1:8000/accounts/login/) | |
| grep -Fq 'alt="Floppy"' <<<"$login_html" | |
| curl -fsS --max-time 10 http://127.0.0.1:8000/static/css/main.css >/dev/null | |
| curl -fsS --max-time 10 http://127.0.0.1:8000/api/v1/info/ | | |
| python3 -c 'import json, sys; assert json.load(sys.stdin)["version"] == "ci-smoke"' | |
| test "$(docker exec "$SMOKE_APP" printenv VERSION)" = ci-smoke | |
| test "$(docker exec "$SMOKE_APP" printenv COMMIT_SHA)" = "$SMOKE_SHA" | |
| timeout 30 docker exec "$SMOKE_APP" test -s /floppy/db/db.sqlite3 | |
| timeout 120 docker exec "$SMOKE_APP" python manage.py migrate --check --noinput | |
| timeout 30 docker exec "$SMOKE_APP" python manage.py shell -c \ | |
| 'from django.conf import settings; import os, redis; assert settings.VERSION == os.environ["VERSION"] == "ci-smoke"; assert settings.COMMIT_SHA == os.environ["COMMIT_SHA"]; assert redis.Redis.from_url(settings.REDIS_URL).ping()' | |
| timeout 30 docker exec "$SMOKE_APP" python -c \ | |
| 'import importlib.metadata; import floppy_mcp; assert importlib.metadata.version("floppy-mcp")' | |
| timeout 10 docker exec "$SMOKE_APP" sh -c \ | |
| 'timeout 5 floppy-mcp </dev/null; status=$?; [ "$status" -eq 0 ] || [ "$status" -eq 124 ]' | |
| } | |
| start_app() { | |
| docker run --detach \ | |
| --name "$SMOKE_APP" \ | |
| --network "$SMOKE_NETWORK" \ | |
| --publish 127.0.0.1:8000:8000 \ | |
| --env TZ=UTC \ | |
| --env SECRET=ci-smoke-only-not-a-production-secret \ | |
| --env REDIS_URL="redis://$SMOKE_REDIS:6379" \ | |
| --env ADMIN_ENABLED=False \ | |
| --env DEMO_ACCOUNT_ENABLED=False \ | |
| --env DEBUG=False \ | |
| --env FLOPPY_RESOURCE_TIER=minimal \ | |
| --volume "$SMOKE_VOLUME:/floppy/db" \ | |
| "$SMOKE_IMAGE" | |
| } | |
| image_env=$(docker image inspect "$SMOKE_IMAGE" --format '{{range .Config.Env}}{{println .}}{{end}}') | |
| grep -Fxq VERSION=ci-smoke <<<"$image_env" | |
| grep -Fxq "COMMIT_SHA=$SMOKE_SHA" <<<"$image_env" | |
| docker network create "$SMOKE_NETWORK" | |
| docker volume create "$SMOKE_VOLUME" | |
| docker run --detach --name "$SMOKE_REDIS" --network "$SMOKE_NETWORK" redis:8-alpine | |
| wait_for_redis | |
| start_app | |
| wait_for_health | |
| assert_runtime | |
| docker logs "$SMOKE_APP" >"$RUNNER_TEMP/floppy-smoke-first.log" 2>&1 | |
| docker rm --force "$SMOKE_APP" | |
| start_app | |
| wait_for_health | |
| assert_runtime | |
| - name: Print test container logs | |
| if: failure() | |
| run: | | |
| if [ -f "$RUNNER_TEMP/floppy-smoke-first.log" ]; then | |
| echo "First Floppy boot logs:" | |
| tail -n 500 "$RUNNER_TEMP/floppy-smoke-first.log" | |
| fi | |
| for container in "$SMOKE_APP" "$SMOKE_REDIS"; do | |
| if docker inspect "$container" >/dev/null 2>&1; then | |
| echo "$container logs:" | |
| docker logs --tail 500 "$container" 2>&1 || true | |
| fi | |
| done | |
| - name: Clean up test containers | |
| if: always() | |
| run: | | |
| docker rm --force "$SMOKE_APP" "$SMOKE_REDIS" 2>/dev/null || true | |
| docker network rm "$SMOKE_NETWORK" 2>/dev/null || true | |
| docker volume rm "$SMOKE_VOLUME" 2>/dev/null || true | |
| build: | |
| needs: test | |
| runs-on: ubuntu-latest | |
| if: github.actor != 'dependabot[bot]' | |
| permissions: | |
| contents: read | |
| packages: write | |
| id-token: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 # This ensures all tags are fetched for git describe | |
| - name: Get version from git | |
| id: get-version | |
| run: | | |
| VERSION=$(git describe --tags 2>/dev/null || echo "dev") | |
| echo "VERSION=$VERSION" >> $GITHUB_OUTPUT | |
| echo "Using version: $VERSION" | |
| - name: Setup Docker buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@v4 | |
| - name: Extract Docker metadata tags | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| # Dual-publish during the rename grace period: the legacy path keeps | |
| # receiving identical tags so pre-rename deployments still get | |
| # updates. Drop the second line once traffic to it has died off. | |
| images: | | |
| ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | |
| ${{ env.REGISTRY }}/${{ env.LEGACY_IMAGE_NAME }} | |
| tags: | | |
| type=ref,event=pr | |
| type=semver,pattern={{version}} | |
| type=semver,pattern={{major}}.{{minor}} | |
| type=semver,pattern={{major}} | |
| type=raw,value=latest,enable=${{ github.ref == 'refs/heads/latest' }} | |
| type=raw,value=release,enable=${{ github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/tags/v') }} | |
| # Pull requests build both architectures to prove the image still | |
| # cross-compiles, but they must never write to the registry. A pr-N tag | |
| # has no consumer and lands in the same public package users pull from, | |
| # where nothing distinguishes it from a release. Publishing happens on | |
| # push to latest/release and on v* tags only. | |
| - name: Log into registry ${{ env.REGISTRY }} | |
| if: github.event_name != 'pull_request' | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| # The one-time bootstrap is needed because floppy was created by a | |
| # PAT-based push, while the existing yamtrack package is already | |
| # associated with this repository. Normal builds stay on GITHUB_TOKEN. | |
| username: ${{ inputs.bootstrap_ghcr == true && 'dannyvfilms' || github.actor }} | |
| password: ${{ inputs.bootstrap_ghcr == true && secrets.GHCR_TOKEN || secrets.GITHUB_TOKEN }} | |
| - name: Build and push Docker image | |
| id: build-and-push | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| push: ${{ github.event_name != 'pull_request' }} | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| provenance: false | |
| platforms: linux/amd64,linux/arm64 | |
| build-args: | | |
| VERSION=${{ steps.get-version.outputs.VERSION }} | |
| COMMIT_SHA=${{ github.sha }} |