From c7d79315e6dfc72834452ef500584301005b9d0b Mon Sep 17 00:00:00 2001 From: Brad Keryan Date: Mon, 18 May 2026 13:19:09 -0500 Subject: [PATCH] ni/python-actions: Enable static analysis with zizmor --- .github/workflows/CI.yml | 7 + .github/workflows/PR.yml | 6 +- .github/workflows/check_actions.yml | 21 +++ .../workflows/sync_github_issues_to_azdo.yml | 2 + .github/workflows/test_actions.yml | 20 +++ analyze-project/README.md | 4 +- analyze-project/action.yml | 32 +++- check-project-version/action.yml | 54 ++++--- setup-poetry/action.yml | 10 +- setup-python/action.yml | 7 +- update-project-version/action.yml | 152 ++++++++++-------- 11 files changed, 208 insertions(+), 107 deletions(-) create mode 100644 .github/workflows/check_actions.yml diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 3019963..6f48358 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -8,7 +8,14 @@ on: workflow_call: workflow_dispatch: +permissions: {} + jobs: + check_actions: + name: Check actions + uses: ./.github/workflows/check_actions.yml + permissions: + security-events: write test_actions: name: Test actions uses: ./.github/workflows/test_actions.yml diff --git a/.github/workflows/PR.yml b/.github/workflows/PR.yml index 3d28ed8..1522824 100644 --- a/.github/workflows/PR.yml +++ b/.github/workflows/PR.yml @@ -8,6 +8,8 @@ on: workflow_call: workflow_dispatch: +permissions: {} + concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true @@ -15,4 +17,6 @@ concurrency: jobs: run_ci: name: Run CI - uses: ./.github/workflows/CI.yml \ No newline at end of file + uses: ./.github/workflows/CI.yml + permissions: + security-events: write diff --git a/.github/workflows/check_actions.yml b/.github/workflows/check_actions.yml new file mode 100644 index 0000000..3b9bb48 --- /dev/null +++ b/.github/workflows/check_actions.yml @@ -0,0 +1,21 @@ +name: Check actions + +on: + workflow_call: + workflow_dispatch: + +permissions: {} + +jobs: + zizmor: + name: Run zizmor + runs-on: ubuntu-latest + permissions: + security-events: write + steps: + - name: Check out repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Run zizmor + uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3 diff --git a/.github/workflows/sync_github_issues_to_azdo.yml b/.github/workflows/sync_github_issues_to_azdo.yml index c1f0e55..1cb0db6 100644 --- a/.github/workflows/sync_github_issues_to_azdo.yml +++ b/.github/workflows/sync_github_issues_to_azdo.yml @@ -8,6 +8,8 @@ on: issue_comment: types: [created, edited, deleted] +permissions: {} + jobs: alert: if: ${{ !github.event.issue.pull_request && github.event.issue.title != 'Dependency Dashboard' }} diff --git a/.github/workflows/test_actions.yml b/.github/workflows/test_actions.yml index 6b7abd3..b284e86 100644 --- a/.github/workflows/test_actions.yml +++ b/.github/workflows/test_actions.yml @@ -4,6 +4,8 @@ on: workflow_call: workflow_dispatch: +permissions: {} + jobs: test_setup_python: name: Test setup-python @@ -16,6 +18,8 @@ jobs: steps: - name: Check out repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Python uses: ./setup-python with: @@ -51,6 +55,8 @@ jobs: steps: - name: Check out repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Python uses: ./setup-python with: @@ -78,6 +84,8 @@ jobs: steps: - name: Check out repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Python uses: ./setup-python with: @@ -110,6 +118,8 @@ jobs: steps: - name: Check out repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Python uses: ./setup-python with: @@ -139,6 +149,8 @@ jobs: steps: - name: Check out repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Python uses: ./setup-python - name: Set up Poetry @@ -173,6 +185,8 @@ jobs: steps: - name: Check out repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Python uses: ./setup-python - name: Set up Poetry @@ -225,6 +239,8 @@ jobs: steps: - name: Check out repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Python uses: ./setup-python with: @@ -324,6 +340,8 @@ jobs: steps: - name: Check out repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Python uses: ./setup-python with: @@ -382,6 +400,8 @@ jobs: steps: - name: Check out repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Python uses: ./setup-python with: diff --git a/analyze-project/README.md b/analyze-project/README.md index 91655b8..8dca54d 100644 --- a/analyze-project/README.md +++ b/analyze-project/README.md @@ -42,7 +42,9 @@ If there are extra command-line arguments you need to install from your pyproject.toml, specify them with this input. You can specify any arguments that work with `poetry install` including `--extras` and `--with`. These `install-args` will be appended to the basic command line which is `poetry -install -v`. For example, +install -v`. Do not pass untrusted user input. + +For example, ```yaml - uses: ni/python-actions/analyze-project@v0 diff --git a/analyze-project/action.yml b/analyze-project/action.yml index 6eafb4f..1319a89 100644 --- a/analyze-project/action.yml +++ b/analyze-project/action.yml @@ -1,6 +1,6 @@ name: Analyze project description: > - This workflow analyzes the code quality of a Python project using various + This action analyzes the code quality of a Python project using various linters and type checkers including ni-python-styleguide, mypy (if the 'mypy' package is installed), and pyright (if the 'pyright' package is installed). @@ -11,7 +11,9 @@ inputs: default: ${{ github.workspace }} install-args: # E.g. "--extras 'drivers addons' --with examples,docs" - description: 'Extra arguments. Install command will be "poetry install ".' + description: > + Extra arguments. Install command will be "poetry install -v ". + Do not pass untrusted user input. default: '' required: false type: string @@ -19,6 +21,12 @@ inputs: runs: using: composite steps: + - name: Validate event type + if: ${{ github.event_name == 'pull_request_target' || github.event_name == 'workflow_run' }} + run: | + echo "::error title=Analyze Project Error::Unsupported event '$GITHUB_EVENT_NAME'" + exit 1 + shell: bash - name: Get project info id: get_project_info run: | @@ -36,14 +44,17 @@ runs: shell: bash working-directory: ${{ inputs.project-directory }} - name: Check for lock changes - run: poetry check --lock -C "${{ inputs.project-directory }}" + run: poetry check --lock shell: bash + working-directory: ${{ inputs.project-directory }} - name: Generate install args hash id: install_args_hash run: | - install_args_hash=$(echo "${{ inputs.install-args }}" | sha256sum | cut -d ' ' -f1) + install_args_hash=$(echo "$INSTALL_ARGS" | sha256sum | cut -d ' ' -f1) echo "hash=$install_args_hash" >> "$GITHUB_OUTPUT" shell: bash + env: + INSTALL_ARGS: ${{ inputs.install-args }} - name: Cache virtualenv uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: @@ -51,10 +62,12 @@ runs: key: ${{ steps.get_project_info.outputs.name }}-${{ runner.os }}-py${{ env.pythonVersion }}-${{ hashFiles(format('{0}/poetry.lock', inputs.project-directory)) }}-${{ steps.install_args_hash.outputs.hash }} - name: Install ${{ steps.get_project_info.outputs.name }} run: | - install_cmd="poetry install -v ${{ inputs.install-args }}" + install_cmd="poetry install -v $INSTALL_ARGS" eval $install_cmd working-directory: ${{ inputs.project-directory }} shell: bash + env: + INSTALL_ARGS: ${{ inputs.install-args }} - name: Lint run: poetry run ni-python-styleguide lint working-directory: ${{ inputs.project-directory }} @@ -79,9 +92,12 @@ runs: shell: poetry run python {0} - name: Echo check_tools outputs run: | - echo "mypy installed: ${{ steps.check_tools.outputs.mypy }}" - echo "pyright installed: ${{ steps.check_tools.outputs.pyright }}" + echo "mypy installed: $MYPY_INSTALLED" + echo "pyright installed: $PYRIGHT_INSTALLED" shell: bash + env: + MYPY_INSTALLED: ${{ steps.check_tools.outputs.mypy }} + PYRIGHT_INSTALLED: ${{ steps.check_tools.outputs.pyright }} - name: Mypy static analysis if: steps.check_tools.outputs.mypy == 'true' run: poetry run mypy @@ -90,7 +106,7 @@ runs: - name: Add virtualenv to the path for pyright-action if: steps.check_tools.outputs.pyright == 'true' shell: bash - run: | + run: | # zizmor: ignore[github-env] # intentionally add project venv to the path echo "$(dirname $(poetry env info --executable))" >> $GITHUB_PATH working-directory: ${{ inputs.project-directory }} - name: Pyright static analysis diff --git a/check-project-version/action.yml b/check-project-version/action.yml index 4b7eb64..d17b483 100644 --- a/check-project-version/action.yml +++ b/check-project-version/action.yml @@ -13,33 +13,35 @@ inputs: runs: using: composite steps: - - name: Check project version - run: | - project_version="$(poetry version --short)" - expected_version="${{ inputs.expected-version }}" - # Strip the leading 'v', in case this is a GitHub release tag. - expected_version="${expected_version#v}" + - name: Check project version + run: | + project_version="$(poetry version --short)" + expected_version="$EXPECTED_VERSION" + # Strip the leading 'v', in case this is a GitHub release tag. + expected_version="${expected_version#v}" - error_message="$(cat <> "$GITHUB_ENV" - shell: bash \ No newline at end of file + run: | # zizmor: ignore[github-env] # value is queried from the installed Python interpreter, not user input + echo "pythonVersion=$PYTHON_VERSION" >> "$GITHUB_ENV" + shell: bash + env: + PYTHON_VERSION: ${{ steps.get-python-version.outputs.python-version }} diff --git a/update-project-version/action.yml b/update-project-version/action.yml index f76f201..7f9559a 100644 --- a/update-project-version/action.yml +++ b/update-project-version/action.yml @@ -30,73 +30,91 @@ inputs: runs: using: composite steps: - - name: Set variables - id: set-vars - run: | - # For release events, github.ref_name is the release tag (e.g. 1.0.0) and - # github.event.release.target_commitish is the branch/tag/commit that the - # tag was created from (e.g. main). - # - # For workflow_dispatch events, github.ref_name is the branch/tag/commit - # that the workflow was dispatched on. - base_branch="${{ github.event_name == 'release' && github.event.release.target_commitish || github.ref_name }}" - base_branch_no_slashes=$(echo $base_branch | tr '/' '-') - echo "base-branch=$base_branch" >> "$GITHUB_OUTPUT" - echo "branch-name=${{ inputs.branch-prefix }}update-project-version-$base_branch_no_slashes" >> "$GITHUB_OUTPUT" - shell: bash - - name: Update the version in pyproject.toml - run: python ${{ github.action_path }}/update_version.py ${{ inputs.version-rule }} ${{ inputs.use-dev-suffix == 'true' && '--dev' || '' }} - shell: bash - working-directory: ${{ inputs.project-directory }} - - name: Get PR details - id: get-pr-details - run: | - import pathlib, os, subprocess - changed_files = [pathlib.Path(file) for file in subprocess.run(["git", "diff", "--name-only"], capture_output=True, check=True, text=True).stdout.split()] - changed_projects = [file for file in changed_files if file.name == "pyproject.toml"] - project_info = {} - for project in changed_projects: - name, version = subprocess.run(["poetry", "version", "-C", str(project.parent)], capture_output=True, check=True, text=True).stdout.split() - project_info[project] = {"name": name, "version": version} - if len(changed_projects) == 1: - title = "chore: Update project {} to v{}".format(project_info[changed_projects[0]]["name"], project_info[changed_projects[0]]["version"]) - else: - title = "chore: Update project versions" - base_branch = "${{ steps.set-vars.outputs.base-branch }}" - if base_branch not in ["main", "master"]: - title = f"[{base_branch}] {title}" - with open(os.environ["GITHUB_OUTPUT"], "a") as output: - print(f"title={title}", file=output) - print("project-table<> "$GITHUB_OUTPUT" + echo "branch-name=${BRANCH_PREFIX}update-project-version-$base_branch_no_slashes" >> "$GITHUB_OUTPUT" + shell: bash + env: + BASE_BRANCH: ${{ github.event_name == 'release' && github.event.release.target_commitish || github.ref_name }} + BRANCH_PREFIX: ${{ inputs.branch-prefix }} + - name: Update the version in pyproject.toml + run: | + extra_args="" + if [ x"$USE_DEV_SUFFIX" = x"true" ]; then + extra_args="--dev $extra_args" + fi + python ${{ github.action_path }}/update_version.py "$VERSION_RULE" $extra_args + shell: bash + working-directory: ${{ inputs.project-directory }} + env: + VERSION_RULE: ${{ inputs.version-rule }} + USE_DEV_SUFFIX: ${{ inputs.use-dev-suffix }} + - name: Get PR details + id: get-pr-details + run: | + import pathlib, os, subprocess + changed_files = [pathlib.Path(file) for file in subprocess.run(["git", "diff", "--name-only"], capture_output=True, check=True, text=True).stdout.split()] + changed_projects = [file for file in changed_files if file.name == "pyproject.toml"] + project_info = {} + for project in changed_projects: + name, version = subprocess.run(["poetry", "version", "-C", str(project.parent)], capture_output=True, check=True, text=True).stdout.split() + project_info[project] = {"name": name, "version": version} + if len(changed_projects) == 1: + title = "chore: Update project {} to v{}".format(project_info[changed_projects[0]]["name"], project_info[changed_projects[0]]["version"]) + else: + title = "chore: Update project versions" + base_branch = os.environ["BASE_BRANCH"] + if base_branch not in ["main", "master"]: + title = f"[{base_branch}] {title}" + with open(os.environ["GITHUB_OUTPUT"], "a") as output: + print(f"title={title}", file=output) + print("project-table<> Inputs >> token](${{ env.action-url }}#token) and - [create-pull-request >> Triggering further workflow runs](https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#triggering-further-workflow-runs) - for more details. + If the checks for this pull request appear to hang, you can work around this by closing and re-opening the PR. See + [ni/python-actions/update-project-version >> Inputs >> token](${{ env.action-url }}#token) and + [create-pull-request >> Triggering further workflow runs](https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#triggering-further-workflow-runs) + for more details. - This PR was generated by [ni/python-actions/update-project-version](${{ env.action-url }}). View the [workflow log](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}). + This PR was generated by [ni/python-actions/update-project-version](${{ env.action-url }}). View the [workflow log](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}).