From b70859e086acb1af370e1aa0f0f7e03500628c0b Mon Sep 17 00:00:00 2001 From: Sagent Date: Mon, 8 Jun 2026 21:45:25 +0000 Subject: [PATCH] feat: migrate to 4-job CI pipeline with SHA-pinned actions --- .gitea/workflows/docker-build.yaml | 84 ++++++++++++++++++++-------- tests/test.sh | 89 ++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 24 deletions(-) create mode 100644 tests/test.sh diff --git a/.gitea/workflows/docker-build.yaml b/.gitea/workflows/docker-build.yaml index 95951c9..c2e8bf7 100644 --- a/.gitea/workflows/docker-build.yaml +++ b/.gitea/workflows/docker-build.yaml @@ -9,38 +9,74 @@ on: - cron: '0 0 * * *' jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + + - name: Hadolint + uses: hadolint/hadolint-action@54c9adbab1582c2ef04b2016b760714a4bfde3cf # v3.1.0 + with: + dockerfile: Dockerfile + build: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + - name: Build image + run: docker build -t ci-image:${{ github.sha }} . + + - name: Save image + run: docker save -o image.tar ci-image:${{ github.sha }} + + - name: Upload artifact + uses: https://github.com/ChristopherHX/gitea-upload-artifact@62ac910c5d3dfa85c7cb2df15afe2e342b2407c2 # main + with: + name: docker-image + path: image.tar + retention-days: 1 + + test: + runs-on: ubuntu-latest + needs: [lint, build] + steps: + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + + - name: Download artifact + uses: https://github.com/ChristopherHX/gitea-download-artifact@75635f32b4c1c41c4b3d64e8f85210112ed4c9c7 # main + with: + name: docker-image + + - name: Load image + run: docker load < image.tar + + - name: Run tests + run: bash tests/test.sh ci-image:${{ github.sha }} + + push: + runs-on: ubuntu-latest + needs: test + if: github.event_name != 'pull_request' + steps: + - name: Download artifact + uses: https://github.com/ChristopherHX/gitea-download-artifact@75635f32b4c1c41c4b3d64e8f85210112ed4c9c7 # main + with: + name: docker-image + + - name: Load image + run: docker load < image.tar - name: Login to Docker Hub - if: github.event_name != 'pull_request' - uses: docker/login-action@v4 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Docker metadata - id: meta - uses: docker/metadata-action@v6 - with: - images: jcabillot/ttrss - tags: | - #type=ref,event=branch - #type=ref,event=pr - #type=sha - type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }} - - - name: 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 }} - pull: true + - name: Tag and push + run: | + docker tag ci-image:${{ github.sha }} jcabillot/ttrss:latest + docker push jcabillot/ttrss:latest diff --git a/tests/test.sh b/tests/test.sh new file mode 100644 index 0000000..411de5c --- /dev/null +++ b/tests/test.sh @@ -0,0 +1,89 @@ +#!/bin/bash +set -euo pipefail + +IMAGE="$1" +CONTAINER_NAME="test-$(echo "$IMAGE" | tr ':/' '-')-$$" +DB_CONTAINER="ttrss-db-$$" +TMPDIR="$(mktemp -d)" +trap 'docker rm -f "$CONTAINER_NAME" 2>/dev/null; docker rm -f "$DB_CONTAINER" 2>/dev/null; rm -rf "$TMPDIR"' EXIT + +DOCKER_GW=$(docker network inspect bridge --format '{{range .IPAM.Config}}{{.Gateway}}{{end}}') +BASE_URL="http://${DOCKER_GW}:8080" +FAILED=0 +PASSED=0 + +assert_eq() { + local desc="$1" expected="$2" actual="$3" + if [ "$expected" = "$actual" ]; then + echo "PASS: $desc" + PASSED=$((PASSED + 1)) + else + echo "FAIL: $desc (expected '$expected', got '$actual')" + FAILED=$((FAILED + 1)) + fi +} + +assert_contains() { + local desc="$1" pattern="$2" file="$3" + if grep -qEi "$pattern" "$file"; then + echo "PASS: $desc" + PASSED=$((PASSED + 1)) + else + echo "FAIL: $desc" + FAILED=$((FAILED + 1)) + fi +} + +echo "Starting PostgreSQL..." +docker pull postgres:16-alpine > /dev/null 2>&1 +docker run -d --name "$DB_CONTAINER" \ + -e POSTGRES_DB=ttrss -e POSTGRES_USER=ttrss -e POSTGRES_PASSWORD=ttrss \ + postgres:16-alpine + +echo "Waiting for PostgreSQL to be ready..." +for i in $(seq 1 30); do + if docker exec "$DB_CONTAINER" pg_isready -U ttrss -d ttrss 2>/dev/null; then + echo "PostgreSQL ready (attempt $i)" + break + fi + sleep 2 +done + +echo "Starting TTRSS..." +docker run -d --name "$CONTAINER_NAME" --link "$DB_CONTAINER:db" -p 8080:8080 "$IMAGE" + +echo "Waiting for TTRSS to be ready..." +READY=false +for i in $(seq 1 60); do + if curl -sf -o /dev/null "$BASE_URL/"; then + echo "TTRSS ready (attempt $i)" + READY=true + break + fi + sleep 3 +done + +if [ "$READY" = false ]; then + echo "Container did not become ready within 3 minutes" + echo "=== Container logs ===" + docker logs "$CONTAINER_NAME" 2>&1 || true + exit 1 +fi + +# Test 1: HTTP status +STATUS=$(curl -sf -o "$TMPDIR/body" -w '%{http_code}' "$BASE_URL/") +echo "HTTP status: $STATUS" +assert_eq "HTTP 200" "200" "$STATUS" + +# Test 2: Content-Type contains text/html +curl -sf -o /dev/null -D "$TMPDIR/headers" "$BASE_URL/" +assert_contains "Content-Type text/html" "text/html" "$TMPDIR/headers" + +# Test 3: Body contains HTML +assert_contains "Body is HTML" "