diff --git a/.github/cookiecutter-migrate.template.py b/.github/cookiecutter-migrate.template.py index b684dc69..e12ba506 100644 --- a/.github/cookiecutter-migrate.template.py +++ b/.github/cookiecutter-migrate.template.py @@ -10,7 +10,7 @@ To run it, the simplest way is to fetch it from GitHub and run it directly: - curl -sSL https://raw.githubusercontent.com/frequenz-floss/frequenz-repo-config-python//cookiecutter/migrate.py | python3 + curl -sSLf https://raw.githubusercontent.com/frequenz-floss/frequenz-repo-config-python//cookiecutter/migrate.py | python3 Make sure to replace the `` to the version you want to migrate to in the URL. diff --git a/.github/workflows/auto-dependabot.yaml b/.github/workflows/auto-dependabot.yaml index ce9b142e..369344a4 100644 --- a/.github/workflows/auto-dependabot.yaml +++ b/.github/workflows/auto-dependabot.yaml @@ -12,7 +12,9 @@ on: pull_request_target: permissions: + # Read repository contents and Dependabot metadata used by the nested action. contents: read + # The nested action also uses `github.token` internally for PR operations. pull-requests: write jobs: @@ -27,6 +29,12 @@ jobs: with: app-id: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_ID }} private-key: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_PRIVATE_KEY }} + # Merge Dependabot PRs. + permission-contents: write + # Create the auto-merged label if it does not exist. + permission-issues: write + # Approve PRs, add labels, and enable auto-merge. + permission-pull-requests: write - name: Auto-merge Dependabot PR uses: frequenz-floss/dependabot-auto-approve@e943399cc9d76fbb6d7faae446cd57301d110165 # v1.5.0 diff --git a/.github/workflows/ci-pr.yaml b/.github/workflows/ci-pr.yaml index 059236ff..015d585c 100644 --- a/.github/workflows/ci-pr.yaml +++ b/.github/workflows/ci-pr.yaml @@ -3,6 +3,10 @@ name: Test PR on: pull_request: +permissions: + # Read repository contents for checkout and dependency resolution only. + contents: read + env: # Please make sure this version is included in the `matrix`, as the # `matrix` section can't use `env`, so it must be entered manually @@ -17,7 +21,7 @@ jobs: steps: - name: Run nox - uses: frequenz-floss/gh-action-nox@v1.1.0 + uses: frequenz-floss/gh-action-nox@e1351cf45e05e85afc1c79ab883e06322892d34c # v1.1.0 with: python-version: "3.11" nox-session: ci_checks_max @@ -27,15 +31,15 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 - name: Fetch sources - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.2 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: .[dev-mkdocs] @@ -44,11 +48,14 @@ jobs: env: MIKE_VERSION: gh-${{ github.job }} run: | - mike deploy $MIKE_VERSION - mike set-default $MIKE_VERSION + # mike is installed as a console script, not a runnable module. + # Run the installed script under isolated mode to avoid importing from + # the workspace when building docs from checked-out code. + python -I "$(command -v mike)" deploy "$MIKE_VERSION" + python -I "$(command -v mike)" set-default "$MIKE_VERSION" - name: Upload site - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: docs-site path: site/ diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3ab9c6d4..c3b4cfbf 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -15,6 +15,10 @@ on: - 'dependabot/**' workflow_dispatch: +permissions: + # Read repository contents for checkout and dependency resolution only. + contents: read + env: # Please make sure this version is included in the `matrix`, as the # `matrix` section can't use `env`, so it must be entered manually @@ -43,7 +47,7 @@ jobs: steps: - name: Run nox - uses: frequenz-floss/gh-action-nox@v1.1.0 + uses: frequenz-floss/gh-action-nox@e1351cf45e05e85afc1c79ab883e06322892d34c # v1.1.0 with: python-version: ${{ matrix.python }} nox-session: ${{ matrix.nox-session }} @@ -59,6 +63,8 @@ jobs: # We skip this job only if nox was also skipped if: always() && needs.nox.result != 'skipped' runs-on: ubuntu-slim + # Drop token permissions: this job only checks matrix status from `needs`. + permissions: {} env: DEPS_RESULT: ${{ needs.nox.result }} steps: @@ -74,24 +80,24 @@ jobs: steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 - name: Fetch sources - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.2 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: build - name: Build the source and binary distribution - run: python -m build + run: python -Im build - name: Upload distribution files - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: dist-packages path: dist/ @@ -113,13 +119,13 @@ jobs: steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 - name: Print environment (debug) run: env - name: Download package - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist-packages path: dist @@ -139,13 +145,13 @@ jobs: > pyproject.toml - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.2 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ matrix.python }} dependencies: dist/*.whl - name: Print installed packages (debug) - run: python -m pip freeze + run: python -Im pip freeze # This job runs if all the `test-installation` matrix jobs ran and succeeded. # It is only used to have a single job that we can require in branch @@ -158,6 +164,8 @@ jobs: # We skip this job only if test-installation was also skipped if: always() && needs.test-installation.result != 'skipped' runs-on: ubuntu-slim + # Drop token permissions: this job only checks matrix status from `needs`. + permissions: {} env: DEPS_RESULT: ${{ needs.test-installation.result }} steps: @@ -170,15 +178,15 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 - name: Fetch sources - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.2 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: .[dev-mkdocs] @@ -187,11 +195,14 @@ jobs: env: MIKE_VERSION: gh-${{ github.job }} run: | - mike deploy $MIKE_VERSION - mike set-default $MIKE_VERSION + # mike is installed as a console script, not a runnable module. + # Run the installed script under isolated mode to avoid importing from + # the workspace when building docs from checked-out code. + python -I "$(command -v mike)" deploy "$MIKE_VERSION" + python -I "$(command -v mike)" set-default "$MIKE_VERSION" - name: Upload site - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: docs-site path: site/ @@ -203,18 +214,19 @@ jobs: if: github.event_name == 'push' runs-on: ubuntu-24.04 permissions: + # Push generated documentation updates to the `gh-pages` branch. contents: write steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 - name: Fetch sources - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.2 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: .[dev-mkdocs] @@ -227,7 +239,7 @@ jobs: GIT_REF: ${{ github.ref }} GIT_SHA: ${{ github.sha }} run: | - python -m frequenz.repo.config.cli.version.mike.info + python -Im frequenz.repo.config.cli.version.mike.info - name: Fetch the gh-pages branch if: steps.mike-version.outputs.version @@ -248,13 +260,23 @@ jobs: GIT_REF: ${{ github.ref }} GIT_SHA: ${{ github.sha }} run: | - mike deploy --update-aliases --title "$TITLE" "$VERSION" $ALIASES + # Collect aliases into an array to avoid accidental (or malicious) + # shell injection when passing them to mike. + aliases=() + if test -n "$ALIASES"; then + read -r -a aliases <<<"$ALIASES" + fi + # mike is installed as a console script, not a runnable module. + # Run the installed script under isolated mode to avoid importing from + # the workspace when building docs from checked-out code. + python -I "$(command -v mike)" \ + deploy --update-aliases --title "$TITLE" "$VERSION" "${aliases[@]}" - name: Sort site versions if: steps.mike-version.outputs.version run: | git checkout gh-pages - python -m frequenz.repo.config.cli.version.mike.sort versions.json + python -Im frequenz.repo.config.cli.version.mike.sort versions.json git commit -a -m "Sort versions.json" - name: Publish site @@ -268,14 +290,12 @@ jobs: # Create a release only on tags creation if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') permissions: - # We need write permissions on contents to create GitHub releases and on - # discussions to create the release announcement in the discussion forums + # Create GitHub releases and upload distribution artifacts. contents: write - discussions: write runs-on: ubuntu-slim steps: - name: Download distribution files - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist-packages path: dist @@ -297,14 +317,14 @@ jobs: - name: Create GitHub release run: | set -ux - extra_opts= - if echo "$REF_NAME" | grep -- -; then extra_opts=" --prerelease"; fi + extra_opts=() + if echo "$REF_NAME" | grep -- -; then extra_opts+=(--prerelease); fi gh release create \ -R "$REPOSITORY" \ --notes-file RELEASE_NOTES.md \ --generate-notes \ - $extra_opts \ - $REF_NAME \ + "${extra_opts[@]}" \ + "$REF_NAME" \ dist/* env: REF_NAME: ${{ github.ref_name }} @@ -321,10 +341,10 @@ jobs: id-token: write steps: - name: Download distribution files - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist-packages path: dist - name: Publish the Python distribution to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 diff --git a/.github/workflows/dco-merge-queue.yml b/.github/workflows/dco-merge-queue.yml index d9597ad0..7a4260de 100644 --- a/.github/workflows/dco-merge-queue.yml +++ b/.github/workflows/dco-merge-queue.yml @@ -3,6 +3,9 @@ name: DCO on: merge_group: +# Drop all token permissions: this workflow only runs a local echo command. +permissions: {} + jobs: DCO: runs-on: ubuntu-slim diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index c327e7f2..393ddfc2 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -5,7 +5,9 @@ on: [pull_request_target] jobs: Label: permissions: + # Read the labeler configuration from the repository. contents: read + # Add labels to pull requests. pull-requests: write runs-on: ubuntu-slim steps: diff --git a/.github/workflows/release-notes-check.yml b/.github/workflows/release-notes-check.yml index 0f1250af..e42c9edb 100644 --- a/.github/workflows/release-notes-check.yml +++ b/.github/workflows/release-notes-check.yml @@ -18,6 +18,7 @@ jobs: name: Check release notes are updated runs-on: ubuntu-slim permissions: + # Read pull request metadata to evaluate labels and changed files. pull-requests: read steps: - name: Check for a release notes update diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 30292845..5f567d32 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -13,7 +13,7 @@ All upgrading should be done via the migration script or regenerating the templates. ```bash -curl -sSL https://raw.githubusercontent.com/frequenz-floss/frequenz-repo-config-python//cookiecutter/migrate.py | python3 +curl -sSLf https://raw.githubusercontent.com/frequenz-floss/frequenz-repo-config-python//cookiecutter/migrate.py | python3 -I ``` But you might still need to adapt your code: diff --git a/cookiecutter/migrate.py b/cookiecutter/migrate.py index b684dc69..dd5b01fa 100644 --- a/cookiecutter/migrate.py +++ b/cookiecutter/migrate.py @@ -10,7 +10,7 @@ To run it, the simplest way is to fetch it from GitHub and run it directly: - curl -sSL https://raw.githubusercontent.com/frequenz-floss/frequenz-repo-config-python//cookiecutter/migrate.py | python3 + curl -sSLf https://raw.githubusercontent.com/frequenz-floss/frequenz-repo-config-python//cookiecutter/migrate.py | python3 -I Make sure to replace the `` to the version you want to migrate to in the URL. @@ -26,19 +26,48 @@ import hashlib import json import os +import re import subprocess import sys import tempfile +import textwrap from pathlib import Path from typing import Any, SupportsIndex _manual_steps: list[str] = [] # pylint: disable=invalid-name +_RELEASE_NOTES_CHECK_REPLACEMENTS = [ + ( + " permissions:\n pull-requests: read\n", + " permissions:\n" + " # Read pull request metadata to evaluate labels and changed files.\n" + " pull-requests: read\n", + ), + ( + " runs-on: ubuntu-slim\n steps:\n", + " runs-on: ubuntu-slim\n" + " permissions:\n" + " # Read pull request metadata to evaluate labels and changed files.\n" + " pull-requests: read\n" + " steps:\n", + ), +] + + def main() -> None: """Run the migration steps.""" # Add a separation line like this one after each migration step. print("=" * 72) + print("Updating generated CI workflows...") + migrate_ci_workflows() + print("=" * 72) + print("Updating generated Dependabot workflows...") + migrate_dependabot_workflows() + print("=" * 72) + print("Updating auxiliary GitHub workflows...") + migrate_auxiliary_workflows() + print("=" * 72) print() if _manual_steps: @@ -61,6 +90,403 @@ def main() -> None: print() +def migrate_ci_workflows() -> None: + """Update the generated CI workflows to the latest template.""" + _migrate_workflow_file( + Path(".github/workflows/ci-pr.yaml"), + [ + ( + "on:\n pull_request:\n\nenv:\n", + "on:\n pull_request:\n\npermissions:\n" + " # Read repository contents for checkout and dependency " + "resolution only.\n" + " contents: read\n\nenv:\n", + ), + ( + " run: |\n" + " mike deploy $MIKE_VERSION\n" + " mike set-default $MIKE_VERSION\n", + " run: |\n" + " # mike is installed as a console script, not a " + "runnable module.\n" + " # Run the installed script under isolated mode to " + "avoid importing from\n" + " # the workspace when building docs from checked-out " + "code.\n" + ' python -I "$(command -v mike)" deploy "$MIKE_VERSION"\n' + ' python -I "$(command -v mike)" set-default "$MIKE_VERSION"\n', + ), + ], + description="updated CI pull-request workflow", + ) + + _migrate_workflow_file( + Path(".github/workflows/ci.yaml"), + [ + ( + " workflow_dispatch:\n\nenv:\n", + " workflow_dispatch:\n\npermissions:\n" + " # Read repository contents for checkout and dependency " + "resolution only.\n" + " contents: read\n\nenv:\n", + ), + ( + " if: always() && needs.nox.result != 'skipped'\n" + " runs-on: ubuntu-slim\n" + " env:\n", + " if: always() && needs.nox.result != 'skipped'\n" + " runs-on: ubuntu-slim\n" + " # Drop token permissions: this job only checks matrix " + "status from `needs`.\n" + " permissions: {}\n" + " env:\n", + ), + (" run: python -m build\n", " run: python -Im build\n"), + ( + " run: python -m pip freeze\n", + " run: python -Im pip freeze\n", + ), + ( + " if: always() && needs.test-installation.result != 'skipped'\n" + " runs-on: ubuntu-slim\n" + " env:\n", + " if: always() && needs.test-installation.result != 'skipped'\n" + " runs-on: ubuntu-slim\n" + " # Drop token permissions: this job only checks matrix " + "status from `needs`.\n" + " permissions: {}\n" + " env:\n", + ), + ( + " run: |\n" + " mike deploy $MIKE_VERSION\n" + " mike set-default $MIKE_VERSION\n", + " run: |\n" + " # mike is installed as a console script, not a " + "runnable module.\n" + " # Run the installed script under isolated mode to " + "avoid importing from\n" + " # the workspace when building docs from checked-out " + "code.\n" + ' python -I "$(command -v mike)" deploy "$MIKE_VERSION"\n' + ' python -I "$(command -v mike)" set-default "$MIKE_VERSION"\n', + ), + ( + " permissions:\n contents: write\n", + " permissions:\n" + " # Push generated documentation updates to the `gh-pages` " + "branch.\n" + " contents: write\n", + ), + ( + " python -m frequenz.repo.config.cli.version.mike.info\n", + " python -Im frequenz.repo.config.cli.version.mike.info\n", + ), + ( + " run: |\n" + ' mike deploy --update-aliases --title "$TITLE" ' + '"$VERSION" $ALIASES\n', + " run: |\n" + " # Collect aliases into an array to avoid accidental " + "(or malicious)\n" + " # shell injection when passing them to mike.\n" + " aliases=()\n" + ' if test -n "$ALIASES"; then\n' + ' read -r -a aliases <<<"$ALIASES"\n' + " fi\n" + " # mike is installed as a console script, not a " + "runnable module.\n" + " # Run the installed script under isolated mode to " + "avoid importing from\n" + " # the workspace when building docs from checked-out " + "code.\n" + ' python -I "$(command -v mike)" \\\n' + ' deploy --update-aliases --title "$TITLE" ' + '"$VERSION" "${aliases[@]}"\n', + ), + ( + " python -m frequenz.repo.config.cli.version.mike.sort " + "versions.json\n", + " python -Im frequenz.repo.config.cli.version.mike.sort " + "versions.json\n", + ), + ( + " permissions:\n" + " # We need write permissions on contents to create GitHub " + "releases and on\n" + " # discussions to create the release announcement in the " + "discussion forums\n" + " contents: write\n" + " discussions: write\n", + " permissions:\n" + " # Create GitHub releases and upload distribution " + "artifacts.\n" + " contents: write\n", + ), + ( + " extra_opts=\n" + ' if echo "$REF_NAME" | grep -- -; then ' + 'extra_opts=" --prerelease"; fi\n' + " gh release create \\\n" + ' -R "$REPOSITORY" \\\n' + " --notes-file RELEASE_NOTES.md \\\n" + " --generate-notes \\\n" + " $extra_opts \\\n" + " $REF_NAME \\\n" + " dist/*\n", + " extra_opts=()\n" + ' if echo "$REF_NAME" | grep -- -; then ' + "extra_opts+=(--prerelease); fi\n" + " gh release create \\\n" + ' -R "$REPOSITORY" \\\n' + " --notes-file RELEASE_NOTES.md \\\n" + " --generate-notes \\\n" + ' "${extra_opts[@]}" \\\n' + ' "$REF_NAME" \\\n' + " dist/*\n", + ), + ], + description="updated main CI workflow", + ) + + +def migrate_dependabot_workflows() -> None: + """Update the generated Dependabot automation workflows.""" + _migrate_workflow_file( + Path(".github/workflows/auto-dependabot.yaml"), + [ + ( + "permissions:\n contents: read\n pull-requests: write\n", + "permissions:\n" + " # Read repository contents and Dependabot metadata used by " + "the nested action.\n" + " contents: read\n" + " # The nested action also uses `github.token` internally for " + "PR operations.\n" + " pull-requests: write\n", + ), + ( + " with:\n" + " app-id: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_ID }}\n" + " private-key: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_PRIVATE_KEY }}\n", + " with:\n" + " app-id: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_ID }}\n" + " private-key: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_PRIVATE_KEY }}\n" + " # Merge Dependabot PRs.\n" + " permission-contents: write\n" + " # Create the auto-merged label if it does not exist.\n" + " permission-issues: write\n" + " # Approve PRs, add labels, and enable auto-merge.\n" + " permission-pull-requests: write\n", + ), + ], + description="updated Dependabot auto-merge workflow", + ) + + _migrate_workflow_file( + Path(".github/workflows/repo-config-migration.yaml"), + [ + ( + "permissions:\n" + " contents: write\n" + " issues: write\n" + " pull-requests: write\n", + "permissions:\n" + " # Commit migration changes back to the PR branch.\n" + " contents: write\n" + " # Create and normalize migration state labels.\n" + " issues: write\n" + " # Read/update pull request metadata and comments.\n" + " pull-requests: write\n", + ), + ( + " with:\n" + " app-id: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_ID }}\n" + " private-key: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_PRIVATE_KEY }}\n", + " with:\n" + " app-id: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_ID }}\n" + " private-key: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_PRIVATE_KEY }}\n" + " # Push migration commits to the PR branch.\n" + " permission-contents: write\n" + " # Manage labels when auto-merging patch-only updates.\n" + " permission-issues: write\n" + " # Approve pull requests and enable auto-merge.\n" + " permission-pull-requests: write\n" + " # Allow pushes when migration changes workflow files.\n" + " permission-workflows: write\n", + ), + ], + description="updated repo-config migration workflow", + ) + + +def migrate_auxiliary_workflows() -> None: + """Update the remaining generated GitHub workflows.""" + _migrate_workflow_file( + Path(".github/workflows/dco-merge-queue.yml"), + [ + ( + "on:\n merge_group:\n\njobs:\n", + "on:\n merge_group:\n\n" + "# Drop all token permissions: this workflow only runs a local " + "echo command.\n" + "permissions: {}\n\njobs:\n", + ), + ], + description="updated DCO merge queue workflow", + ) + + _migrate_workflow_file( + Path(".github/workflows/labeler.yml"), + [ + ( + " permissions:\n contents: read\n pull-requests: write\n", + " permissions:\n" + " # Read the labeler configuration from the repository.\n" + " contents: read\n" + " # Add labels to pull requests.\n" + " pull-requests: write\n", + ), + ], + description="updated labeler workflow", + ) + + _migrate_workflow_file( + Path(".github/workflows/release-notes-check.yml"), + _RELEASE_NOTES_CHECK_REPLACEMENTS, + description="updated release notes check workflow", + ) + + +_WORKFLOW_ACTION_PINS: dict[str, str] = { + "frequenz-floss/gh-action-setup-git": ( + "16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0" + ), + "actions/checkout": "de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2", + "yoheimuta/action-protolint": ("e62319541dc5107df5e3a5010acb8987004d3d25 # v1.3.0"), + "frequenz-floss/gh-action-nox": ( + "e1351cf45e05e85afc1c79ab883e06322892d34c # v1.1.0" + ), + "frequenz-floss/gh-action-setup-python-with-deps": ( + "0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2" + ), + "actions/upload-artifact": "bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0", + "actions/download-artifact": ("3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1"), + "pypa/gh-action-pypi-publish": ( + "ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0" + ), +} + + +def _normalize_content(content: str) -> str: + """Normalize content for stable hashing and comparisons.""" + content = content.replace("\r\n", "\n") + if not content.endswith("\n"): + content += "\n" + return content + + +def _pin_workflow_action_references(content: str) -> str: + """Pin known action references, but only when they don't use a hash yet.""" + hash_pattern = re.compile(r"[0-9a-f]{40}") + + for action, pin in _WORKFLOW_ACTION_PINS.items(): + pattern = re.compile( + rf"(^[ \t]*uses:[ \t]+){re.escape(action)}@(?P[^ \t#\n]+)" + rf"(?:[ \t]+#.*)?$", + re.MULTILINE, + ) + + def replace( + match: re.Match[str], *, action: str = action, pin: str = pin + ) -> str: + ref = match.group("ref") + if hash_pattern.fullmatch(ref): + return match.group(0) + return f"{match.group(1)}{action}@{pin}" + + content = pattern.sub(replace, content) + + return content + + +def _format_missing_patterns(patterns: list[str]) -> str: + """Format missing replacement patterns for readable error messages.""" + return "\n".join( + f"Pattern {index}:\n{textwrap.indent(pattern.rstrip(), ' ')}" + for index, pattern in enumerate(patterns, start=1) + ) + + +def _apply_idempotent_replacements( + content: str, replacements: list[tuple[str, str]] +) -> tuple[str, list[str]]: + """Apply plain-text replacements without duplicating prior migrations.""" + pending_missing_patterns: list[tuple[str, str]] = [] + + for old, new in replacements: + if new in content: + continue + if old not in content: + pending_missing_patterns.append((old, new)) + continue + content = content.replace(old, new) + + missing_patterns = [ + old for old, new in pending_missing_patterns if new not in content + ] + + return content, missing_patterns + + +def _migrate_workflow_file( + filepath: Path, + replacements: list[tuple[str, str]], + *, + description: str, +) -> None: + """Apply text replacements to a generated workflow file. + + The migration is optimized for repositories that still use the generated + workflow unchanged. It pins known action references only when they still + use tags, leaving already hashed references alone so Dependabot can update + them later. + """ + if not filepath.exists(): + manual_step( + f"{filepath} needs updating, but it was not found. Check if the " + "file was renamed or is missing and update it manually." + ) + return + + content = _normalize_content(filepath.read_text(encoding="utf-8")) + + updated = _pin_workflow_action_references(content) + updated, missing_patterns = _apply_idempotent_replacements(updated, replacements) + + if updated == content: + if not missing_patterns: + print(f" Skipped {filepath}: already has the expected updates") + return + + manual_step( + f"Could not find the expected pattern(s) in {filepath}. " + "Please compare it with the latest template and update it manually.\n" + f"{_format_missing_patterns(missing_patterns)}" + ) + return + + replace_file_atomically(filepath, updated) + print(f" Updated {filepath}: {description}") + + if missing_patterns: + manual_step( + f"Updated {filepath}, but could not find the expected pattern(s). " + "Please compare it with the latest template and complete the " + f"remaining changes manually.\n{_format_missing_patterns(missing_patterns)}" + ) + + def apply_patch(patch_content: str) -> None: """Apply a patch using the patch utility.""" subprocess.run(["patch", "-p1"], input=patch_content.encode(), check=True) diff --git a/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/auto-dependabot.yaml b/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/auto-dependabot.yaml index 94e4169d..ea543366 100644 --- a/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/auto-dependabot.yaml +++ b/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/auto-dependabot.yaml @@ -13,7 +13,9 @@ on: pull_request_target: permissions: + # Read repository contents and Dependabot metadata used by the nested action. contents: read + # The nested action also uses `github.token` internally for PR operations. pull-requests: write jobs: @@ -26,10 +28,16 @@ jobs: steps: - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: app-id: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_ID }} private-key: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_PRIVATE_KEY }} + # Merge Dependabot PRs. + permission-contents: write + # Create the auto-merged label if it does not exist. + permission-issues: write + # Approve PRs, add labels, and enable auto-merge. + permission-pull-requests: write - name: Auto-merge Dependabot PR uses: frequenz-floss/dependabot-auto-approve@e943399cc9d76fbb6d7faae446cd57301d110165 # v1.5.0 diff --git a/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/ci-pr.yaml b/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/ci-pr.yaml index 46e07c63..01640bd0 100644 --- a/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/ci-pr.yaml +++ b/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/ci-pr.yaml @@ -4,6 +4,10 @@ name: Test PR on: pull_request: +permissions: + # Read repository contents for checkout and dependency resolution only. + contents: read + env: # Please make sure this version is included in the `matrix`, as the # `matrix` section can't use `env`, so it must be entered manually @@ -19,14 +23,14 @@ jobs: steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true @@ -34,7 +38,7 @@ jobs: # Only use hashes here, as we are passing the github token, we want to # make sure updates are done consciously to avoid security issues if the # action repo gets hacked - uses: yoheimuta/action-protolint@e94cc01b1ad085ed9427098442f66f2519c723eb # v1.0.0 + uses: yoheimuta/action-protolint@e62319541dc5107df5e3a5010acb8987004d3d25 # v1.3.0 with: fail_on_error: true filter_mode: nofilter @@ -50,7 +54,7 @@ jobs: steps: - name: Run nox - uses: frequenz-floss/gh-action-nox@v1.0.0 + uses: frequenz-floss/gh-action-nox@e1351cf45e05e85afc1c79ab883e06322892d34c # v1.1.0 with: python-version: "3.11" nox-session: ci_checks_max @@ -63,19 +67,19 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: .[dev-mkdocs] @@ -84,11 +88,14 @@ jobs: env: MIKE_VERSION: gh-${{ github.job }} run: | - mike deploy $MIKE_VERSION - mike set-default $MIKE_VERSION + # mike is installed as a console script, not a runnable module. + # Run the installed script under isolated mode to avoid importing from + # the workspace when building docs from checked-out code. + python -I "$(command -v mike)" deploy "$MIKE_VERSION" + python -I "$(command -v mike)" set-default "$MIKE_VERSION" - name: Upload site - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: docs-site path: site/ diff --git a/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/ci.yaml b/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/ci.yaml index 39c4267b..69cbc657 100644 --- a/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/ci.yaml +++ b/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/ci.yaml @@ -16,6 +16,10 @@ on: - 'dependabot/**' workflow_dispatch: +permissions: + # Read repository contents for checkout and dependency resolution only. + contents: read + env: # Please make sure this version is included in the `matrix`, as the # `matrix` section can't use `env`, so it must be entered manually @@ -31,14 +35,14 @@ jobs: steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true @@ -46,7 +50,7 @@ jobs: # Only use hashes here, as we are passing the github token, we want to # make sure updates are done consciously to avoid security issues if the # action repo gets hacked - uses: yoheimuta/action-protolint@e94cc01b1ad085ed9427098442f66f2519c723eb # v1.0.0 + uses: yoheimuta/action-protolint@e62319541dc5107df5e3a5010acb8987004d3d25 # v1.3.0 with: fail_on_error: true filter_mode: nofilter @@ -76,7 +80,7 @@ jobs: steps: - name: Run nox - uses: frequenz-floss/gh-action-nox@v1.0.0 + uses: frequenz-floss/gh-action-nox@e1351cf45e05e85afc1c79ab883e06322892d34c # v1.1.0 with: python-version: ${{ matrix.python }} nox-session: ${{ matrix.nox-session }} @@ -95,6 +99,8 @@ jobs: # We skip this job only if nox was also skipped if: always() && needs.nox.result != 'skipped' runs-on: ubuntu-slim + # Drop token permissions: this job only checks matrix status from `needs`. + permissions: {} env: DEPS_RESULT: ${{ needs.nox.result }} steps: @@ -110,28 +116,28 @@ jobs: steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: build - name: Build the source and binary distribution - run: python -m build + run: python -Im build - name: Upload distribution files - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: dist-packages path: dist/ @@ -153,7 +159,7 @@ jobs: steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} @@ -163,7 +169,7 @@ jobs: run: env - name: Download package - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist-packages path: dist @@ -183,13 +189,13 @@ jobs: > pyproject.toml - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ matrix.python }} dependencies: dist/*.whl - name: Print installed packages (debug) - run: python -m pip freeze + run: python -Im pip freeze # This job runs if all the `test-installation` matrix jobs ran and succeeded. # It is only used to have a single job that we can require in branch @@ -202,6 +208,8 @@ jobs: # We skip this job only if test-installation was also skipped if: always() && needs.test-installation.result != 'skipped' runs-on: ubuntu-slim + # Drop token permissions: this job only checks matrix status from `needs`. + permissions: {} env: DEPS_RESULT: ${{ needs.test-installation.result }} steps: @@ -214,19 +222,19 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: .[dev-mkdocs] @@ -235,11 +243,14 @@ jobs: env: MIKE_VERSION: gh-${{ github.job }} run: | - mike deploy $MIKE_VERSION - mike set-default $MIKE_VERSION + # mike is installed as a console script, not a runnable module. + # Run the installed script under isolated mode to avoid importing from + # the workspace when building docs from checked-out code. + python -I "$(command -v mike)" deploy "$MIKE_VERSION" + python -I "$(command -v mike)" set-default "$MIKE_VERSION" - name: Upload site - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: docs-site path: site/ @@ -251,22 +262,23 @@ jobs: if: github.event_name == 'push' runs-on: ubuntu-24.04 permissions: + # Push generated documentation updates to the `gh-pages` branch. contents: write steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: .[dev-mkdocs] @@ -279,7 +291,7 @@ jobs: GIT_REF: ${{ github.ref }} GIT_SHA: ${{ github.sha }} run: | - python -m frequenz.repo.config.cli.version.mike.info + python -Im frequenz.repo.config.cli.version.mike.info - name: Fetch the gh-pages branch if: steps.mike-version.outputs.version @@ -300,13 +312,23 @@ jobs: GIT_REF: ${{ github.ref }} GIT_SHA: ${{ github.sha }} run: | - mike deploy --update-aliases --title "$TITLE" "$VERSION" $ALIASES + # Collect aliases into an array to avoid accidental (or malicious) + # shell injection when passing them to mike. + aliases=() + if test -n "$ALIASES"; then + read -r -a aliases <<<"$ALIASES" + fi + # mike is installed as a console script, not a runnable module. + # Run the installed script under isolated mode to avoid importing from + # the workspace when building docs from checked-out code. + python -I "$(command -v mike)" \ + deploy --update-aliases --title "$TITLE" "$VERSION" "${aliases[@]}" - name: Sort site versions if: steps.mike-version.outputs.version run: | git checkout gh-pages - python -m frequenz.repo.config.cli.version.mike.sort versions.json + python -Im frequenz.repo.config.cli.version.mike.sort versions.json git commit -a -m "Sort versions.json" - name: Publish site @@ -320,14 +342,12 @@ jobs: # Create a release only on tags creation if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') permissions: - # We need write permissions on contents to create GitHub releases and on - # discussions to create the release announcement in the discussion forums + # Create GitHub releases and upload distribution artifacts. contents: write - discussions: write runs-on: ubuntu-slim steps: - name: Download distribution files - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist-packages path: dist @@ -349,14 +369,14 @@ jobs: - name: Create GitHub release run: | set -ux - extra_opts= - if echo "$REF_NAME" | grep -- -; then extra_opts=" --prerelease"; fi + extra_opts=() + if echo "$REF_NAME" | grep -- -; then extra_opts+=(--prerelease); fi gh release create \ -R "$REPOSITORY" \ --notes-file RELEASE_NOTES.md \ --generate-notes \ - $extra_opts \ - $REF_NAME \ + "${extra_opts[@]}" \ + "$REF_NAME" \ dist/* env: REF_NAME: ${{ github.ref_name }} @@ -373,11 +393,11 @@ jobs: id-token: write steps: - name: Download distribution files - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist-packages path: dist - name: Publish the Python distribution to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 {%- endraw %} diff --git a/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/dco-merge-queue.yml b/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/dco-merge-queue.yml index cd596d5e..440e4b1a 100644 --- a/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/dco-merge-queue.yml +++ b/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/dco-merge-queue.yml @@ -4,6 +4,9 @@ name: DCO on: merge_group: +# Drop all token permissions: this workflow only runs a local echo command. +permissions: {} + jobs: DCO: runs-on: ubuntu-slim diff --git a/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/labeler.yml b/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/labeler.yml index b1bb1401..d2bb1b51 100644 --- a/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/labeler.yml +++ b/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/labeler.yml @@ -6,7 +6,9 @@ on: [pull_request_target] jobs: Label: permissions: + # Read the labeler configuration from the repository. contents: read + # Add labels to pull requests. pull-requests: write runs-on: ubuntu-slim steps: @@ -19,7 +21,7 @@ jobs: # only use hashes to pick the action to execute (instead of tags or branches). # For more details read: # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # 5.0.0 + uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" dot: true diff --git a/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/release-notes-check.yml b/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/release-notes-check.yml index 8e748d56..df3670a8 100644 --- a/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/release-notes-check.yml +++ b/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/release-notes-check.yml @@ -18,6 +18,7 @@ jobs: name: Check release notes are updated runs-on: ubuntu-slim permissions: + # Read pull request metadata to evaluate labels and changed files. pull-requests: read steps: - name: Check for a release notes update diff --git a/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/repo-config-migration.yaml b/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/repo-config-migration.yaml index 306828b1..9196983c 100644 --- a/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/repo-config-migration.yaml +++ b/cookiecutter/{{cookiecutter.github_repo_name}}/.github/workflows/repo-config-migration.yaml @@ -25,8 +25,11 @@ on: types: [opened, synchronize, reopened, labeled, unlabeled] permissions: + # Commit migration changes back to the PR branch. contents: write + # Create and normalize migration state labels. issues: write + # Read/update pull request metadata and comments. pull-requests: write jobs: @@ -43,10 +46,18 @@ jobs: steps: - name: Generate token id: create-app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: app-id: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_ID }} private-key: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_PRIVATE_KEY }} + # Push migration commits to the PR branch. + permission-contents: write + # Manage labels when auto-merging patch-only updates. + permission-issues: write + # Approve pull requests and enable auto-merge. + permission-pull-requests: write + # Allow pushes when migration changes workflow files. + permission-workflows: write - name: Migrate uses: frequenz-floss/gh-action-dependabot-migrate@07dc7e74726498c50726a80cc2167a04d896508f # v1.0.0 with: diff --git a/docs/user-guide/advanced-usage.md b/docs/user-guide/advanced-usage.md index 473b27da..56b27ab1 100644 --- a/docs/user-guide/advanced-usage.md +++ b/docs/user-guide/advanced-usage.md @@ -42,12 +42,16 @@ Create `.github/workflows/repo-config-migration.yaml` in your repository: name: Repo Config Migration on: + merge_group: # To allow using this as a required check for merging pull_request_target: types: [opened, synchronize, reopened, labeled, unlabeled] permissions: + # Commit migration changes back to the PR branch. contents: write + # Create and normalize migration state labels. issues: write + # Read/update pull request metadata and comments. pull-requests: write jobs: @@ -62,6 +66,14 @@ jobs: with: app-id: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_ID }} private-key: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_PRIVATE_KEY }} + # Push migration commits to the PR branch. + permission-contents: write + # Manage labels when auto-merging patch-only updates. + permission-issues: write + # Approve pull requests and enable auto-merge. + permission-pull-requests: write + # Allow pushes when migration changes workflow files. + permission-workflows: write - name: Migrate uses: frequenz-floss/gh-action-dependabot-migrate@07dc7e74726498c50726a80cc2167a04d896508f # v1.0.0 with: @@ -93,6 +105,8 @@ The key repo-config-specific settings are: when applicable). Because it is not `GITHUB_TOKEN`, API calls made with this token trigger follow-up workflows (merge queue CI, status checks, etc.). + Scope this token explicitly with `permission-*` inputs when creating it + (`contents`, `issues`, `pull-requests`, and `workflows` write). * **`migration-token`** — a token exposed to the migration script as `GH_TOKEN` / `GITHUB_TOKEN` for authenticated GitHub API calls (e.g. updating repository settings or branch rulesets). @@ -108,6 +122,8 @@ The key repo-config-specific settings are: * **`if` condition** — matches PRs with `the repo-config group` in the title, which is how [Dependabot] names PRs for the `repo-config` dependency group. +* **`merge_group` trigger** — lets you use the workflow as a required check in + repositories that gate merges through the merge queue. !!! Warning "Security" diff --git a/docs/user-guide/update-to-a-new-version.md b/docs/user-guide/update-to-a-new-version.md index 96b434d2..5307fc96 100644 --- a/docs/user-guide/update-to-a-new-version.md +++ b/docs/user-guide/update-to-a-new-version.md @@ -115,8 +115,8 @@ The script can also only migrate from one version to the next. If you are skipping versions, you will have to run the script multiple times. ```sh -curl -sSL https://raw.githubusercontent.com/frequenz-floss/frequenz-repo-config-python/{{ ref_name }}/cookiecutter/migrate.py \ - | python3 +curl -sSLf https://raw.githubusercontent.com/frequenz-floss/frequenz-repo-config-python/{{ ref_name }}/cookiecutter/migrate.py \ + | python3 -I ``` Make sure that the version (`{{ ref_name }}`) matches the diff --git a/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/auto-dependabot.yaml b/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/auto-dependabot.yaml index a6c76658..1e1d1f95 100644 --- a/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/auto-dependabot.yaml +++ b/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/auto-dependabot.yaml @@ -12,7 +12,9 @@ on: pull_request_target: permissions: + # Read repository contents and Dependabot metadata used by the nested action. contents: read + # The nested action also uses `github.token` internally for PR operations. pull-requests: write jobs: @@ -25,10 +27,16 @@ jobs: steps: - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: app-id: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_ID }} private-key: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_PRIVATE_KEY }} + # Merge Dependabot PRs. + permission-contents: write + # Create the auto-merged label if it does not exist. + permission-issues: write + # Approve PRs, add labels, and enable auto-merge. + permission-pull-requests: write - name: Auto-merge Dependabot PR uses: frequenz-floss/dependabot-auto-approve@e943399cc9d76fbb6d7faae446cd57301d110165 # v1.5.0 diff --git a/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/ci-pr.yaml b/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/ci-pr.yaml index 72c5d4a8..c2756bf7 100644 --- a/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/ci-pr.yaml +++ b/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/ci-pr.yaml @@ -3,6 +3,10 @@ name: Test PR on: pull_request: +permissions: + # Read repository contents for checkout and dependency resolution only. + contents: read + env: # Please make sure this version is included in the `matrix`, as the # `matrix` section can't use `env`, so it must be entered manually @@ -17,7 +21,7 @@ jobs: steps: - name: Run nox - uses: frequenz-floss/gh-action-nox@v1.0.0 + uses: frequenz-floss/gh-action-nox@e1351cf45e05e85afc1c79ab883e06322892d34c # v1.1.0 with: python-version: "3.11" nox-session: ci_checks_max @@ -30,19 +34,19 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: .[dev-mkdocs] @@ -51,11 +55,14 @@ jobs: env: MIKE_VERSION: gh-${{ github.job }} run: | - mike deploy $MIKE_VERSION - mike set-default $MIKE_VERSION + # mike is installed as a console script, not a runnable module. + # Run the installed script under isolated mode to avoid importing from + # the workspace when building docs from checked-out code. + python -I "$(command -v mike)" deploy "$MIKE_VERSION" + python -I "$(command -v mike)" set-default "$MIKE_VERSION" - name: Upload site - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: docs-site path: site/ diff --git a/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/ci.yaml b/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/ci.yaml index 0108d706..d9aeb2a2 100644 --- a/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/ci.yaml +++ b/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/ci.yaml @@ -15,6 +15,10 @@ on: - 'dependabot/**' workflow_dispatch: +permissions: + # Read repository contents for checkout and dependency resolution only. + contents: read + env: # Please make sure this version is included in the `matrix`, as the # `matrix` section can't use `env`, so it must be entered manually @@ -43,7 +47,7 @@ jobs: steps: - name: Run nox - uses: frequenz-floss/gh-action-nox@v1.0.0 + uses: frequenz-floss/gh-action-nox@e1351cf45e05e85afc1c79ab883e06322892d34c # v1.1.0 with: python-version: ${{ matrix.python }} nox-session: ${{ matrix.nox-session }} @@ -62,6 +66,8 @@ jobs: # We skip this job only if nox was also skipped if: always() && needs.nox.result != 'skipped' runs-on: ubuntu-slim + # Drop token permissions: this job only checks matrix status from `needs`. + permissions: {} env: DEPS_RESULT: ${{ needs.nox.result }} steps: @@ -77,28 +83,28 @@ jobs: steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: build - name: Build the source and binary distribution - run: python -m build + run: python -Im build - name: Upload distribution files - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: dist-packages path: dist/ @@ -120,7 +126,7 @@ jobs: steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} @@ -130,7 +136,7 @@ jobs: run: env - name: Download package - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist-packages path: dist @@ -150,13 +156,13 @@ jobs: > pyproject.toml - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ matrix.python }} dependencies: dist/*.whl - name: Print installed packages (debug) - run: python -m pip freeze + run: python -Im pip freeze # This job runs if all the `test-installation` matrix jobs ran and succeeded. # It is only used to have a single job that we can require in branch @@ -169,6 +175,8 @@ jobs: # We skip this job only if test-installation was also skipped if: always() && needs.test-installation.result != 'skipped' runs-on: ubuntu-slim + # Drop token permissions: this job only checks matrix status from `needs`. + permissions: {} env: DEPS_RESULT: ${{ needs.test-installation.result }} steps: @@ -181,19 +189,19 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: .[dev-mkdocs] @@ -202,11 +210,14 @@ jobs: env: MIKE_VERSION: gh-${{ github.job }} run: | - mike deploy $MIKE_VERSION - mike set-default $MIKE_VERSION + # mike is installed as a console script, not a runnable module. + # Run the installed script under isolated mode to avoid importing from + # the workspace when building docs from checked-out code. + python -I "$(command -v mike)" deploy "$MIKE_VERSION" + python -I "$(command -v mike)" set-default "$MIKE_VERSION" - name: Upload site - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: docs-site path: site/ @@ -218,22 +229,23 @@ jobs: if: github.event_name == 'push' runs-on: ubuntu-24.04 permissions: + # Push generated documentation updates to the `gh-pages` branch. contents: write steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: .[dev-mkdocs] @@ -246,7 +258,7 @@ jobs: GIT_REF: ${{ github.ref }} GIT_SHA: ${{ github.sha }} run: | - python -m frequenz.repo.config.cli.version.mike.info + python -Im frequenz.repo.config.cli.version.mike.info - name: Fetch the gh-pages branch if: steps.mike-version.outputs.version @@ -267,13 +279,23 @@ jobs: GIT_REF: ${{ github.ref }} GIT_SHA: ${{ github.sha }} run: | - mike deploy --update-aliases --title "$TITLE" "$VERSION" $ALIASES + # Collect aliases into an array to avoid accidental (or malicious) + # shell injection when passing them to mike. + aliases=() + if test -n "$ALIASES"; then + read -r -a aliases <<<"$ALIASES" + fi + # mike is installed as a console script, not a runnable module. + # Run the installed script under isolated mode to avoid importing from + # the workspace when building docs from checked-out code. + python -I "$(command -v mike)" \ + deploy --update-aliases --title "$TITLE" "$VERSION" "${aliases[@]}" - name: Sort site versions if: steps.mike-version.outputs.version run: | git checkout gh-pages - python -m frequenz.repo.config.cli.version.mike.sort versions.json + python -Im frequenz.repo.config.cli.version.mike.sort versions.json git commit -a -m "Sort versions.json" - name: Publish site @@ -287,14 +309,12 @@ jobs: # Create a release only on tags creation if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') permissions: - # We need write permissions on contents to create GitHub releases and on - # discussions to create the release announcement in the discussion forums + # Create GitHub releases and upload distribution artifacts. contents: write - discussions: write runs-on: ubuntu-slim steps: - name: Download distribution files - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist-packages path: dist @@ -316,14 +336,14 @@ jobs: - name: Create GitHub release run: | set -ux - extra_opts= - if echo "$REF_NAME" | grep -- -; then extra_opts=" --prerelease"; fi + extra_opts=() + if echo "$REF_NAME" | grep -- -; then extra_opts+=(--prerelease); fi gh release create \ -R "$REPOSITORY" \ --notes-file RELEASE_NOTES.md \ --generate-notes \ - $extra_opts \ - $REF_NAME \ + "${extra_opts[@]}" \ + "$REF_NAME" \ dist/* env: REF_NAME: ${{ github.ref_name }} @@ -340,10 +360,10 @@ jobs: id-token: write steps: - name: Download distribution files - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist-packages path: dist - name: Publish the Python distribution to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 diff --git a/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/dco-merge-queue.yml b/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/dco-merge-queue.yml index d9597ad0..7a4260de 100644 --- a/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/dco-merge-queue.yml +++ b/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/dco-merge-queue.yml @@ -3,6 +3,9 @@ name: DCO on: merge_group: +# Drop all token permissions: this workflow only runs a local echo command. +permissions: {} + jobs: DCO: runs-on: ubuntu-slim diff --git a/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/labeler.yml b/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/labeler.yml index f6692548..7ae21735 100644 --- a/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/labeler.yml +++ b/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/labeler.yml @@ -5,7 +5,9 @@ on: [pull_request_target] jobs: Label: permissions: + # Read the labeler configuration from the repository. contents: read + # Add labels to pull requests. pull-requests: write runs-on: ubuntu-slim steps: @@ -18,7 +20,7 @@ jobs: # only use hashes to pick the action to execute (instead of tags or branches). # For more details read: # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # 5.0.0 + uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" dot: true diff --git a/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/release-notes-check.yml b/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/release-notes-check.yml index 545d537a..4bf1c392 100644 --- a/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/release-notes-check.yml +++ b/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/release-notes-check.yml @@ -18,6 +18,7 @@ jobs: name: Check release notes are updated runs-on: ubuntu-slim permissions: + # Read pull request metadata to evaluate labels and changed files. pull-requests: read steps: - name: Check for a release notes update diff --git a/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/repo-config-migration.yaml b/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/repo-config-migration.yaml index 57a54c32..01b71120 100644 --- a/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/repo-config-migration.yaml +++ b/tests_golden/integration/test_cookiecutter_generation/actor/frequenz-actor-test/.github/workflows/repo-config-migration.yaml @@ -24,8 +24,11 @@ on: types: [opened, synchronize, reopened, labeled, unlabeled] permissions: + # Commit migration changes back to the PR branch. contents: write + # Create and normalize migration state labels. issues: write + # Read/update pull request metadata and comments. pull-requests: write jobs: @@ -42,10 +45,18 @@ jobs: steps: - name: Generate token id: create-app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: app-id: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_ID }} private-key: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_PRIVATE_KEY }} + # Push migration commits to the PR branch. + permission-contents: write + # Manage labels when auto-merging patch-only updates. + permission-issues: write + # Approve pull requests and enable auto-merge. + permission-pull-requests: write + # Allow pushes when migration changes workflow files. + permission-workflows: write - name: Migrate uses: frequenz-floss/gh-action-dependabot-migrate@07dc7e74726498c50726a80cc2167a04d896508f # v1.0.0 with: diff --git a/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/auto-dependabot.yaml b/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/auto-dependabot.yaml index a6c76658..1e1d1f95 100644 --- a/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/auto-dependabot.yaml +++ b/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/auto-dependabot.yaml @@ -12,7 +12,9 @@ on: pull_request_target: permissions: + # Read repository contents and Dependabot metadata used by the nested action. contents: read + # The nested action also uses `github.token` internally for PR operations. pull-requests: write jobs: @@ -25,10 +27,16 @@ jobs: steps: - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: app-id: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_ID }} private-key: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_PRIVATE_KEY }} + # Merge Dependabot PRs. + permission-contents: write + # Create the auto-merged label if it does not exist. + permission-issues: write + # Approve PRs, add labels, and enable auto-merge. + permission-pull-requests: write - name: Auto-merge Dependabot PR uses: frequenz-floss/dependabot-auto-approve@e943399cc9d76fbb6d7faae446cd57301d110165 # v1.5.0 diff --git a/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/ci-pr.yaml b/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/ci-pr.yaml index 232a3e7f..6a9a997e 100644 --- a/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/ci-pr.yaml +++ b/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/ci-pr.yaml @@ -3,6 +3,10 @@ name: Test PR on: pull_request: +permissions: + # Read repository contents for checkout and dependency resolution only. + contents: read + env: # Please make sure this version is included in the `matrix`, as the # `matrix` section can't use `env`, so it must be entered manually @@ -17,14 +21,14 @@ jobs: steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true @@ -32,7 +36,7 @@ jobs: # Only use hashes here, as we are passing the github token, we want to # make sure updates are done consciously to avoid security issues if the # action repo gets hacked - uses: yoheimuta/action-protolint@e94cc01b1ad085ed9427098442f66f2519c723eb # v1.0.0 + uses: yoheimuta/action-protolint@e62319541dc5107df5e3a5010acb8987004d3d25 # v1.3.0 with: fail_on_error: true filter_mode: nofilter @@ -47,7 +51,7 @@ jobs: steps: - name: Run nox - uses: frequenz-floss/gh-action-nox@v1.0.0 + uses: frequenz-floss/gh-action-nox@e1351cf45e05e85afc1c79ab883e06322892d34c # v1.1.0 with: python-version: "3.11" nox-session: ci_checks_max @@ -60,19 +64,19 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: .[dev-mkdocs] @@ -81,11 +85,14 @@ jobs: env: MIKE_VERSION: gh-${{ github.job }} run: | - mike deploy $MIKE_VERSION - mike set-default $MIKE_VERSION + # mike is installed as a console script, not a runnable module. + # Run the installed script under isolated mode to avoid importing from + # the workspace when building docs from checked-out code. + python -I "$(command -v mike)" deploy "$MIKE_VERSION" + python -I "$(command -v mike)" set-default "$MIKE_VERSION" - name: Upload site - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: docs-site path: site/ diff --git a/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/ci.yaml b/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/ci.yaml index 060c331a..f67a45e1 100644 --- a/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/ci.yaml +++ b/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/ci.yaml @@ -15,6 +15,10 @@ on: - 'dependabot/**' workflow_dispatch: +permissions: + # Read repository contents for checkout and dependency resolution only. + contents: read + env: # Please make sure this version is included in the `matrix`, as the # `matrix` section can't use `env`, so it must be entered manually @@ -29,14 +33,14 @@ jobs: steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v3 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true @@ -44,7 +48,7 @@ jobs: # Only use hashes here, as we are passing the github token, we want to # make sure updates are done consciously to avoid security issues if the # action repo gets hacked - uses: yoheimuta/action-protolint@e94cc01b1ad085ed9427098442f66f2519c723eb # v1.0.0 + uses: yoheimuta/action-protolint@e62319541dc5107df5e3a5010acb8987004d3d25 # v1.3.0 with: fail_on_error: true filter_mode: nofilter @@ -73,7 +77,7 @@ jobs: steps: - name: Run nox - uses: frequenz-floss/gh-action-nox@v1.0.0 + uses: frequenz-floss/gh-action-nox@e1351cf45e05e85afc1c79ab883e06322892d34c # v1.1.0 with: python-version: ${{ matrix.python }} nox-session: ${{ matrix.nox-session }} @@ -92,6 +96,8 @@ jobs: # We skip this job only if nox was also skipped if: always() && needs.nox.result != 'skipped' runs-on: ubuntu-slim + # Drop token permissions: this job only checks matrix status from `needs`. + permissions: {} env: DEPS_RESULT: ${{ needs.nox.result }} steps: @@ -107,28 +113,28 @@ jobs: steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: build - name: Build the source and binary distribution - run: python -m build + run: python -Im build - name: Upload distribution files - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: dist-packages path: dist/ @@ -150,7 +156,7 @@ jobs: steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} @@ -160,7 +166,7 @@ jobs: run: env - name: Download package - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist-packages path: dist @@ -180,13 +186,13 @@ jobs: > pyproject.toml - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ matrix.python }} dependencies: dist/*.whl - name: Print installed packages (debug) - run: python -m pip freeze + run: python -Im pip freeze # This job runs if all the `test-installation` matrix jobs ran and succeeded. # It is only used to have a single job that we can require in branch @@ -199,6 +205,8 @@ jobs: # We skip this job only if test-installation was also skipped if: always() && needs.test-installation.result != 'skipped' runs-on: ubuntu-slim + # Drop token permissions: this job only checks matrix status from `needs`. + permissions: {} env: DEPS_RESULT: ${{ needs.test-installation.result }} steps: @@ -211,19 +219,19 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: .[dev-mkdocs] @@ -232,11 +240,14 @@ jobs: env: MIKE_VERSION: gh-${{ github.job }} run: | - mike deploy $MIKE_VERSION - mike set-default $MIKE_VERSION + # mike is installed as a console script, not a runnable module. + # Run the installed script under isolated mode to avoid importing from + # the workspace when building docs from checked-out code. + python -I "$(command -v mike)" deploy "$MIKE_VERSION" + python -I "$(command -v mike)" set-default "$MIKE_VERSION" - name: Upload site - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: docs-site path: site/ @@ -248,22 +259,23 @@ jobs: if: github.event_name == 'push' runs-on: ubuntu-24.04 permissions: + # Push generated documentation updates to the `gh-pages` branch. contents: write steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: .[dev-mkdocs] @@ -276,7 +288,7 @@ jobs: GIT_REF: ${{ github.ref }} GIT_SHA: ${{ github.sha }} run: | - python -m frequenz.repo.config.cli.version.mike.info + python -Im frequenz.repo.config.cli.version.mike.info - name: Fetch the gh-pages branch if: steps.mike-version.outputs.version @@ -297,13 +309,23 @@ jobs: GIT_REF: ${{ github.ref }} GIT_SHA: ${{ github.sha }} run: | - mike deploy --update-aliases --title "$TITLE" "$VERSION" $ALIASES + # Collect aliases into an array to avoid accidental (or malicious) + # shell injection when passing them to mike. + aliases=() + if test -n "$ALIASES"; then + read -r -a aliases <<<"$ALIASES" + fi + # mike is installed as a console script, not a runnable module. + # Run the installed script under isolated mode to avoid importing from + # the workspace when building docs from checked-out code. + python -I "$(command -v mike)" \ + deploy --update-aliases --title "$TITLE" "$VERSION" "${aliases[@]}" - name: Sort site versions if: steps.mike-version.outputs.version run: | git checkout gh-pages - python -m frequenz.repo.config.cli.version.mike.sort versions.json + python -Im frequenz.repo.config.cli.version.mike.sort versions.json git commit -a -m "Sort versions.json" - name: Publish site @@ -317,14 +339,12 @@ jobs: # Create a release only on tags creation if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') permissions: - # We need write permissions on contents to create GitHub releases and on - # discussions to create the release announcement in the discussion forums + # Create GitHub releases and upload distribution artifacts. contents: write - discussions: write runs-on: ubuntu-slim steps: - name: Download distribution files - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist-packages path: dist @@ -346,14 +366,14 @@ jobs: - name: Create GitHub release run: | set -ux - extra_opts= - if echo "$REF_NAME" | grep -- -; then extra_opts=" --prerelease"; fi + extra_opts=() + if echo "$REF_NAME" | grep -- -; then extra_opts+=(--prerelease); fi gh release create \ -R "$REPOSITORY" \ --notes-file RELEASE_NOTES.md \ --generate-notes \ - $extra_opts \ - $REF_NAME \ + "${extra_opts[@]}" \ + "$REF_NAME" \ dist/* env: REF_NAME: ${{ github.ref_name }} @@ -370,10 +390,10 @@ jobs: id-token: write steps: - name: Download distribution files - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist-packages path: dist - name: Publish the Python distribution to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 diff --git a/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/dco-merge-queue.yml b/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/dco-merge-queue.yml index d9597ad0..7a4260de 100644 --- a/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/dco-merge-queue.yml +++ b/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/dco-merge-queue.yml @@ -3,6 +3,9 @@ name: DCO on: merge_group: +# Drop all token permissions: this workflow only runs a local echo command. +permissions: {} + jobs: DCO: runs-on: ubuntu-slim diff --git a/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/labeler.yml b/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/labeler.yml index f6692548..7ae21735 100644 --- a/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/labeler.yml +++ b/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/labeler.yml @@ -5,7 +5,9 @@ on: [pull_request_target] jobs: Label: permissions: + # Read the labeler configuration from the repository. contents: read + # Add labels to pull requests. pull-requests: write runs-on: ubuntu-slim steps: @@ -18,7 +20,7 @@ jobs: # only use hashes to pick the action to execute (instead of tags or branches). # For more details read: # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # 5.0.0 + uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" dot: true diff --git a/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/release-notes-check.yml b/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/release-notes-check.yml index 2515b127..ad0f6aa7 100644 --- a/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/release-notes-check.yml +++ b/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/release-notes-check.yml @@ -18,6 +18,7 @@ jobs: name: Check release notes are updated runs-on: ubuntu-slim permissions: + # Read pull request metadata to evaluate labels and changed files. pull-requests: read steps: - name: Check for a release notes update diff --git a/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/repo-config-migration.yaml b/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/repo-config-migration.yaml index 57a54c32..01b71120 100644 --- a/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/repo-config-migration.yaml +++ b/tests_golden/integration/test_cookiecutter_generation/api/frequenz-api-test/.github/workflows/repo-config-migration.yaml @@ -24,8 +24,11 @@ on: types: [opened, synchronize, reopened, labeled, unlabeled] permissions: + # Commit migration changes back to the PR branch. contents: write + # Create and normalize migration state labels. issues: write + # Read/update pull request metadata and comments. pull-requests: write jobs: @@ -42,10 +45,18 @@ jobs: steps: - name: Generate token id: create-app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: app-id: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_ID }} private-key: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_PRIVATE_KEY }} + # Push migration commits to the PR branch. + permission-contents: write + # Manage labels when auto-merging patch-only updates. + permission-issues: write + # Approve pull requests and enable auto-merge. + permission-pull-requests: write + # Allow pushes when migration changes workflow files. + permission-workflows: write - name: Migrate uses: frequenz-floss/gh-action-dependabot-migrate@07dc7e74726498c50726a80cc2167a04d896508f # v1.0.0 with: diff --git a/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/auto-dependabot.yaml b/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/auto-dependabot.yaml index a6c76658..1e1d1f95 100644 --- a/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/auto-dependabot.yaml +++ b/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/auto-dependabot.yaml @@ -12,7 +12,9 @@ on: pull_request_target: permissions: + # Read repository contents and Dependabot metadata used by the nested action. contents: read + # The nested action also uses `github.token` internally for PR operations. pull-requests: write jobs: @@ -25,10 +27,16 @@ jobs: steps: - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: app-id: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_ID }} private-key: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_PRIVATE_KEY }} + # Merge Dependabot PRs. + permission-contents: write + # Create the auto-merged label if it does not exist. + permission-issues: write + # Approve PRs, add labels, and enable auto-merge. + permission-pull-requests: write - name: Auto-merge Dependabot PR uses: frequenz-floss/dependabot-auto-approve@e943399cc9d76fbb6d7faae446cd57301d110165 # v1.5.0 diff --git a/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/ci-pr.yaml b/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/ci-pr.yaml index 72c5d4a8..c2756bf7 100644 --- a/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/ci-pr.yaml +++ b/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/ci-pr.yaml @@ -3,6 +3,10 @@ name: Test PR on: pull_request: +permissions: + # Read repository contents for checkout and dependency resolution only. + contents: read + env: # Please make sure this version is included in the `matrix`, as the # `matrix` section can't use `env`, so it must be entered manually @@ -17,7 +21,7 @@ jobs: steps: - name: Run nox - uses: frequenz-floss/gh-action-nox@v1.0.0 + uses: frequenz-floss/gh-action-nox@e1351cf45e05e85afc1c79ab883e06322892d34c # v1.1.0 with: python-version: "3.11" nox-session: ci_checks_max @@ -30,19 +34,19 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: .[dev-mkdocs] @@ -51,11 +55,14 @@ jobs: env: MIKE_VERSION: gh-${{ github.job }} run: | - mike deploy $MIKE_VERSION - mike set-default $MIKE_VERSION + # mike is installed as a console script, not a runnable module. + # Run the installed script under isolated mode to avoid importing from + # the workspace when building docs from checked-out code. + python -I "$(command -v mike)" deploy "$MIKE_VERSION" + python -I "$(command -v mike)" set-default "$MIKE_VERSION" - name: Upload site - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: docs-site path: site/ diff --git a/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/ci.yaml b/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/ci.yaml index 0108d706..d9aeb2a2 100644 --- a/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/ci.yaml +++ b/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/ci.yaml @@ -15,6 +15,10 @@ on: - 'dependabot/**' workflow_dispatch: +permissions: + # Read repository contents for checkout and dependency resolution only. + contents: read + env: # Please make sure this version is included in the `matrix`, as the # `matrix` section can't use `env`, so it must be entered manually @@ -43,7 +47,7 @@ jobs: steps: - name: Run nox - uses: frequenz-floss/gh-action-nox@v1.0.0 + uses: frequenz-floss/gh-action-nox@e1351cf45e05e85afc1c79ab883e06322892d34c # v1.1.0 with: python-version: ${{ matrix.python }} nox-session: ${{ matrix.nox-session }} @@ -62,6 +66,8 @@ jobs: # We skip this job only if nox was also skipped if: always() && needs.nox.result != 'skipped' runs-on: ubuntu-slim + # Drop token permissions: this job only checks matrix status from `needs`. + permissions: {} env: DEPS_RESULT: ${{ needs.nox.result }} steps: @@ -77,28 +83,28 @@ jobs: steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: build - name: Build the source and binary distribution - run: python -m build + run: python -Im build - name: Upload distribution files - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: dist-packages path: dist/ @@ -120,7 +126,7 @@ jobs: steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} @@ -130,7 +136,7 @@ jobs: run: env - name: Download package - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist-packages path: dist @@ -150,13 +156,13 @@ jobs: > pyproject.toml - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ matrix.python }} dependencies: dist/*.whl - name: Print installed packages (debug) - run: python -m pip freeze + run: python -Im pip freeze # This job runs if all the `test-installation` matrix jobs ran and succeeded. # It is only used to have a single job that we can require in branch @@ -169,6 +175,8 @@ jobs: # We skip this job only if test-installation was also skipped if: always() && needs.test-installation.result != 'skipped' runs-on: ubuntu-slim + # Drop token permissions: this job only checks matrix status from `needs`. + permissions: {} env: DEPS_RESULT: ${{ needs.test-installation.result }} steps: @@ -181,19 +189,19 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: .[dev-mkdocs] @@ -202,11 +210,14 @@ jobs: env: MIKE_VERSION: gh-${{ github.job }} run: | - mike deploy $MIKE_VERSION - mike set-default $MIKE_VERSION + # mike is installed as a console script, not a runnable module. + # Run the installed script under isolated mode to avoid importing from + # the workspace when building docs from checked-out code. + python -I "$(command -v mike)" deploy "$MIKE_VERSION" + python -I "$(command -v mike)" set-default "$MIKE_VERSION" - name: Upload site - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: docs-site path: site/ @@ -218,22 +229,23 @@ jobs: if: github.event_name == 'push' runs-on: ubuntu-24.04 permissions: + # Push generated documentation updates to the `gh-pages` branch. contents: write steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: .[dev-mkdocs] @@ -246,7 +258,7 @@ jobs: GIT_REF: ${{ github.ref }} GIT_SHA: ${{ github.sha }} run: | - python -m frequenz.repo.config.cli.version.mike.info + python -Im frequenz.repo.config.cli.version.mike.info - name: Fetch the gh-pages branch if: steps.mike-version.outputs.version @@ -267,13 +279,23 @@ jobs: GIT_REF: ${{ github.ref }} GIT_SHA: ${{ github.sha }} run: | - mike deploy --update-aliases --title "$TITLE" "$VERSION" $ALIASES + # Collect aliases into an array to avoid accidental (or malicious) + # shell injection when passing them to mike. + aliases=() + if test -n "$ALIASES"; then + read -r -a aliases <<<"$ALIASES" + fi + # mike is installed as a console script, not a runnable module. + # Run the installed script under isolated mode to avoid importing from + # the workspace when building docs from checked-out code. + python -I "$(command -v mike)" \ + deploy --update-aliases --title "$TITLE" "$VERSION" "${aliases[@]}" - name: Sort site versions if: steps.mike-version.outputs.version run: | git checkout gh-pages - python -m frequenz.repo.config.cli.version.mike.sort versions.json + python -Im frequenz.repo.config.cli.version.mike.sort versions.json git commit -a -m "Sort versions.json" - name: Publish site @@ -287,14 +309,12 @@ jobs: # Create a release only on tags creation if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') permissions: - # We need write permissions on contents to create GitHub releases and on - # discussions to create the release announcement in the discussion forums + # Create GitHub releases and upload distribution artifacts. contents: write - discussions: write runs-on: ubuntu-slim steps: - name: Download distribution files - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist-packages path: dist @@ -316,14 +336,14 @@ jobs: - name: Create GitHub release run: | set -ux - extra_opts= - if echo "$REF_NAME" | grep -- -; then extra_opts=" --prerelease"; fi + extra_opts=() + if echo "$REF_NAME" | grep -- -; then extra_opts+=(--prerelease); fi gh release create \ -R "$REPOSITORY" \ --notes-file RELEASE_NOTES.md \ --generate-notes \ - $extra_opts \ - $REF_NAME \ + "${extra_opts[@]}" \ + "$REF_NAME" \ dist/* env: REF_NAME: ${{ github.ref_name }} @@ -340,10 +360,10 @@ jobs: id-token: write steps: - name: Download distribution files - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist-packages path: dist - name: Publish the Python distribution to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 diff --git a/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/dco-merge-queue.yml b/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/dco-merge-queue.yml index d9597ad0..7a4260de 100644 --- a/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/dco-merge-queue.yml +++ b/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/dco-merge-queue.yml @@ -3,6 +3,9 @@ name: DCO on: merge_group: +# Drop all token permissions: this workflow only runs a local echo command. +permissions: {} + jobs: DCO: runs-on: ubuntu-slim diff --git a/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/labeler.yml b/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/labeler.yml index f6692548..7ae21735 100644 --- a/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/labeler.yml +++ b/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/labeler.yml @@ -5,7 +5,9 @@ on: [pull_request_target] jobs: Label: permissions: + # Read the labeler configuration from the repository. contents: read + # Add labels to pull requests. pull-requests: write runs-on: ubuntu-slim steps: @@ -18,7 +20,7 @@ jobs: # only use hashes to pick the action to execute (instead of tags or branches). # For more details read: # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # 5.0.0 + uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" dot: true diff --git a/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/release-notes-check.yml b/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/release-notes-check.yml index 545d537a..4bf1c392 100644 --- a/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/release-notes-check.yml +++ b/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/release-notes-check.yml @@ -18,6 +18,7 @@ jobs: name: Check release notes are updated runs-on: ubuntu-slim permissions: + # Read pull request metadata to evaluate labels and changed files. pull-requests: read steps: - name: Check for a release notes update diff --git a/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/repo-config-migration.yaml b/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/repo-config-migration.yaml index 57a54c32..01b71120 100644 --- a/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/repo-config-migration.yaml +++ b/tests_golden/integration/test_cookiecutter_generation/app/frequenz-app-test/.github/workflows/repo-config-migration.yaml @@ -24,8 +24,11 @@ on: types: [opened, synchronize, reopened, labeled, unlabeled] permissions: + # Commit migration changes back to the PR branch. contents: write + # Create and normalize migration state labels. issues: write + # Read/update pull request metadata and comments. pull-requests: write jobs: @@ -42,10 +45,18 @@ jobs: steps: - name: Generate token id: create-app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: app-id: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_ID }} private-key: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_PRIVATE_KEY }} + # Push migration commits to the PR branch. + permission-contents: write + # Manage labels when auto-merging patch-only updates. + permission-issues: write + # Approve pull requests and enable auto-merge. + permission-pull-requests: write + # Allow pushes when migration changes workflow files. + permission-workflows: write - name: Migrate uses: frequenz-floss/gh-action-dependabot-migrate@07dc7e74726498c50726a80cc2167a04d896508f # v1.0.0 with: diff --git a/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/auto-dependabot.yaml b/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/auto-dependabot.yaml index a6c76658..1e1d1f95 100644 --- a/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/auto-dependabot.yaml +++ b/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/auto-dependabot.yaml @@ -12,7 +12,9 @@ on: pull_request_target: permissions: + # Read repository contents and Dependabot metadata used by the nested action. contents: read + # The nested action also uses `github.token` internally for PR operations. pull-requests: write jobs: @@ -25,10 +27,16 @@ jobs: steps: - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: app-id: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_ID }} private-key: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_PRIVATE_KEY }} + # Merge Dependabot PRs. + permission-contents: write + # Create the auto-merged label if it does not exist. + permission-issues: write + # Approve PRs, add labels, and enable auto-merge. + permission-pull-requests: write - name: Auto-merge Dependabot PR uses: frequenz-floss/dependabot-auto-approve@e943399cc9d76fbb6d7faae446cd57301d110165 # v1.5.0 diff --git a/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/ci-pr.yaml b/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/ci-pr.yaml index 72c5d4a8..c2756bf7 100644 --- a/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/ci-pr.yaml +++ b/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/ci-pr.yaml @@ -3,6 +3,10 @@ name: Test PR on: pull_request: +permissions: + # Read repository contents for checkout and dependency resolution only. + contents: read + env: # Please make sure this version is included in the `matrix`, as the # `matrix` section can't use `env`, so it must be entered manually @@ -17,7 +21,7 @@ jobs: steps: - name: Run nox - uses: frequenz-floss/gh-action-nox@v1.0.0 + uses: frequenz-floss/gh-action-nox@e1351cf45e05e85afc1c79ab883e06322892d34c # v1.1.0 with: python-version: "3.11" nox-session: ci_checks_max @@ -30,19 +34,19 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: .[dev-mkdocs] @@ -51,11 +55,14 @@ jobs: env: MIKE_VERSION: gh-${{ github.job }} run: | - mike deploy $MIKE_VERSION - mike set-default $MIKE_VERSION + # mike is installed as a console script, not a runnable module. + # Run the installed script under isolated mode to avoid importing from + # the workspace when building docs from checked-out code. + python -I "$(command -v mike)" deploy "$MIKE_VERSION" + python -I "$(command -v mike)" set-default "$MIKE_VERSION" - name: Upload site - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: docs-site path: site/ diff --git a/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/ci.yaml b/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/ci.yaml index 0108d706..d9aeb2a2 100644 --- a/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/ci.yaml +++ b/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/ci.yaml @@ -15,6 +15,10 @@ on: - 'dependabot/**' workflow_dispatch: +permissions: + # Read repository contents for checkout and dependency resolution only. + contents: read + env: # Please make sure this version is included in the `matrix`, as the # `matrix` section can't use `env`, so it must be entered manually @@ -43,7 +47,7 @@ jobs: steps: - name: Run nox - uses: frequenz-floss/gh-action-nox@v1.0.0 + uses: frequenz-floss/gh-action-nox@e1351cf45e05e85afc1c79ab883e06322892d34c # v1.1.0 with: python-version: ${{ matrix.python }} nox-session: ${{ matrix.nox-session }} @@ -62,6 +66,8 @@ jobs: # We skip this job only if nox was also skipped if: always() && needs.nox.result != 'skipped' runs-on: ubuntu-slim + # Drop token permissions: this job only checks matrix status from `needs`. + permissions: {} env: DEPS_RESULT: ${{ needs.nox.result }} steps: @@ -77,28 +83,28 @@ jobs: steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: build - name: Build the source and binary distribution - run: python -m build + run: python -Im build - name: Upload distribution files - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: dist-packages path: dist/ @@ -120,7 +126,7 @@ jobs: steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} @@ -130,7 +136,7 @@ jobs: run: env - name: Download package - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist-packages path: dist @@ -150,13 +156,13 @@ jobs: > pyproject.toml - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ matrix.python }} dependencies: dist/*.whl - name: Print installed packages (debug) - run: python -m pip freeze + run: python -Im pip freeze # This job runs if all the `test-installation` matrix jobs ran and succeeded. # It is only used to have a single job that we can require in branch @@ -169,6 +175,8 @@ jobs: # We skip this job only if test-installation was also skipped if: always() && needs.test-installation.result != 'skipped' runs-on: ubuntu-slim + # Drop token permissions: this job only checks matrix status from `needs`. + permissions: {} env: DEPS_RESULT: ${{ needs.test-installation.result }} steps: @@ -181,19 +189,19 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: .[dev-mkdocs] @@ -202,11 +210,14 @@ jobs: env: MIKE_VERSION: gh-${{ github.job }} run: | - mike deploy $MIKE_VERSION - mike set-default $MIKE_VERSION + # mike is installed as a console script, not a runnable module. + # Run the installed script under isolated mode to avoid importing from + # the workspace when building docs from checked-out code. + python -I "$(command -v mike)" deploy "$MIKE_VERSION" + python -I "$(command -v mike)" set-default "$MIKE_VERSION" - name: Upload site - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: docs-site path: site/ @@ -218,22 +229,23 @@ jobs: if: github.event_name == 'push' runs-on: ubuntu-24.04 permissions: + # Push generated documentation updates to the `gh-pages` branch. contents: write steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: .[dev-mkdocs] @@ -246,7 +258,7 @@ jobs: GIT_REF: ${{ github.ref }} GIT_SHA: ${{ github.sha }} run: | - python -m frequenz.repo.config.cli.version.mike.info + python -Im frequenz.repo.config.cli.version.mike.info - name: Fetch the gh-pages branch if: steps.mike-version.outputs.version @@ -267,13 +279,23 @@ jobs: GIT_REF: ${{ github.ref }} GIT_SHA: ${{ github.sha }} run: | - mike deploy --update-aliases --title "$TITLE" "$VERSION" $ALIASES + # Collect aliases into an array to avoid accidental (or malicious) + # shell injection when passing them to mike. + aliases=() + if test -n "$ALIASES"; then + read -r -a aliases <<<"$ALIASES" + fi + # mike is installed as a console script, not a runnable module. + # Run the installed script under isolated mode to avoid importing from + # the workspace when building docs from checked-out code. + python -I "$(command -v mike)" \ + deploy --update-aliases --title "$TITLE" "$VERSION" "${aliases[@]}" - name: Sort site versions if: steps.mike-version.outputs.version run: | git checkout gh-pages - python -m frequenz.repo.config.cli.version.mike.sort versions.json + python -Im frequenz.repo.config.cli.version.mike.sort versions.json git commit -a -m "Sort versions.json" - name: Publish site @@ -287,14 +309,12 @@ jobs: # Create a release only on tags creation if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') permissions: - # We need write permissions on contents to create GitHub releases and on - # discussions to create the release announcement in the discussion forums + # Create GitHub releases and upload distribution artifacts. contents: write - discussions: write runs-on: ubuntu-slim steps: - name: Download distribution files - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist-packages path: dist @@ -316,14 +336,14 @@ jobs: - name: Create GitHub release run: | set -ux - extra_opts= - if echo "$REF_NAME" | grep -- -; then extra_opts=" --prerelease"; fi + extra_opts=() + if echo "$REF_NAME" | grep -- -; then extra_opts+=(--prerelease); fi gh release create \ -R "$REPOSITORY" \ --notes-file RELEASE_NOTES.md \ --generate-notes \ - $extra_opts \ - $REF_NAME \ + "${extra_opts[@]}" \ + "$REF_NAME" \ dist/* env: REF_NAME: ${{ github.ref_name }} @@ -340,10 +360,10 @@ jobs: id-token: write steps: - name: Download distribution files - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist-packages path: dist - name: Publish the Python distribution to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 diff --git a/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/dco-merge-queue.yml b/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/dco-merge-queue.yml index d9597ad0..7a4260de 100644 --- a/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/dco-merge-queue.yml +++ b/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/dco-merge-queue.yml @@ -3,6 +3,9 @@ name: DCO on: merge_group: +# Drop all token permissions: this workflow only runs a local echo command. +permissions: {} + jobs: DCO: runs-on: ubuntu-slim diff --git a/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/labeler.yml b/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/labeler.yml index f6692548..7ae21735 100644 --- a/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/labeler.yml +++ b/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/labeler.yml @@ -5,7 +5,9 @@ on: [pull_request_target] jobs: Label: permissions: + # Read the labeler configuration from the repository. contents: read + # Add labels to pull requests. pull-requests: write runs-on: ubuntu-slim steps: @@ -18,7 +20,7 @@ jobs: # only use hashes to pick the action to execute (instead of tags or branches). # For more details read: # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # 5.0.0 + uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" dot: true diff --git a/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/release-notes-check.yml b/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/release-notes-check.yml index 545d537a..4bf1c392 100644 --- a/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/release-notes-check.yml +++ b/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/release-notes-check.yml @@ -18,6 +18,7 @@ jobs: name: Check release notes are updated runs-on: ubuntu-slim permissions: + # Read pull request metadata to evaluate labels and changed files. pull-requests: read steps: - name: Check for a release notes update diff --git a/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/repo-config-migration.yaml b/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/repo-config-migration.yaml index 57a54c32..01b71120 100644 --- a/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/repo-config-migration.yaml +++ b/tests_golden/integration/test_cookiecutter_generation/lib/frequenz-test-python/.github/workflows/repo-config-migration.yaml @@ -24,8 +24,11 @@ on: types: [opened, synchronize, reopened, labeled, unlabeled] permissions: + # Commit migration changes back to the PR branch. contents: write + # Create and normalize migration state labels. issues: write + # Read/update pull request metadata and comments. pull-requests: write jobs: @@ -42,10 +45,18 @@ jobs: steps: - name: Generate token id: create-app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: app-id: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_ID }} private-key: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_PRIVATE_KEY }} + # Push migration commits to the PR branch. + permission-contents: write + # Manage labels when auto-merging patch-only updates. + permission-issues: write + # Approve pull requests and enable auto-merge. + permission-pull-requests: write + # Allow pushes when migration changes workflow files. + permission-workflows: write - name: Migrate uses: frequenz-floss/gh-action-dependabot-migrate@07dc7e74726498c50726a80cc2167a04d896508f # v1.0.0 with: diff --git a/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/auto-dependabot.yaml b/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/auto-dependabot.yaml index a6c76658..1e1d1f95 100644 --- a/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/auto-dependabot.yaml +++ b/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/auto-dependabot.yaml @@ -12,7 +12,9 @@ on: pull_request_target: permissions: + # Read repository contents and Dependabot metadata used by the nested action. contents: read + # The nested action also uses `github.token` internally for PR operations. pull-requests: write jobs: @@ -25,10 +27,16 @@ jobs: steps: - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: app-id: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_ID }} private-key: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_PRIVATE_KEY }} + # Merge Dependabot PRs. + permission-contents: write + # Create the auto-merged label if it does not exist. + permission-issues: write + # Approve PRs, add labels, and enable auto-merge. + permission-pull-requests: write - name: Auto-merge Dependabot PR uses: frequenz-floss/dependabot-auto-approve@e943399cc9d76fbb6d7faae446cd57301d110165 # v1.5.0 diff --git a/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/ci-pr.yaml b/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/ci-pr.yaml index 72c5d4a8..c2756bf7 100644 --- a/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/ci-pr.yaml +++ b/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/ci-pr.yaml @@ -3,6 +3,10 @@ name: Test PR on: pull_request: +permissions: + # Read repository contents for checkout and dependency resolution only. + contents: read + env: # Please make sure this version is included in the `matrix`, as the # `matrix` section can't use `env`, so it must be entered manually @@ -17,7 +21,7 @@ jobs: steps: - name: Run nox - uses: frequenz-floss/gh-action-nox@v1.0.0 + uses: frequenz-floss/gh-action-nox@e1351cf45e05e85afc1c79ab883e06322892d34c # v1.1.0 with: python-version: "3.11" nox-session: ci_checks_max @@ -30,19 +34,19 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: .[dev-mkdocs] @@ -51,11 +55,14 @@ jobs: env: MIKE_VERSION: gh-${{ github.job }} run: | - mike deploy $MIKE_VERSION - mike set-default $MIKE_VERSION + # mike is installed as a console script, not a runnable module. + # Run the installed script under isolated mode to avoid importing from + # the workspace when building docs from checked-out code. + python -I "$(command -v mike)" deploy "$MIKE_VERSION" + python -I "$(command -v mike)" set-default "$MIKE_VERSION" - name: Upload site - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: docs-site path: site/ diff --git a/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/ci.yaml b/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/ci.yaml index 0108d706..d9aeb2a2 100644 --- a/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/ci.yaml +++ b/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/ci.yaml @@ -15,6 +15,10 @@ on: - 'dependabot/**' workflow_dispatch: +permissions: + # Read repository contents for checkout and dependency resolution only. + contents: read + env: # Please make sure this version is included in the `matrix`, as the # `matrix` section can't use `env`, so it must be entered manually @@ -43,7 +47,7 @@ jobs: steps: - name: Run nox - uses: frequenz-floss/gh-action-nox@v1.0.0 + uses: frequenz-floss/gh-action-nox@e1351cf45e05e85afc1c79ab883e06322892d34c # v1.1.0 with: python-version: ${{ matrix.python }} nox-session: ${{ matrix.nox-session }} @@ -62,6 +66,8 @@ jobs: # We skip this job only if nox was also skipped if: always() && needs.nox.result != 'skipped' runs-on: ubuntu-slim + # Drop token permissions: this job only checks matrix status from `needs`. + permissions: {} env: DEPS_RESULT: ${{ needs.nox.result }} steps: @@ -77,28 +83,28 @@ jobs: steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: build - name: Build the source and binary distribution - run: python -m build + run: python -Im build - name: Upload distribution files - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: dist-packages path: dist/ @@ -120,7 +126,7 @@ jobs: steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} @@ -130,7 +136,7 @@ jobs: run: env - name: Download package - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist-packages path: dist @@ -150,13 +156,13 @@ jobs: > pyproject.toml - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ matrix.python }} dependencies: dist/*.whl - name: Print installed packages (debug) - run: python -m pip freeze + run: python -Im pip freeze # This job runs if all the `test-installation` matrix jobs ran and succeeded. # It is only used to have a single job that we can require in branch @@ -169,6 +175,8 @@ jobs: # We skip this job only if test-installation was also skipped if: always() && needs.test-installation.result != 'skipped' runs-on: ubuntu-slim + # Drop token permissions: this job only checks matrix status from `needs`. + permissions: {} env: DEPS_RESULT: ${{ needs.test-installation.result }} steps: @@ -181,19 +189,19 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: .[dev-mkdocs] @@ -202,11 +210,14 @@ jobs: env: MIKE_VERSION: gh-${{ github.job }} run: | - mike deploy $MIKE_VERSION - mike set-default $MIKE_VERSION + # mike is installed as a console script, not a runnable module. + # Run the installed script under isolated mode to avoid importing from + # the workspace when building docs from checked-out code. + python -I "$(command -v mike)" deploy "$MIKE_VERSION" + python -I "$(command -v mike)" set-default "$MIKE_VERSION" - name: Upload site - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: docs-site path: site/ @@ -218,22 +229,23 @@ jobs: if: github.event_name == 'push' runs-on: ubuntu-24.04 permissions: + # Push generated documentation updates to the `gh-pages` branch. contents: write steps: - name: Setup Git - uses: frequenz-floss/gh-action-setup-git@v1.0.0 + uses: frequenz-floss/gh-action-setup-git@16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0 # TODO(cookiecutter): Uncomment this for projects with private dependencies # with: # username: ${{ secrets.GIT_USER }} # password: ${{ secrets.GIT_PASS }} - name: Fetch sources - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Setup Python - uses: frequenz-floss/gh-action-setup-python-with-deps@v1.0.0 + uses: frequenz-floss/gh-action-setup-python-with-deps@0d0d77eac3b54799f31f25a1060ef2c6ebdf9299 # v1.0.2 with: python-version: ${{ env.DEFAULT_PYTHON_VERSION }} dependencies: .[dev-mkdocs] @@ -246,7 +258,7 @@ jobs: GIT_REF: ${{ github.ref }} GIT_SHA: ${{ github.sha }} run: | - python -m frequenz.repo.config.cli.version.mike.info + python -Im frequenz.repo.config.cli.version.mike.info - name: Fetch the gh-pages branch if: steps.mike-version.outputs.version @@ -267,13 +279,23 @@ jobs: GIT_REF: ${{ github.ref }} GIT_SHA: ${{ github.sha }} run: | - mike deploy --update-aliases --title "$TITLE" "$VERSION" $ALIASES + # Collect aliases into an array to avoid accidental (or malicious) + # shell injection when passing them to mike. + aliases=() + if test -n "$ALIASES"; then + read -r -a aliases <<<"$ALIASES" + fi + # mike is installed as a console script, not a runnable module. + # Run the installed script under isolated mode to avoid importing from + # the workspace when building docs from checked-out code. + python -I "$(command -v mike)" \ + deploy --update-aliases --title "$TITLE" "$VERSION" "${aliases[@]}" - name: Sort site versions if: steps.mike-version.outputs.version run: | git checkout gh-pages - python -m frequenz.repo.config.cli.version.mike.sort versions.json + python -Im frequenz.repo.config.cli.version.mike.sort versions.json git commit -a -m "Sort versions.json" - name: Publish site @@ -287,14 +309,12 @@ jobs: # Create a release only on tags creation if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') permissions: - # We need write permissions on contents to create GitHub releases and on - # discussions to create the release announcement in the discussion forums + # Create GitHub releases and upload distribution artifacts. contents: write - discussions: write runs-on: ubuntu-slim steps: - name: Download distribution files - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist-packages path: dist @@ -316,14 +336,14 @@ jobs: - name: Create GitHub release run: | set -ux - extra_opts= - if echo "$REF_NAME" | grep -- -; then extra_opts=" --prerelease"; fi + extra_opts=() + if echo "$REF_NAME" | grep -- -; then extra_opts+=(--prerelease); fi gh release create \ -R "$REPOSITORY" \ --notes-file RELEASE_NOTES.md \ --generate-notes \ - $extra_opts \ - $REF_NAME \ + "${extra_opts[@]}" \ + "$REF_NAME" \ dist/* env: REF_NAME: ${{ github.ref_name }} @@ -340,10 +360,10 @@ jobs: id-token: write steps: - name: Download distribution files - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: dist-packages path: dist - name: Publish the Python distribution to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 diff --git a/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/dco-merge-queue.yml b/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/dco-merge-queue.yml index d9597ad0..7a4260de 100644 --- a/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/dco-merge-queue.yml +++ b/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/dco-merge-queue.yml @@ -3,6 +3,9 @@ name: DCO on: merge_group: +# Drop all token permissions: this workflow only runs a local echo command. +permissions: {} + jobs: DCO: runs-on: ubuntu-slim diff --git a/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/labeler.yml b/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/labeler.yml index f6692548..7ae21735 100644 --- a/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/labeler.yml +++ b/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/labeler.yml @@ -5,7 +5,9 @@ on: [pull_request_target] jobs: Label: permissions: + # Read the labeler configuration from the repository. contents: read + # Add labels to pull requests. pull-requests: write runs-on: ubuntu-slim steps: @@ -18,7 +20,7 @@ jobs: # only use hashes to pick the action to execute (instead of tags or branches). # For more details read: # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # 5.0.0 + uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" dot: true diff --git a/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/release-notes-check.yml b/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/release-notes-check.yml index 545d537a..4bf1c392 100644 --- a/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/release-notes-check.yml +++ b/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/release-notes-check.yml @@ -18,6 +18,7 @@ jobs: name: Check release notes are updated runs-on: ubuntu-slim permissions: + # Read pull request metadata to evaluate labels and changed files. pull-requests: read steps: - name: Check for a release notes update diff --git a/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/repo-config-migration.yaml b/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/repo-config-migration.yaml index 57a54c32..01b71120 100644 --- a/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/repo-config-migration.yaml +++ b/tests_golden/integration/test_cookiecutter_generation/model/frequenz-model-test/.github/workflows/repo-config-migration.yaml @@ -24,8 +24,11 @@ on: types: [opened, synchronize, reopened, labeled, unlabeled] permissions: + # Commit migration changes back to the PR branch. contents: write + # Create and normalize migration state labels. issues: write + # Read/update pull request metadata and comments. pull-requests: write jobs: @@ -42,10 +45,18 @@ jobs: steps: - name: Generate token id: create-app-token - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: app-id: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_ID }} private-key: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_PRIVATE_KEY }} + # Push migration commits to the PR branch. + permission-contents: write + # Manage labels when auto-merging patch-only updates. + permission-issues: write + # Approve pull requests and enable auto-merge. + permission-pull-requests: write + # Allow pushes when migration changes workflow files. + permission-workflows: write - name: Migrate uses: frequenz-floss/gh-action-dependabot-migrate@07dc7e74726498c50726a80cc2167a04d896508f # v1.0.0 with: