upstream-release: fail fast when TOKEN_WEBSCRAPBOOK is empty #73
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: ci | |
| on: | |
| push: | |
| branches: | |
| - master | |
| tags: | |
| - '*.*.*' | |
| pull_request: | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| jobs: | |
| # Build a single-arch image and make sure the container actually boots and | |
| # serves before anything is published. | |
| test: | |
| name: Build & smoke test | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Build image (amd64) | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| load: true | |
| platforms: linux/amd64 | |
| tags: webscrapbook:test | |
| build-args: | | |
| wsb_ver=${{ github.ref_type == 'tag' && github.ref_name || '' }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| - name: Smoke test (service starts and serves) | |
| run: | | |
| set -e | |
| docker run -d --name wsb -p 8080:8080 webscrapbook:test | |
| echo "Waiting for the server to become ready..." | |
| for _ in $(seq 1 30); do | |
| if curl -fsS http://localhost:8080/ >/dev/null 2>&1; then | |
| ready=1 | |
| break | |
| fi | |
| sleep 2 | |
| done | |
| docker logs wsb || true | |
| if [ "${ready:-0}" != "1" ]; then | |
| echo "::error::server did not become ready" | |
| exit 1 | |
| fi | |
| ver="$(docker exec wsb wsb --version | awk '{print $2}')" | |
| echo "Installed webscrapbook $ver" | |
| if [ "${{ github.ref_type }}" = "tag" ] && [ "$ver" != "${{ github.ref_name }}" ]; then | |
| echo "::error::version mismatch: image has $ver but tag is ${{ github.ref_name }}" | |
| exit 1 | |
| fi | |
| docker rm -f wsb | |
| # On every push to master, publish a rolling amd64 image tagged "test" to | |
| # GHCR (Docker Hub only receives tagged releases). Runs only if the smoke | |
| # test passed. | |
| publish-test: | |
| name: Publish test image (GHCR) | |
| needs: test | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/master' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Login to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build and push (test) | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| push: true | |
| platforms: linux/amd64 | |
| tags: ghcr.io/${{ github.repository_owner }}/webscrapbook:test | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| # Publish the multi-arch image, only for version tags and only if the smoke | |
| # test above succeeded. | |
| publish: | |
| name: Build & push (multi-arch) | |
| needs: test | |
| if: github.ref_type == 'tag' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write # push to GitHub Container Registry (ghcr.io) | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@v3 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Login to Docker Hub | |
| uses: docker/login-action@v3 | |
| with: | |
| username: ${{ github.repository_owner }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| - name: Login to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build and push | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| push: true | |
| platforms: linux/386,linux/amd64,linux/arm64/v8,linux/arm/v7,linux/arm/v6 | |
| tags: | | |
| ${{ github.repository_owner }}/webscrapbook:${{ github.ref_name }} | |
| ${{ github.repository_owner }}/webscrapbook:latest | |
| ghcr.io/${{ github.repository_owner }}/webscrapbook:${{ github.ref_name }} | |
| ghcr.io/${{ github.repository_owner }}/webscrapbook:latest | |
| build-args: | | |
| wsb_ver=${{ github.ref_name }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| # Create a GitHub Release for the tag. The tag is the product (WebScrapBook) | |
| # version; the image keeps its own version in CHANGELOG.md. The notes always | |
| # include the upstream WebScrapBook changelog, plus this image's latest | |
| # changelog entry ONLY when CHANGELOG.md changed since the previous tag (i.e. | |
| # the image itself changed for this release). | |
| release: | |
| name: GitHub Release | |
| needs: publish | |
| if: github.ref_type == 'tag' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write # create releases | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 # need history and tags to diff against the previous tag | |
| - name: Build release notes | |
| env: | |
| TAG: ${{ github.ref_name }} | |
| OWNER: ${{ github.repository_owner }} | |
| run: | | |
| # Upstream WebScrapBook changelog (best-effort; a link is used if it | |
| # cannot be fetched or the version is not found there). | |
| curl -fsSL https://raw.githubusercontent.com/danny0838/PyWebScrapBook/master/CHANGELOG.md -o upstream.md || true | |
| # Did the image change for this release? -> CHANGELOG.md differs from | |
| # the previous tag (or this is the first tag). | |
| prev="$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || true)" | |
| if [ -z "$prev" ] || ! git diff --quiet "$prev" "$TAG" -- CHANGELOG.md; then | |
| image_changed=1 | |
| else | |
| image_changed=0 | |
| fi | |
| echo "Previous tag: ${prev:-<none>} | image_changed=$image_changed" | |
| python3 - "$TAG" "$OWNER" "$image_changed" > notes.md <<'PY' | |
| import re, sys, pathlib | |
| tag, owner, image_changed = sys.argv[1], sys.argv[2], sys.argv[3] == '1' | |
| def section_body(text, tag): | |
| """Bullet body of the '## [tag]' section, or None.""" | |
| parts = [s for s in re.split(r'(?m)^(?=## \[)', text) if s.startswith('## [')] | |
| sec = next((s for s in parts if re.match(rf'## \[{re.escape(tag)}\]', s)), None) | |
| return sec.split('\n', 1)[1].strip() if sec and '\n' in sec else None | |
| def top_section(text): | |
| """(version, body) of the most recent '## [x]' section, or (None, None).""" | |
| parts = [s for s in re.split(r'(?m)^(?=## \[)', text) if s.startswith('## [')] | |
| if not parts: | |
| return None, None | |
| m = re.match(r'## \[([^\]]+)\]', parts[0]) | |
| ver = m.group(1) if m else '?' | |
| body = parts[0].split('\n', 1)[1].strip() if '\n' in parts[0] else '' | |
| return ver, body | |
| def read(path): | |
| try: | |
| return pathlib.Path(path).read_text(encoding='utf-8') | |
| except OSError: | |
| return '' | |
| out = [ | |
| f"Docker image for **WebScrapBook {tag}** " | |
| f"(https://pypi.org/project/webscrapbook/{tag}/).", | |
| "", | |
| "## Pull", | |
| "```", | |
| f"docker pull {owner}/webscrapbook:{tag} # Docker Hub", | |
| f"docker pull ghcr.io/{owner}/webscrapbook:{tag} # GHCR", | |
| "```", | |
| "", | |
| f"## WebScrapBook {tag}", | |
| ] | |
| upstream = section_body(read('upstream.md'), tag) | |
| out.append(upstream if upstream else | |
| "See https://github.com/danny0838/PyWebScrapBook/blob/master/CHANGELOG.md") | |
| # Image's own changelog: only when the image changed for this release. | |
| if image_changed: | |
| ver, body = top_section(read('CHANGELOG.md')) | |
| if body: | |
| out += ["", f"## Image changes (v{ver})", body] | |
| sys.stdout.write("\n".join(out) + "\n") | |
| PY | |
| echo "---- notes.md ----"; cat notes.md | |
| - name: Create or update release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ github.ref_name }} | |
| run: | | |
| if gh release view "$TAG" >/dev/null 2>&1; then | |
| gh release edit "$TAG" --title "$TAG" --notes-file notes.md | |
| else | |
| gh release create "$TAG" --title "$TAG" --notes-file notes.md | |
| fi |