Skip to content

upstream-release: fail fast when TOKEN_WEBSCRAPBOOK is empty #73

upstream-release: fail fast when TOKEN_WEBSCRAPBOOK is empty

upstream-release: fail fast when TOKEN_WEBSCRAPBOOK is empty #73

Workflow file for this run

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