You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by jl...@apache.org on 2026/10/06 22:32:08 UTC

(superset) 01/01: ci: skip jobs that cannot complete on forks and private mirrors

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

sadpandajoe pushed a commit to branch ci-skip-uncompletable-jobs-on-forks
in repository https://gitbox.apache.org/repos/asf/superset.git

commit fff5397a0a2ca3f867639d3ea84f2538a010b94c
Author: Joe Li <jo...@preset.io>
AuthorDate: Tue Oct 6 15:31:56 2026 -0700

    ci: skip jobs that cannot complete on forks and private mirrors
    
    A fork or mirror that pushes to master runs every workflow, including
    jobs that depend on capabilities only the upstream repository has. Those
    jobs run to the end and then fail, or produce nothing, on every push.
    
    - CodeQL and zizmor: probe whether code scanning is enabled and skip the
      CodeQL analysis, or the zizmor SARIF upload, where it is not.
    - docker: skip push builds when the Docker Hub credentials are absent;
      pull request builds are unchanged.
    - translations: authenticate the base-ref fetch so it also works when
      the repository is not public.
    - docs and frontend coverage: grant the read permissions that a public
      repository allows implicitly.
    - nightly canary, testcontainers, bundle-size baseline and showtime
      cleanup: run on schedule only in apache/superset; manual dispatch
      still works everywhere.
    
    Co-Authored-By: Claude Opus 5.5 <no...@anthropic.com>
---
 .github/workflows/codeql-analysis.yml              |  19 +++-
 .github/workflows/docker.yml                       |  22 +++-
 .github/workflows/frontend-bundle-size-nightly.yml |   3 +
 .github/workflows/github-action-validator.yml      |  14 +++
 .github/workflows/python-nightly-canary.yml        |   3 +
 .../scripts/check-code-scanning-available.sh       |  64 +++++++++++
 .../scripts/check-code-scanning-available.test.sh  | 118 +++++++++++++++++++++
 .github/workflows/showtime-cleanup.yml             |   3 +
 .github/workflows/superset-docs-verify.yml         |   5 +
 .github/workflows/superset-frontend.yml            |   3 +
 .github/workflows/superset-translations.yml        |   9 +-
 .github/workflows/testcontainers.yml               |   7 +-
 12 files changed, 263 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 254cc6d792e..fd6f75ec795 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -24,8 +24,12 @@ jobs:
     permissions:
       contents: read
       pull-requests: read
+      # Lets the availability probe below tell "code scanning is not
+      # enabled" apart from "this token cannot read alerts".
+      security-events: read
     outputs:
       languages: ${{ steps.check.outputs.languages }}
+      code-scanning: ${{ steps.code-scanning.outputs.available }}
     steps:
       - name: Checkout
         uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -36,6 +40,14 @@ jobs:
         uses: $/.github/actions/change-detector/
         with:
           token: ${{ secrets.GITHUB_TOKEN }}
+      # A repository without code scanning (e.g. a private fork or mirror
+      # without GitHub Code Security) rejects the SARIF upload, so the
+      # analysis would run to completion and then fail on every push.
+      - name: Check code scanning availability
+        id: code-scanning
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        run: .github/workflows/scripts/check-code-scanning-available.sh
 
   analyze:
     name: Analyze
@@ -43,7 +55,12 @@ jobs:
     # Skip on PRs that touch neither code group (e.g. docs-only) so the
     # analysis runners don't spin up. push/schedule runs always proceed:
     # the change-detector returns "all changed" for non-PR events.
-    if: ${{ contains(fromJSON(needs.changes.outputs.languages), 'python') || contains(fromJSON(needs.changes.outputs.languages), 'javascript') }}
+    # Skipped entirely where code scanning is unavailable, since the upload
+    # is this job's only product.
+    if: >-
+      needs.changes.outputs.code-scanning != 'false' &&
+      (contains(fromJSON(needs.changes.outputs.languages), 'python') ||
+       contains(fromJSON(needs.changes.outputs.languages), 'javascript'))
     runs-on: ubuntu-26.04
     timeout-minutes: 15
     permissions:
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index dd7caad6ae5..015f4d905c3 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -40,7 +40,18 @@ jobs:
     timeout-minutes: 5
     outputs:
       matrix_config: ${{ steps.set_matrix.outputs.matrix_config }}
+      has-secrets: ${{ steps.check.outputs.has-secrets }}
     steps:
+      - name: "Check for secrets"
+        id: check
+        shell: bash
+        run: |
+          if [ -n "${DOCKERHUB_USER}" ] && [ -n "${DOCKERHUB_TOKEN}" ]; then
+            echo "has-secrets=1" >> "$GITHUB_OUTPUT"
+          fi
+        env:
+          DOCKERHUB_USER: ${{ (secrets.DOCKERHUB_USER != '') || '' }}
+          DOCKERHUB_TOKEN: ${{ (secrets.DOCKERHUB_TOKEN != '') || '' }}
       - id: set_matrix
         run: |
           MATRIX_CONFIG=$(if [ "${{ github.event_name }}" == "pull_request" ]; then echo '["superset", "dev", "lean"]'; else echo '["superset", "dev", "lean", "py311", "py312"]'; fi)
@@ -280,10 +291,15 @@ jobs:
   docker-build:
     name: docker-build
     needs: [setup_matrix, changes]
+    # A push build exists to publish images. Without the Docker Hub
+    # credentials (e.g. on a fork or mirror) it can publish nothing, so it
+    # is skipped there; pull_request builds, which only validate that the
+    # images build, run everywhere.
     if: >-
-      needs.changes.outputs.python == 'true' ||
-      needs.changes.outputs.frontend == 'true' ||
-      needs.changes.outputs.docker == 'true'
+      (github.event_name != 'push' || needs.setup_matrix.outputs.has-secrets) &&
+      (needs.changes.outputs.python == 'true' ||
+       needs.changes.outputs.frontend == 'true' ||
+       needs.changes.outputs.docker == 'true')
     runs-on: ubuntu-26.04
     timeout-minutes: 60
     permissions:
diff --git a/.github/workflows/frontend-bundle-size-nightly.yml b/.github/workflows/frontend-bundle-size-nightly.yml
index 9955016599f..98a7487cd2d 100644
--- a/.github/workflows/frontend-bundle-size-nightly.yml
+++ b/.github/workflows/frontend-bundle-size-nightly.yml
@@ -25,6 +25,9 @@ permissions:
 
 jobs:
   refresh-baseline:
+    # The nightly baseline is only consumed by upstream pull requests; forks
+    # and mirrors can still start it manually.
+    if: github.event_name != 'schedule' || github.repository == 'apache/superset'
     runs-on: ubuntu-26.04
     timeout-minutes: 30
     env:
diff --git a/.github/workflows/github-action-validator.yml b/.github/workflows/github-action-validator.yml
index b4988af0114..8f3c126bc1f 100644
--- a/.github/workflows/github-action-validator.yml
+++ b/.github/workflows/github-action-validator.yml
@@ -48,5 +48,19 @@ jobs:
       - name: Test docs-deploy freshness gate
         run: bash .github/workflows/scripts/check-docs-deploy-freshness.test.sh
 
+      - name: Test code scanning availability check
+        run: bash .github/workflows/scripts/check-code-scanning-available.test.sh
+
+      # A repository without code scanning (e.g. a private fork or mirror
+      # without GitHub Code Security) rejects the SARIF upload. The audit
+      # still runs there; its findings are reported in the job log instead.
+      - name: Check code scanning availability
+        id: code-scanning
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        run: .github/workflows/scripts/check-code-scanning-available.sh
+
       - name: Check for security issues on GHA workflows
         uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4
+        with:
+          advanced-security: ${{ steps.code-scanning.outputs.available != 'false' }}
diff --git a/.github/workflows/python-nightly-canary.yml b/.github/workflows/python-nightly-canary.yml
index bc5cd02a14f..a2a53228778 100644
--- a/.github/workflows/python-nightly-canary.yml
+++ b/.github/workflows/python-nightly-canary.yml
@@ -30,6 +30,9 @@ concurrency:
 
 jobs:
   unit-tests-next:
+    # The nightly run reports to the upstream issue tracker; forks and
+    # mirrors can still start it manually.
+    if: github.event_name != 'schedule' || github.repository == 'apache/superset'
     runs-on: ubuntu-26.04
     timeout-minutes: 45
     env:
diff --git a/.github/workflows/scripts/check-code-scanning-available.sh b/.github/workflows/scripts/check-code-scanning-available.sh
new file mode 100755
index 00000000000..ac89d591624
--- /dev/null
+++ b/.github/workflows/scripts/check-code-scanning-available.sh
@@ -0,0 +1,64 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Reports whether the repository can accept code scanning (SARIF) uploads,
+# so jobs whose only product is such an upload can be skipped where the
+# upload is guaranteed to be rejected -- e.g. a private fork or mirror
+# without GitHub Code Security, where the analysis would run to completion
+# and then fail at the upload step on every push.
+#
+# Fails open: only GitHub's explicit "must be enabled" rejection counts as
+# unavailable. Any other response (success, an empty alert list, a token
+# without permission to read alerts, a network error) reports available, so
+# the upstream repository and forks that do have code scanning are never
+# skipped by mistake.
+#
+# Required env:
+#   GH_TOKEN          - token used to query the code scanning API
+#   GITHUB_REPOSITORY - "owner/repo" to query
+#   GITHUB_OUTPUT     - path to the step's output file
+# Optional env:
+#   GITHUB_API_URL    - API root, defaults to https://api.github.com
+#   OUTPUT_NAME       - output key to write, defaults to "available"
+
+set -euo pipefail
+
+output_name="${OUTPUT_NAME:-available}"
+api_url="${GITHUB_API_URL:-https://api.github.com}"
+
+# The response body can contain alert details, so it is inspected but never
+# printed. The HTTP status is appended on its own line so that an alert whose
+# text happens to contain the rejection message is not mistaken for it.
+response="$(
+  curl --silent --max-time 30 \
+    --write-out '\n%{http_code}' \
+    --header "Authorization: Bearer ${GH_TOKEN}" \
+    --header "Accept: application/vnd.github+json" \
+    "${api_url}/repos/${GITHUB_REPOSITORY}/code-scanning/alerts?per_page=1" \
+    2>/dev/null || true
+)"
+status="${response##*$'\n'}"
+body="${response%$'\n'*}"
+
+available=true
+if [ "${status}" = "403" ] && grep --quiet --extended-regexp \
+  '(Code|Advanced) Security must be enabled' <<<"${body}"; then
+  available=false
+  echo "Code scanning is not enabled for ${GITHUB_REPOSITORY}; jobs that only upload code scanning results will be skipped."
+fi
+
+echo "${output_name}=${available}" >>"${GITHUB_OUTPUT}"
diff --git a/.github/workflows/scripts/check-code-scanning-available.test.sh b/.github/workflows/scripts/check-code-scanning-available.test.sh
new file mode 100755
index 00000000000..2d054f5c030
--- /dev/null
+++ b/.github/workflows/scripts/check-code-scanning-available.test.sh
@@ -0,0 +1,118 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Exercises check-code-scanning-available.sh against a stubbed `curl`,
+# covering the "not enabled" rejection and the fail-open branches so the
+# output contract (available=true|false) can't silently regress. Run
+# directly, no extra tooling required:
+#   bash .github/workflows/scripts/check-code-scanning-available.test.sh
+
+set -euo pipefail
+
+script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+script_under_test="${script_dir}/check-code-scanning-available.sh"
+
+failures=0
+
+# Runs the script under test with a stubbed `curl` that replies with HTTP
+# status $2 and body $3 and exits with $4, asserting that GITHUB_OUTPUT ends
+# up containing exactly $5.
+run_case() {
+  local case_name="$1"
+  local http_status="$2"
+  local response_body="$3"
+  local curl_exit_code="$4"
+  local expected_line="$5"
+
+  local workdir
+  workdir="$(mktemp -d)"
+  trap 'rm -rf "${workdir}"' RETURN
+
+  # Fake `curl` that replays a canned response regardless of arguments, so
+  # the script under test never touches the network. The status follows the
+  # body on its own line, as the real `--write-out` format produces.
+  printf '%s\n%s' "${response_body}" "${http_status}" >"${workdir}/response"
+  cat >"${workdir}/curl" <<EOF
+#!/bin/bash
+cat '${workdir}/response'
+exit ${curl_exit_code}
+EOF
+  chmod +x "${workdir}/curl"
+
+  local output_file="${workdir}/github_output"
+  : >"${output_file}"
+
+  if PATH="${workdir}:${PATH}" \
+    GITHUB_OUTPUT="${output_file}" \
+    GITHUB_REPOSITORY="apache/superset" \
+    GH_TOKEN="fake-token" \
+    bash "${script_under_test}" >/dev/null; then
+    :
+  else
+    echo "FAIL (${case_name}): script exited non-zero"
+    failures=$((failures + 1))
+    return
+  fi
+
+  local actual
+  actual="$(cat "${output_file}")"
+  if [ "${actual}" = "${expected_line}" ]; then
+    echo "PASS (${case_name})"
+  else
+    echo "FAIL (${case_name}): expected '${expected_line}', got '${actual}'"
+    failures=$((failures + 1))
+  fi
+}
+
+run_case "alerts are readable" \
+  200 '[{"number":1,"state":"open"}]' 0 \
+  "available=true"
+
+run_case "no alerts yet" \
+  200 '[]' 0 \
+  "available=true"
+
+run_case "Code Security is not enabled" \
+  403 '{"message":"Code Security must be enabled for this repository to use code scanning.","status":"403"}' 0 \
+  "available=false"
+
+run_case "Advanced Security is not enabled" \
+  403 '{"message":"Advanced Security must be enabled for this repository to use code scanning.","status":"403"}' 0 \
+  "available=false"
+
+run_case "an alert merely quotes the rejection message" \
+  200 '[{"rule":{"description":"Code Security must be enabled for this repository"}}]' 0 \
+  "available=true"
+
+run_case "token cannot read alerts" \
+  403 '{"message":"Resource not accessible by integration","status":"403"}' 0 \
+  "available=true"
+
+run_case "no analysis found" \
+  404 '{"message":"no analysis found","status":"404"}' 0 \
+  "available=true"
+
+run_case "network failure" \
+  000 '' 28 \
+  "available=true"
+
+if [ "${failures}" -gt 0 ]; then
+  echo "${failures} case(s) failed"
+  exit 1
+fi
+
+echo "All cases passed"
diff --git a/.github/workflows/showtime-cleanup.yml b/.github/workflows/showtime-cleanup.yml
index 2d39f531529..1d1bb5b4c35 100644
--- a/.github/workflows/showtime-cleanup.yml
+++ b/.github/workflows/showtime-cleanup.yml
@@ -23,6 +23,9 @@ permissions:
 jobs:
   cleanup-expired:
     name: Clean up expired showtime environments
+    # The environments live in the upstream project's infrastructure; forks
+    # and mirrors can still start a cleanup manually.
+    if: github.event_name != 'schedule' || github.repository == 'apache/superset'
     runs-on: ubuntu-latest
 
     permissions:
diff --git a/.github/workflows/superset-docs-verify.yml b/.github/workflows/superset-docs-verify.yml
index 5828e98176c..766c492212a 100644
--- a/.github/workflows/superset-docs-verify.yml
+++ b/.github/workflows/superset-docs-verify.yml
@@ -122,6 +122,11 @@ jobs:
       github.event.workflow_run.head_repository.full_name == github.repository
     name: Build (after integration tests)
     runs-on: ubuntu-26.04
+    permissions:
+      contents: read
+      # Required to download the diagnostics artifact from the triggering
+      # run; a public repository allows it implicitly, a private one does not.
+      actions: read
     defaults:
       run:
         working-directory: docs
diff --git a/.github/workflows/superset-frontend.yml b/.github/workflows/superset-frontend.yml
index 6518621206a..485dc807744 100644
--- a/.github/workflows/superset-frontend.yml
+++ b/.github/workflows/superset-frontend.yml
@@ -114,6 +114,9 @@ jobs:
     runs-on: ubuntu-slim
     timeout-minutes: 15
     permissions:
+      # Declaring any permission drops the defaults, and the checkout below
+      # needs read access wherever the repository is not public.
+      contents: read
       id-token: write
     steps:
       - name: Checkout Code
diff --git a/.github/workflows/superset-translations.yml b/.github/workflows/superset-translations.yml
index a613bc2cd16..4e1713f0c25 100644
--- a/.github/workflows/superset-translations.yml
+++ b/.github/workflows/superset-translations.yml
@@ -110,13 +110,18 @@ jobs:
         if: steps.check.outputs.python == 'true' || steps.check.outputs.frontend == 'true'
         env:
           PR_BASE_REF: ${{ github.event.pull_request.base.ref }}
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         run: |
+          # The checkout above does not persist credentials, and an anonymous
+          # fetch only works against a public repository. Authenticate this
+          # one fetch so the comparison also works in private forks/mirrors.
+          AUTH_HEADER="AUTHORIZATION: basic $(printf 'x-access-token:%s' "$GH_TOKEN" | base64 | tr -d '\n')"
           # For PRs use the base branch; for direct pushes compare against the previous commit.
           BASE_REF="$PR_BASE_REF"
           if [ -n "$BASE_REF" ]; then
-            git fetch --depth=1 origin "$BASE_REF"
+            git -c "http.extraheader=$AUTH_HEADER" fetch --depth=1 origin "$BASE_REF"
           else
-            git fetch --depth=2 origin "$GITHUB_REF"
+            git -c "http.extraheader=$AUTH_HEADER" fetch --depth=2 origin "$GITHUB_REF"
           fi
           git worktree add /tmp/base-worktree FETCH_HEAD
 
diff --git a/.github/workflows/testcontainers.yml b/.github/workflows/testcontainers.yml
index ae99c39b906..2b8e31b7d8e 100644
--- a/.github/workflows/testcontainers.yml
+++ b/.github/workflows/testcontainers.yml
@@ -48,6 +48,11 @@ jobs:
     # can't reference `matrix` (only github/inputs/needs/vars are available
     # there), so the filtering has to happen here, before the matrix exists,
     # instead of on the testcontainers job itself.
+    #
+    # The nightly run only exists to catch driver drift for the upstream
+    # project, so it is skipped on forks and mirrors (which can still start
+    # it manually); skipping here skips the testcontainers jobs with it.
+    if: github.event_name != 'schedule' || github.repository == 'apache/superset'
     runs-on: ubuntu-26.04
     outputs:
       include: ${{ steps.filter.outputs.include }}
@@ -177,7 +182,7 @@ jobs:
 
   actions-timeline:
     needs: [testcontainers]
-    if: always()
+    if: always() && (github.event_name != 'schedule' || github.repository == 'apache/superset')
     runs-on: ubuntu-26.04
     permissions:
       actions: read