Skip to content

fix IGDB game backdrop to use artwork_type=2 for Key Art #1595

fix IGDB game backdrop to use artwork_type=2 for Key Art

fix IGDB game backdrop to use artwork_type=2 for Key Art #1595

Workflow file for this run

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 ]'
# The startup-scan observability path (heartbeat, status sidecar,
# timeout marking) must actually be present in the image, not just
# in source.
timeout 10 docker exec "$SMOKE_APP" python -c \
'from config.sqlite_integrity import mark_startup_status_timeout, print_startup_heartbeat, read_startup_status, write_startup_status'
# The build-info file must exist and match this build, and a
# shadowing runtime environment override (an orchestrator's
# persisted .env, for example) must not be able to defeat it --
# this is the failure a real deployment hit.
build_info=$(docker exec "$SMOKE_APP" cat /etc/floppy-build-info)
grep -Fxq "VERSION=ci-smoke" <<<"$build_info"
grep -Fxq "COMMIT_SHA=$SMOKE_SHA" <<<"$build_info"
restored=$(docker exec -e COMMIT_SHA=stale-override -e VERSION=stale-override "$SMOKE_APP" sh -c \
'. /etc/floppy-build-info; export VERSION COMMIT_SHA; echo "$VERSION $COMMIT_SHA"')
test "$restored" = "ci-smoke $SMOKE_SHA"
# HOST_CONTAINERNAME is unset in this smoke run, so the identity
# line must fall back to "floppy" rather than erroring. Captured
# into a variable first: piping a long-lived command straight
# into "grep -q" trips pipefail on the writer's SIGPIPE once grep
# finds its match and closes the pipe early.
smoke_logs=$(docker logs "$SMOKE_APP" 2>&1)
grep -Fq "[entrypoint] Floppy runtime: version=ci-smoke commit=$SMOKE_SHA" <<<"$smoke_logs"
grep -Fq "container_name=floppy" <<<"$smoke_logs"
}
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 }}