You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2026/06/26 00:15:38 UTC

(superset) branch master updated: ci: schedule a weekly Docker image rebuild against the latest release (#40426)

This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 7410ff73c0a ci: schedule a weekly Docker image rebuild against the latest release (#40426)
7410ff73c0a is described below

commit 7410ff73c0aa169b433862a104fde397a1f150be
Author: Evan Rusackas <ev...@preset.io>
AuthorDate: Thu Jun 25 17:15:31 2026 -0700

    ci: schedule a weekly Docker image rebuild against the latest release (#40426)
    
    Co-authored-by: Claude Code <no...@anthropic.com>
---
 .../workflows/scheduled-docker-image-refresh.yml   | 177 +++++++++++++++++++++
 .github/workflows/tag-release.yml                  |   6 +
 2 files changed, 183 insertions(+)

diff --git a/.github/workflows/scheduled-docker-image-refresh.yml b/.github/workflows/scheduled-docker-image-refresh.yml
new file mode 100644
index 00000000000..3ac26561767
--- /dev/null
+++ b/.github/workflows/scheduled-docker-image-refresh.yml
@@ -0,0 +1,177 @@
+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}"
diff --git a/.github/workflows/tag-release.yml b/.github/workflows/tag-release.yml
index 20c2213b01f..7488c555568 100644
--- a/.github/workflows/tag-release.yml
+++ b/.github/workflows/tag-release.yml
@@ -24,6 +24,12 @@ on:
 permissions:
   contents: read
 
+# Serialize with the scheduled Docker image refresh — both workflows push
+# to the same Docker Hub tags and must not race on apache/superset:latest.
+concurrency:
+  group: docker-publish-latest-release
+  cancel-in-progress: false
+
 jobs:
   config:
     runs-on: ubuntu-24.04