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

rusackas pushed a commit to branch ci/scheduled-docker-image-refresh
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 3212243906c14a844038d0b27ac8b9dcac8b4f20
Author: Claude Code <[email protected]>
AuthorDate: Mon May 25 20:26:15 2026 -0700

    ci: schedule a weekly Docker image rebuild against the latest release
    
    Adds a cron-triggered workflow that re-runs the Docker image build
    against the most-recent published release every Monday at 06:00 UTC
    (and on manual workflow_dispatch when an operator wants to force it).
    The Superset code being built doesn't change — but the base image
    layers (python:*-slim-trixie and the Debian OS packages underneath)
    DO receive upstream security patches between Superset releases. Without
    a rebuild, apache/superset:<latest> ships those CVEs unfixed for as
    long as the inter-release gap (typically 3–6 weeks).
    
    Why this approach over the alternatives:
    
    - Tied to releases: defeats the purpose — the gap we're trying to close
      IS the inter-release window. Release-triggered rebuilds happen exactly
      when we already get them.
    - Swap to Chainguard / distroless: would also close the gap, but at the
      cost of a backward-incompatible package-manager change for downstream
      operators who extend `apache/superset:<tag>` with their own apt
      install lines for custom drivers. A scheduled rebuild captures most
      of the CVE-cycling benefit without that breakage.
    - Daily cadence: probably overkill — Debian's security tree updates on
      a roughly weekly rhythm and a daily rebuild would just churn the
      registry without adding much.
    
    Implementation: deliberately reuses the same `supersetbot docker`
    invocation as `tag-release.yml` (same matrix of build presets, same
    `--context release --context-ref <tag> --force-latest` flags), so the
    resulting tags are byte-equivalent to what a manual release dispatch
    would produce — only the base layer changes. Concurrency group
    shared with the release publisher so the two can't race each other
    on the Docker Hub push.
    
    Tag mutability note: the rebuild overwrites both the rolling tags
    (`apache/superset:latest`) AND the version-specific tag of the latest
    release (e.g. `apache/superset:5.0.0`). This is intentional and
    matches how the upstream `python:*-slim-trixie` images themselves
    behave — version tags reflect content + latest patches, not a frozen
    SHA. Users who need a frozen reference should pin by image digest.
---
 .../workflows/scheduled-docker-image-refresh.yml   | 127 +++++++++++++++++++++
 1 file changed, 127 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..69d80440c42
--- /dev/null
+++ b/.github/workflows/scheduled-docker-image-refresh.yml
@@ -0,0 +1,127 @@
+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 — both push to the
+# same Docker Hub tags, so a race here could end with stale layers
+# winning.
+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 }}
+    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 }}
+        run: |
+          # `releases/latest` returns the latest non-prerelease, non-draft
+          # release — which is exactly what `apache/superset:latest`
+          # should reflect.
+          TAG=$(gh api repos/${{ github.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"
+
+  docker-rebuild:
+    needs: config
+    if: needs.config.outputs.has-secrets
+    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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
+        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 }}
+        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.
+          supersetbot docker \
+            --push \
+            --preset ${{ matrix.build_preset }} \
+            --context release \
+            --context-ref "${{ needs.config.outputs.latest-release }}" \
+            --force-latest \
+            --platform "linux/arm64" \
+            --platform "linux/amd64"

Reply via email to