diff --git a/.github/workflows/update_actions.yml b/.github/workflows/update_actions.yml deleted file mode 100644 index 54baa5d0..00000000 --- a/.github/workflows/update_actions.yml +++ /dev/null @@ -1,91 +0,0 @@ -# -# 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. -# -name: Handle Dependabot update -on: - workflow_dispatch: - push: - branches: - - main - paths: - - ".github/actions/for-dependabot-triggered-reviews/action.yml" - pull_request: - paths: - - ".github/workflows/update_actions.yml" - - ".github/actions/for-dependabot-triggered-reviews/action.yml" - - gateway/* - -permissions: - contents: read - -# We want workflows on main to run in order to avoid losing data through race conditions -concurrency: "${{ github.ref }}-${{ github.workflow }}" - -jobs: - update_actions: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: true - token: ${{ secrets.ALLOWLIST_WORKFLOW_TOKEN || github.token }} # zizmor: ignore[secrets-outside-env] - - - name: Print token details - if: ${{ github.event_name != 'pull_request' }} - env: - GH_TOKEN: ${{ secrets.ALLOWLIST_WORKFLOW_TOKEN }} # zizmor: ignore[secrets-outside-env] - run: | - echo "::group::Token details" - echo "Token user and permissions:" - gh api /user --jq '"Login: \(.login)\nName: \(.name)\nEmail: \(.email)"' - echo "" - echo "Token expiration:" - gh api /installation/token --jq '.expires_at' 2>/dev/null || echo "Token expiration not available (likely a PAT, not an installation token)" - echo "" - echo "Token scopes:" - curl -sS -H "Authorization: token ${GH_TOKEN}" -I https://api.github.com/ 2>/dev/null | grep -i 'x-oauth-scopes' || echo "No OAuth scopes header (fine-grained or app token)" - echo "::endgroup::" - - - run: pipx install uv - - - name: Update actions.yml - run: | - uv run python << 'PYEOF' - import sys - sys.path.append("./gateway/") - - import gateway as g - g.update_actions(".github/actions/for-dependabot-triggered-reviews/action.yml", "actions.yml") - g.update_patterns("approved_patterns.yml", "actions.yml") - PYEOF - - - name: Commit and push changes - if: ${{ github.event_name != 'pull_request' }} - env: - GH_TOKEN: ${{ secrets.ALLOWLIST_WORKFLOW_TOKEN || github.token }} # zizmor: ignore[secrets-outside-env] - run: | - AUTHOR_NAME=$(gh api /user --jq '.login' 2>/dev/null || echo "asfgit") - AUTHOR_EMAIL=$(gh api /user --jq '.email // "\(.login)@users.noreply.github.com"' 2>/dev/null || echo "asfgit@users.noreply.github.com") - git config --local user.name "${AUTHOR_NAME}" - git config --local user.email "${AUTHOR_EMAIL}" - git add -f actions.yml approved_patterns.yml - git commit -m "Update actions.yml and approved_patterns.yml based on .github/actions/for-dependabot-triggered-reviews/action.yml" -m "Generated by .github/workflows/update_actions.yml" || echo "No changes" - git push origin diff --git a/.github/workflows/update_allowlist.yml b/.github/workflows/update_allowlist.yml new file mode 100644 index 00000000..6d590fc2 --- /dev/null +++ b/.github/workflows/update_allowlist.yml @@ -0,0 +1,167 @@ +# +# 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. +# +# Replaces the former pair `update_actions.yml` (composite -> actions.yml) +# and `update_composite_action.yml` (actions.yml -> composite). Those ran +# in separate concurrency groups and could race when both files changed in +# overlapping pushes, silently overwriting one direction's edit. This +# single workflow always runs both directions in order, so neither edit +# is lost regardless of which file triggered the run. See #866. +name: Update Allowlist +on: + workflow_dispatch: + push: + branches: + - main + paths: + - "actions.yml" + - ".github/actions/for-dependabot-triggered-reviews/action.yml" + pull_request: + paths: + - ".github/workflows/update_allowlist.yml" + - ".github/actions/for-dependabot-triggered-reviews/action.yml" + - "actions.yml" + - "gateway/*" + +permissions: + contents: read + +# Single group across both inputs so no two updates touch the allowlist +# files in parallel. Don't cancel — every queued run must finish so we +# don't drop a dependabot bump or a manual actions.yml edit. +concurrency: + group: "${{ github.ref }}-update-allowlist" + cancel-in-progress: false + +jobs: + update: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: true + token: ${{ secrets.ALLOWLIST_WORKFLOW_TOKEN || github.token }} # zizmor: ignore[secrets-outside-env] + # On push/dispatch, check out the current tip of main rather + # than the trigger SHA. A queued run that started on an older + # SHA would otherwise regenerate from stale inputs and either + # undo the prior run's commit or fail to push. + ref: ${{ (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && 'main' || '' }} + + - name: Print token details + if: ${{ github.event_name != 'pull_request' }} + env: + GH_TOKEN: ${{ secrets.ALLOWLIST_WORKFLOW_TOKEN }} # zizmor: ignore[secrets-outside-env] + run: | + echo "::group::Token details" + echo "Token user and permissions:" + gh api /user --jq '"Login: \(.login)\nName: \(.name)\nEmail: \(.email)"' + echo "" + echo "Token expiration:" + gh api /installation/token --jq '.expires_at' 2>/dev/null || echo "Token expiration not available (likely a PAT, not an installation token)" + echo "" + echo "Token scopes:" + curl -sS -H "Authorization: token ${GH_TOKEN}" -I https://api.github.com/ 2>/dev/null | grep -i 'x-oauth-scopes' || echo "No OAuth scopes header (fine-grained or app token)" + echo "::endgroup::" + + - run: pipx install uv + + - name: Sync composite action, actions.yml, and approved_patterns.yml + run: | + uv run python << 'PYEOF' + import sys + sys.path.append("./gateway/") + + import gateway as g + + composite = ".github/actions/for-dependabot-triggered-reviews/action.yml" + actions = "actions.yml" + patterns = "approved_patterns.yml" + + # Always run both directions. The previous two-workflow split + # raced on overlapping pushes; running both here means the + # outcome is the same regardless of which file triggered us. + # + # 1. Pull any new refs from the composite (e.g. dependabot + # bumps) into actions.yml. Additive: existing entries stay + # and get their expiry refreshed. + g.update_actions(composite, actions) + + # 2. Regenerate the composite from the (now-merged) + # actions.yml so a manual actions.yml edit is reflected. + g.update_workflow(composite, actions) + + # 3. Regenerate the approved patterns from actions.yml. + g.update_patterns(patterns, actions) + PYEOF + + - name: Commit and push changes + if: ${{ github.event_name != 'pull_request' }} + env: + GH_TOKEN: ${{ secrets.ALLOWLIST_WORKFLOW_TOKEN || github.token }} # zizmor: ignore[secrets-outside-env] + run: | + AUTHOR_NAME=$(gh api /user --jq '.login' 2>/dev/null || echo "asfgit") + AUTHOR_EMAIL=$(gh api /user --jq '.email // "\(.login)@users.noreply.github.com"' 2>/dev/null || echo "asfgit@users.noreply.github.com") + git config --local user.name "${AUTHOR_NAME}" + git config --local user.email "${AUTHOR_EMAIL}" + + composite=".github/actions/for-dependabot-triggered-reviews/action.yml" + if git diff --quiet -- actions.yml approved_patterns.yml "${composite}"; then + echo "No changes" + exit 0 + fi + + git add -f actions.yml approved_patterns.yml "${composite}" + git commit \ + -m "Sync actions.yml, composite action, and approved_patterns.yml" \ + -m "Generated by .github/workflows/update_allowlist.yml" + + # If a concurrent push (e.g. remove_expired.yml) advanced main + # while we were computing, rebase and retry. The sync script is + # idempotent so re-running on the rebased tree is safe. + for attempt in 1 2 3; do + if git push origin HEAD:main; then + exit 0 + fi + echo "Push rejected on attempt ${attempt}; rebasing and re-running sync" + git fetch origin main + git reset --hard origin/main + uv run python << 'PYEOF' + import sys + sys.path.append("./gateway/") + import gateway as g + composite = ".github/actions/for-dependabot-triggered-reviews/action.yml" + actions = "actions.yml" + patterns = "approved_patterns.yml" + g.update_actions(composite, actions) + g.update_workflow(composite, actions) + g.update_patterns(patterns, actions) + PYEOF + if git diff --quiet -- actions.yml approved_patterns.yml "${composite}"; then + echo "Already in sync after rebase; nothing to push" + exit 0 + fi + git add -f actions.yml approved_patterns.yml "${composite}" + git commit \ + -m "Sync actions.yml, composite action, and approved_patterns.yml" \ + -m "Generated by .github/workflows/update_allowlist.yml" + done + echo "Failed to push after 3 attempts" + exit 1 diff --git a/.github/workflows/update_composite_action.yml b/.github/workflows/update_composite_action.yml deleted file mode 100644 index 2b3d58d2..00000000 --- a/.github/workflows/update_composite_action.yml +++ /dev/null @@ -1,90 +0,0 @@ -# -# 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. -# -name: Update Approved Patterns and Composite Action -on: - workflow_dispatch: - push: - branches: - - main - paths: - - "actions.yml" - pull_request: - paths: - - ".github/workflows/update_composite_action.yml" - - "actions.yml" - - gateway/* - -permissions: {} - -# We want workflows on main to run in order to avoid losing data through race conditions -concurrency: "${{ github.ref }}-${{ github.workflow }}" - -jobs: - update: - name: Update Workflow - runs-on: ubuntu-latest - steps: - - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: true - # We have to use a PAT to commit the workflow file - token: ${{ secrets.ALLOWLIST_WORKFLOW_TOKEN || github.token }} # zizmor: ignore[secrets-outside-env] - - - name: Print token details - if: ${{ github.event_name != 'pull_request' }} - env: - GH_TOKEN: ${{ secrets.ALLOWLIST_WORKFLOW_TOKEN }} # zizmor: ignore[secrets-outside-env] - run: | - echo "::group::Token details" - echo "Token user and permissions:" - gh api /user --jq '"Login: \(.login)\nName: \(.name)\nEmail: \(.email)"' - echo "" - echo "Token expiration:" - gh api /installation/token --jq '.expires_at' 2>/dev/null || echo "Token expiration not available (likely a PAT, not an installation token)" - echo "" - echo "Token scopes:" - curl -sS -H "Authorization: token ${GH_TOKEN}" -I https://api.github.com/ 2>/dev/null | grep -i 'x-oauth-scopes' || echo "No OAuth scopes header (fine-grained or app token)" - echo "::endgroup::" - - - run: pipx install uv - - - name: Update Workflow - run: | - uv run python << 'PYEOF' - import sys - sys.path.append("./gateway/") - - import gateway as g - g.update_workflow(".github/actions/for-dependabot-triggered-reviews/action.yml", "actions.yml") - g.update_patterns("approved_patterns.yml", "actions.yml") - PYEOF - - - name: Commit and push changes - if: ${{ github.event_name != 'pull_request' }} - env: - GH_TOKEN: ${{ secrets.ALLOWLIST_WORKFLOW_TOKEN || github.token }} # zizmor: ignore[secrets-outside-env] - run: | - AUTHOR_NAME=$(gh api /user --jq '.login' 2>/dev/null || echo "asfgit") - AUTHOR_EMAIL=$(gh api /user --jq '.email // "\(.login)@users.noreply.github.com"' 2>/dev/null || echo "asfgit@users.noreply.github.com") - git config --local user.name "${AUTHOR_NAME}" - git config --local user.email "${AUTHOR_EMAIL}" - git add -f .github/actions/for-dependabot-triggered-reviews/action.yml approved_patterns.yml - git commit -m "Update approved_patterns.yml and .github/actions/for-dependabot-triggered-reviews/action.yml based on actions.yml" -m "Generated by .github/workflows/update_composite_action.yml" || echo "No changes" - git push origin diff --git a/README.md b/README.md index 27e74991..b702db81 100644 --- a/README.md +++ b/README.md @@ -121,11 +121,11 @@ graph LR dependabot-->composite dependabot-.verified by.-verify["verify_dependabot_action.yml
(rebuild & diff)"] - composite=="update_actions.yml
(on merge)"==>actions + composite=="update_allowlist.yml
(on merge)"==>actions cron=="remove_expired.yml"==>actions - actions=="update_composite_action.yml"==>composite - actions=="update_composite_action.yml"==>approved + actions=="update_allowlist.yml"==>composite + actions=="update_allowlist.yml"==>approved guard["check_approved_limit.yml
(fails at 800 / 1000)"]-.monitors.-approved @@ -149,11 +149,11 @@ Solid arrows (`==>`) are workflow regeneration edges — the "source → generat ```mermaid graph TD; manual["manual PR"]--new entry-->actions.yml - actions.yml--"update_composite_action.yml"-->composite[".github/actions/for-dependabot-triggered-reviews/action.yml"] - actions.yml--"update_composite_action.yml"-->approved["approved_patterns.yml"] + actions.yml--"update_allowlist.yml"-->composite[".github/actions/for-dependabot-triggered-reviews/action.yml"] + actions.yml--"update_allowlist.yml"-->approved["approved_patterns.yml"] ``` -A human-authored PR edits `actions.yml` directly. Once it merges to `main`, the **`update_composite_action.yml`** workflow regenerates both `.github/actions/for-dependabot-triggered-reviews/action.yml` and `approved_patterns.yml` from the new entries, so contributors never have to hand-edit the generated files. +A human-authored PR edits `actions.yml` directly. Once it merges to `main`, the **`update_allowlist.yml`** workflow regenerates both `.github/actions/for-dependabot-triggered-reviews/action.yml` and `approved_patterns.yml` from the new entries, so contributors never have to hand-edit the generated files. To request addition of an action to the allow list: @@ -185,14 +185,14 @@ The infrastructure team will review your request and either approve, request cha graph TD; dependabot--"PR updates"-->composite[".github/actions/for-dependabot-triggered-reviews/action.yml"] dependabot-.verified by.-verify["verify_dependabot_action.yml"] - composite--"update_actions.yml (on merge)"-->actions.yml - actions.yml--"update_actions.yml"-->approved["approved_patterns.yml"] + composite--"update_allowlist.yml (on merge)"-->actions.yml + actions.yml--"update_allowlist.yml"-->approved["approved_patterns.yml"] ``` In most cases, new versions are automatically added through Dependabot: - Dependabot opens PRs against `.github/actions/for-dependabot-triggered-reviews/action.yml` to update actions to the newest releases - **`verify_dependabot_action.yml`** runs on each such PR, rebuilds the action's compiled JavaScript in Docker, and diffs it against the published version (see [Automated Verification in CI](#automated-verification-in-ci)) -- Once a reviewer merges the PR, **`update_actions.yml`** reflects the new commit SHAs back into `actions.yml` and regenerates `approved_patterns.yml` +- Once a reviewer merges the PR, **`update_allowlist.yml`** reflects the new commit SHAs back into `actions.yml` and regenerates `approved_patterns.yml` - The previously approved version is marked with an `expires_at` date 3 months out, giving projects a grace period to update their workflows; see [Automatic Expiration of Old Versions](#automatic-expiration-of-old-versions) for how the cleanup runs Projects are encouraged to help review updates to actions they use. Please have a look at the diff and mention in your approval what you have checked and why you think the action is safe. @@ -356,15 +356,15 @@ If you add older version of the action and want to set an expiration date for it ```mermaid graph TD; entry["actions.yml entry
with expires_at"]--"remove_expired.yml (daily, 02:04 UTC)"-->actions.yml - actions.yml--"update_composite_action.yml"-->composite[".github/actions/for-dependabot-triggered-reviews/action.yml"] - actions.yml--"update_composite_action.yml"-->approved["approved_patterns.yml"] + actions.yml--"update_allowlist.yml"-->composite[".github/actions/for-dependabot-triggered-reviews/action.yml"] + actions.yml--"update_allowlist.yml"-->approved["approved_patterns.yml"] ``` Routine cleanup of superseded versions is automated: - Any entry in `actions.yml` with an `expires_at: YYYY-MM-DD` field is a candidate for removal. - Dependabot-driven updates (see [Updating Version of Already Approved Action](#updating-version-of-already-approved-action)) set `expires_at` to **3 months out** on the previously approved version. For manually added older versions, set `expires_at` explicitly (see [Manual Addition of Specific Versions](#manual-addition-of-specific-versions)). -- The **`remove_expired.yml`** workflow runs daily at **02:04 UTC**. Every entry whose `expires_at` date has passed is deleted from `actions.yml`; the workflow then commits the change and lets `update_composite_action.yml` regenerate `approved_patterns.yml` and the dependabot composite. +- The **`remove_expired.yml`** workflow runs daily at **02:04 UTC**. Every entry whose `expires_at` date has passed is deleted from `actions.yml`; the workflow then commits the change and lets `update_allowlist.yml` regenerate `approved_patterns.yml` and the dependabot composite. - Entries without `expires_at` (for example, `keep: true` wildcards and the current approved version) are never auto-removed — removal of those requires a manual PR. No human action is required for the routine case: projects get a 3-month grace window after a version bump, and the old entry disappears on its own afterwards. diff --git a/utils/check_cache_settings/audit-actions-cache.py b/utils/check_cache_settings/audit-actions-cache.py index 588ac3cc..98c0df64 100644 --- a/utils/check_cache_settings/audit-actions-cache.py +++ b/utils/check_cache_settings/audit-actions-cache.py @@ -1,4 +1,23 @@ #!/usr/bin/env python3 +# +# 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. +# + """ audit-actions-cache.py diff --git a/utils/check_cache_settings/test_audit_actions_cache.py b/utils/check_cache_settings/test_audit_actions_cache.py index 895fe251..8fefa526 100644 --- a/utils/check_cache_settings/test_audit_actions_cache.py +++ b/utils/check_cache_settings/test_audit_actions_cache.py @@ -1,3 +1,22 @@ +# +# 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. +# + """ test_audit_actions_cache.py