mirror of
https://github.com/apache/superset.git
synced 2026-07-08 15:55:33 +00:00
178 lines
7.4 KiB
YAML
178 lines
7.4 KiB
YAML
name: Scheduled Docker image refresh
|
||
|
||
# Re-runs the Docker image build against the latest published release on a
|
||
# weekly cadence. The code being built doesn't change — but the base image
|
||
# layers (python:*-slim-trixie and its OS packages) DO get upstream
|
||
# security patches between Superset releases, and those patches don't
|
||
# reach our published images unless we rebuild.
|
||
#
|
||
# Without this workflow, `apache/superset:<latest>` lags behind upstream
|
||
# Debian/Python base patches by whatever interval falls between Superset
|
||
# releases (typically 3–6 weeks). With it, the lag drops to at most one
|
||
# week regardless of release cadence.
|
||
#
|
||
# This is a security-hygiene cron, not a release. It overwrites the
|
||
# existing tags for the most recent release (e.g. `apache/superset:5.0.0`
|
||
# and `apache/superset:latest`) with bit-for-bit-equivalent contents
|
||
# layered on a refreshed base. Image digests change; everything users
|
||
# actually pin against (image content, code, deps) does not.
|
||
|
||
on:
|
||
schedule:
|
||
# Mondays at 06:00 UTC — gives the weekend for upstream patches to
|
||
# settle and surfaces failures at the start of the work week so a
|
||
# human can react.
|
||
- cron: "0 6 * * 1"
|
||
|
||
# Manual trigger so operators can force a refresh on demand (e.g.
|
||
# immediately after a high-severity base-image CVE drops).
|
||
workflow_dispatch: {}
|
||
|
||
permissions:
|
||
contents: read
|
||
|
||
# Serialize with itself and with the release publisher (tag-release.yml) —
|
||
# both push to the same Docker Hub tags, so a race could end with stale
|
||
# layers winning. Both workflows must declare this group for the lock to work.
|
||
concurrency:
|
||
group: docker-publish-latest-release
|
||
cancel-in-progress: false
|
||
|
||
jobs:
|
||
config:
|
||
runs-on: ubuntu-24.04
|
||
outputs:
|
||
has-secrets: ${{ steps.check.outputs.has-secrets }}
|
||
latest-release: ${{ steps.latest.outputs.tag }}
|
||
force-latest: ${{ steps.latest.outputs.force-latest }}
|
||
steps:
|
||
- name: Check for Docker Hub secrets
|
||
id: check
|
||
shell: bash
|
||
run: |
|
||
if [ -n "${DOCKERHUB_USER}" ]; then
|
||
echo "has-secrets=1" >> "$GITHUB_OUTPUT"
|
||
fi
|
||
env:
|
||
DOCKERHUB_USER: ${{ (secrets.DOCKERHUB_USER != '' && secrets.DOCKERHUB_TOKEN != '') || '' }}
|
||
|
||
- name: Look up latest published release
|
||
id: latest
|
||
shell: bash
|
||
env:
|
||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
REPOSITORY: ${{ github.repository }}
|
||
run: |
|
||
# `releases/latest` returns the latest non-prerelease, non-draft
|
||
# release — which is exactly what `apache/superset:latest`
|
||
# should reflect.
|
||
TAG=$(gh api "repos/${REPOSITORY}/releases/latest" --jq .tag_name)
|
||
if [ -z "$TAG" ] || [ "$TAG" = "null" ]; then
|
||
echo "::error::Could not determine latest release tag"
|
||
exit 1
|
||
fi
|
||
echo "Latest release: $TAG"
|
||
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
||
|
||
# Only move `:latest` when the release flagged "latest" is also the
|
||
# highest semver release. This guards against a mis-click leaving an
|
||
# older maintenance release (e.g. a 5.x patch shipped after 6.0 GA)
|
||
# marked latest, which would otherwise roll `:latest` back a major
|
||
# version on the next cron run. If it isn't the newest, we still
|
||
# refresh that release's own version tag but leave `:latest` alone.
|
||
HIGHEST=$(gh api --paginate "repos/${REPOSITORY}/releases" \
|
||
--jq '.[] | select(.draft|not) | select(.prerelease|not) | .tag_name' \
|
||
| sed 's/^v//' | sort -V | tail -n1)
|
||
if [ "${TAG#v}" = "$HIGHEST" ]; then
|
||
echo "force-latest=1" >> "$GITHUB_OUTPUT"
|
||
else
|
||
echo "::warning::Latest-flagged release $TAG is not the highest semver ($HIGHEST); refreshing its version tag but leaving :latest untouched"
|
||
fi
|
||
|
||
docker-rebuild:
|
||
needs: config
|
||
if: needs.config.outputs.has-secrets == '1'
|
||
name: docker-rebuild
|
||
runs-on: ubuntu-24.04
|
||
strategy:
|
||
# Mirror the same matrix the release publisher uses so every variant
|
||
# operators consume from Docker Hub gets the refreshed base.
|
||
matrix:
|
||
build_preset: ["dev", "lean", "py310", "websocket", "dockerize", "py311", "py312"]
|
||
fail-fast: false
|
||
steps:
|
||
- name: "Checkout release tag: ${{ needs.config.outputs.latest-release }}"
|
||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||
with:
|
||
ref: ${{ needs.config.outputs.latest-release }}
|
||
fetch-depth: 0
|
||
persist-credentials: false
|
||
|
||
- name: Setup Docker Environment
|
||
uses: ./.github/actions/setup-docker
|
||
with:
|
||
dockerhub-user: ${{ secrets.DOCKERHUB_USER }}
|
||
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
|
||
install-docker-compose: "false"
|
||
build: "true"
|
||
|
||
- name: Use Node.js 20
|
||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||
with:
|
||
node-version: 20
|
||
|
||
- name: Setup supersetbot
|
||
uses: ./.github/actions/setup-supersetbot/
|
||
|
||
- name: Rebuild and push
|
||
env:
|
||
DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }}
|
||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
BUILD_PRESET: ${{ matrix.build_preset }}
|
||
LATEST_RELEASE: ${{ needs.config.outputs.latest-release }}
|
||
FORCE_LATEST_FLAG: ${{ needs.config.outputs.force-latest == '1' && '--force-latest' || '' }}
|
||
run: |
|
||
# Reuses the same supersetbot invocation as the release
|
||
# publisher (`tag-release.yml`), so the resulting tags are
|
||
# identical to what a manual release dispatch would produce —
|
||
# just with a freshly-pulled base image layer underneath.
|
||
# `--force-latest` is only passed when the config job confirmed the
|
||
# fetched release is the newest one (see FORCE_LATEST_FLAG above).
|
||
supersetbot docker \
|
||
--push \
|
||
--preset "$BUILD_PRESET" \
|
||
--context release \
|
||
--context-ref "$LATEST_RELEASE" \
|
||
$FORCE_LATEST_FLAG \
|
||
--platform "linux/arm64" \
|
||
--platform "linux/amd64"
|
||
|
||
# The whole point of this cron is catching base-image CVEs, so a silent
|
||
# failure is the expensive case — a red X in the Actions tab nobody is
|
||
# watching on a Monday. File a tracked issue when any rebuild leg fails so
|
||
# a missed security refresh surfaces instead of sitting unnoticed.
|
||
notify-on-failure:
|
||
needs: [config, docker-rebuild]
|
||
if: failure() && needs.config.outputs.has-secrets == '1'
|
||
runs-on: ubuntu-24.04
|
||
permissions:
|
||
contents: read
|
||
issues: write
|
||
steps:
|
||
- name: Open a tracking issue
|
||
env:
|
||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
REPOSITORY: ${{ github.repository }}
|
||
LATEST_RELEASE: ${{ needs.config.outputs.latest-release }}
|
||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||
run: |
|
||
gh issue create \
|
||
--repo "$REPOSITORY" \
|
||
--title "Scheduled Docker image refresh failed for ${LATEST_RELEASE}" \
|
||
--label "infra:container" \
|
||
--label "bug" \
|
||
--body "The weekly Docker base-image refresh failed for release \`${LATEST_RELEASE}\`. Published images may be missing upstream base-layer security patches until this is resolved.
|
||
|
||
Failed run: ${RUN_URL}"
|