Compare commits

...

1 Commits

Author SHA1 Message Date
Joe Li
fff5397a0a 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 <noreply@anthropic.com>
2026-10-06 15:31:56 -07:00
12 changed files with 263 additions and 7 deletions

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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' }}

View File

@@ -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:

View File

@@ -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}"

View File

@@ -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"

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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