diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index afa1052..8db61f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,8 +69,10 @@ jobs: with: persist-credentials: false - - name: Install uv - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 + # The tests below need uv, which the python/setup action installs. Using it + # here also exercises the action itself on every run. + - name: Install Python tooling + uses: $/python/setup - name: Run uv lock update tests run: | diff --git a/README.md b/README.md index 5c7dbb3..4c3d465 100644 --- a/README.md +++ b/README.md @@ -422,218 +422,11 @@ jobs: token: ${{ github.token }} ``` -## Python Helper Scripts +## Python Actions -These scripts are opinionated helper scripts for Python releases. +Python helper actions have their own READMEs: -### Pre-Publish - -Bump the version and create a new tag. Verify the tag. -Push the commit and tag to the source branch unless `dry_run` is set. - -```yaml -- name: Setup - uses: mongodb-labs/drivers-github-tools/setup@v3 - with: - ... - -- uses: mongodb-labs/drivers-github-tools/python/pre-publishv2 - with: - version: ${{ inputs.version }} - version_bump_script: ./.github/scripts/bump-version.sh - dry_run: ${{ inputs.dry_run }} -``` - -### Post-publish - -To be run after separately publishing the [Python package](https://github.com/pypa/gh-action-pypi-publish#trusted-publishing). -Handles follow-up tasks related to publishing Python packages, including -signing `dist` files and uploading report assets to S3. -It will also push the following (dev) version to the source branch. -It will create a draft GitHub release and attach the signature files. -If `dry_run` is set, nothing will be pushed. - -The jobs should look something like: - -```yaml -publish: - name: Upload release to PyPI - runs-on: ubuntu-latest - environment: release - permissions: - id-token: write - steps: - - name: Download all the dists - uses: actions/download-artifact@v4 - with: - name: all-dist-${{ github.run_id }} - path: dist/ - - name: Publish package distributions to PyPI - if: inputs.dry_run == 'false' - uses: pypa/gh-action-pypi-publish@release/v1 - -post-publish: - needs: [publish] - name: Handle post-publish actions - runs-on: ubuntu-latest - environment: release - permissions: - id-token: write - contents: write - attestations: write - security-events: write - steps: - - name: Setup - uses: mongodb-labs/drivers-github-tools/setup@v3 - with: - ... - - - uses: mongodb-labs/drivers-github-tools/python/post-publish@v3 - with: - version: ${{ inputs.version }} - following_version: ${{ inputs.following_version }} - version_bump_script: ./.github/scripts/bump-version.sh - product_name: winkerberos - token: ${{ github.token }} - dry_run: ${{ inputs.dry_run }} -``` - -### uv Lock Update - -This action runs `uv lock --upgrade` and opens a pull request with the resulting -lock file changes. It maintains a single open pull request: a subsequent run -updates the existing one rather than opening a second. - -The caller checks out the repository and puts `uv` on `PATH`. - -```yaml -name: Update uv.lock - -on: - schedule: - - cron: "0 7 * * 1" - workflow_dispatch: - -# Runs must serialize: two at once would force push the same branch and race on -# the pull request. Keep the group static rather than keying it on the ref. -concurrency: - group: uv-lock-update - cancel-in-progress: false - -jobs: - update-lock: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - uses: astral-sh/setup-uv@v8 - - uses: mongodb-labs/drivers-github-tools/python/uv-lock-update@v3 - with: - app_id: ${{ vars.APP_ID }} - private_key: ${{ secrets.APP_PRIVATE_KEY }} -``` - -`app_id` and `private_key` are required unless `dry_run` is true. - -`base` defaults to the ref the workflow ran on, which is what a checkout with no -`ref` takes. If you check out a different ref, set `base` to match it, or the -pull request will contain every unrelated commit between the two branches. - -Every label named in `labels` must already exist in the repository, because -GitHub rejects a pull request that asks for an unknown one. - -Set `dry_run: true` to log the branch and pull request the action would have -created, without pushing or opening anything. - -The upgrade skips releases published within the last 7 days, so a broken or -compromised release has time to be yanked before it can land in the lock file. -Change the cutoff with `exclude_newer`, which takes anything uv's -`--exclude-newer` accepts: a date, an RFC 3339 timestamp, a duration such as -`30 days`, or `false` to upgrade to the newest releases with no cutoff at all. -It reaches uv as `UV_EXCLUDE_NEWER`, so it overrides an `exclude-newer` the -repository sets in `pyproject.toml` or `uv.toml`. Pass `exclude_newer: ""` to -leave that setting in charge instead. - -```yaml - - uses: mongodb-labs/drivers-github-tools/python/uv-lock-update@v3 - with: - app_id: ${{ vars.APP_ID }} - private_key: ${{ secrets.APP_PRIVATE_KEY }} - exclude_newer: 14 days -``` - -## Python Labs Helper Scripts - -These scripts are opinionated helper scripts for Python releases in MongoDB Labs. -In contrast to the regulare Python scripts, it does not generate the -SSDLC compliance assets or upload anything to S3. - -### Pre-Publish - -Create a new tag. Verify the tag. -Push the commit and tag to the source branch unless `dry_run` is set. - -```yaml -- name: Setup - uses: mongodb-labs/drivers-github-tools/setup@v3 - with: - ... - -- uses: mongodb-labs/drivers-github-tools/python-labs/pre-publishv2 - with: - version_bump_script: ./.github/scripts/bump-version.sh - dry_run: ${{ inputs.dry_run }} -``` - -### Post-publish - -To be run after separately publishing the [Python package](https://github.com/pypa/gh-action-pypi-publish#trusted-publishing). -Handles follow-up tasks related to publishing Python packages. -It will push the following (dev) version to the source branch. -It will create a draft GitHub release with generated release notes. -If `dry_run` is set, nothing will be pushed. - -The jobs should look something like: - -```yaml -publish: - name: Upload release to PyPI - runs-on: ubuntu-latest - environment: release - permissions: - id-token: write - steps: - - name: Download all the dists - uses: actions/download-artifact@v4 - with: - name: all-dist-${{ github.run_id }} - path: dist/ - - name: Publish package distributions to PyPI - if: inputs.dry_run == 'false' - uses: pypa/gh-action-pypi-publish@release/v1 - -post-publish: - needs: [publish] - name: Handle post-publish actions - runs-on: ubuntu-latest - environment: release - permissions: - id-token: write - contents: write - attestations: write - security-events: write - steps: - - name: Setup - uses: mongodb-labs/drivers-github-tools/setup@v3 - with: - ... - - - uses: mongodb-labs/drivers-github-tools/python-labs/post-publish@v3 - with: - following_version: ${{ inputs.following_version }} - version_bump_script: ./.github/scripts/bump-version.sh - product_name: python-bsonjs - token: ${{ github.token }} - dry_run: ${{ inputs.dry_run }} -``` \ No newline at end of file +- [`python/`](python/README.md) covers setup, pre-publish, post-publish, and uv + lock updates for the Python drivers. +- [`python-labs/`](python-labs/README.md) covers pre-publish and post-publish for + MongoDB Labs projects, without the SSDLC assets and S3 upload. diff --git a/python-labs/README.md b/python-labs/README.md new file mode 100644 index 0000000..a2b3051 --- /dev/null +++ b/python-labs/README.md @@ -0,0 +1,74 @@ +# Python Labs Actions + +Opinionated helper actions for Python releases in MongoDB Labs. Unlike the +[regular Python actions](../python/README.md), these do not generate the SSDLC +compliance assets or upload anything to S3. + +## Pre-Publish + +Create a new tag. Verify the tag. +Push the commit and tag to the source branch unless `dry_run` is set. + +```yaml +- name: Setup + uses: mongodb-labs/drivers-github-tools/setup@v3 + with: + ... + +- uses: mongodb-labs/drivers-github-tools/python-labs/pre-publish@v3 + with: + version_bump_script: ./.github/scripts/bump-version.sh + dry_run: ${{ inputs.dry_run }} +``` + +## Post-publish + +To be run after separately publishing the [Python package](https://github.com/pypa/gh-action-pypi-publish#trusted-publishing). +Handles follow-up tasks related to publishing Python packages. +It will push the following (dev) version to the source branch. +It will create a draft GitHub release with generated release notes. +If `dry_run` is set, nothing will be pushed. + +The jobs should look something like: + +```yaml +publish: + name: Upload release to PyPI + runs-on: ubuntu-latest + environment: release + permissions: + id-token: write + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: all-dist-${{ github.run_id }} + path: dist/ + - name: Publish package distributions to PyPI + if: inputs.dry_run == 'false' + uses: pypa/gh-action-pypi-publish@release/v1 + +post-publish: + needs: [publish] + name: Handle post-publish actions + runs-on: ubuntu-latest + environment: release + permissions: + id-token: write + contents: write + attestations: write + security-events: write + steps: + - name: Setup + uses: mongodb-labs/drivers-github-tools/setup@v3 + with: + ... + + - uses: mongodb-labs/drivers-github-tools/python-labs/post-publish@v3 + with: + following_version: ${{ inputs.following_version }} + version_bump_script: ./.github/scripts/bump-version.sh + product_name: python-bsonjs + token: ${{ github.token }} + dry_run: ${{ inputs.dry_run }} +``` diff --git a/python/README.md b/python/README.md new file mode 100644 index 0000000..1f04b4d --- /dev/null +++ b/python/README.md @@ -0,0 +1,169 @@ +# Python Actions + +Opinionated helper actions for Python CI and releases. See the +[top-level README](../README.md) for actions shared across all drivers. + +## Setup + +Puts Python, uv, and just on `PATH`, replacing the setup steps that Python driver +repos repeat in every CI job. + +```yaml +- uses: mongodb-labs/drivers-github-tools/python/setup@v3 + with: + python-version: "3.10" +``` + +Python comes from `actions/setup-python`, which is faster than having uv download +a managed interpreter. uv is then pinned to that interpreter through `UV_PYTHON`, +so the project's `pyproject.toml` and `.python-version` do not change the choice. + +The action runs no just recipes. A job that needs project dependencies runs its +own `just install` step after this one. + +The action sets no resolution policy. A repo that wants to hold back newly +published packages configures `exclude-newer` in its own `pyproject.toml` or +`uv.toml`, or commits a `uv.lock`. + +`enable-cache` defaults to `true` and is passed through to `astral-sh/setup-uv`. + +`allow-prereleases` defaults to `true`, so a Python version with no stable release +resolves to the prerelease instead of failing. Set it to `"false"` for a job that +should fail instead of testing against a beta. + +## Pre-Publish + +Bump the version and create a new tag. Verify the tag. +Push the commit and tag to the source branch unless `dry_run` is set. + +```yaml +- name: Setup + uses: mongodb-labs/drivers-github-tools/setup@v3 + with: + ... + +- uses: mongodb-labs/drivers-github-tools/python/pre-publish@v3 + with: + version: ${{ inputs.version }} + version_bump_script: ./.github/scripts/bump-version.sh + dry_run: ${{ inputs.dry_run }} +``` + +## Post-publish + +To be run after separately publishing the [Python package](https://github.com/pypa/gh-action-pypi-publish#trusted-publishing). +Handles follow-up tasks related to publishing Python packages, including +signing `dist` files and uploading report assets to S3. +It will also push the following (dev) version to the source branch. +It will create a draft GitHub release and attach the signature files. +If `dry_run` is set, nothing will be pushed. + +The jobs should look something like: + +```yaml +publish: + name: Upload release to PyPI + runs-on: ubuntu-latest + environment: release + permissions: + id-token: write + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: all-dist-${{ github.run_id }} + path: dist/ + - name: Publish package distributions to PyPI + if: inputs.dry_run == 'false' + uses: pypa/gh-action-pypi-publish@release/v1 + +post-publish: + needs: [publish] + name: Handle post-publish actions + runs-on: ubuntu-latest + environment: release + permissions: + id-token: write + contents: write + attestations: write + security-events: write + steps: + - name: Setup + uses: mongodb-labs/drivers-github-tools/setup@v3 + with: + ... + + - uses: mongodb-labs/drivers-github-tools/python/post-publish@v3 + with: + version: ${{ inputs.version }} + following_version: ${{ inputs.following_version }} + version_bump_script: ./.github/scripts/bump-version.sh + product_name: winkerberos + token: ${{ github.token }} + dry_run: ${{ inputs.dry_run }} +``` + +## uv Lock Update + +This action runs `uv lock --upgrade` and opens a pull request with the resulting +lock file changes. It maintains a single open pull request: a subsequent run +updates the existing one rather than opening a second. + +The caller checks out the repository and puts `uv` on `PATH`. + +```yaml +name: Update uv.lock + +on: + schedule: + - cron: "0 7 * * 1" + workflow_dispatch: + +# Runs must serialize: two at once would force push the same branch and race on +# the pull request. Keep the group static rather than keying it on the ref. +concurrency: + group: uv-lock-update + cancel-in-progress: false + +jobs: + update-lock: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: astral-sh/setup-uv@v8 + - uses: mongodb-labs/drivers-github-tools/python/uv-lock-update@v3 + with: + app_id: ${{ vars.APP_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} +``` + +`app_id` and `private_key` are required unless `dry_run` is true. + +`base` defaults to the ref the workflow ran on, which is what a checkout with no +`ref` takes. If you check out a different ref, set `base` to match it, or the +pull request will contain every unrelated commit between the two branches. + +Every label named in `labels` must already exist in the repository, because +GitHub rejects a pull request that asks for an unknown one. + +Set `dry_run: true` to log the branch and pull request the action would have +created, without pushing or opening anything. + +The upgrade skips releases published within the last 7 days, so a broken or +compromised release has time to be yanked before it can land in the lock file. +Change the cutoff with `exclude_newer`, which takes anything uv's +`--exclude-newer` accepts: a date, an RFC 3339 timestamp, a duration such as +`30 days`, or `false` to upgrade to the newest releases with no cutoff at all. +It reaches uv as `UV_EXCLUDE_NEWER`, so it overrides an `exclude-newer` the +repository sets in `pyproject.toml` or `uv.toml`. Pass `exclude_newer: ""` to +leave that setting in charge instead. + +```yaml + - uses: mongodb-labs/drivers-github-tools/python/uv-lock-update@v3 + with: + app_id: ${{ vars.APP_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} + exclude_newer: 14 days +``` diff --git a/python/setup/action.yml b/python/setup/action.yml new file mode 100644 index 0000000..ce2e554 --- /dev/null +++ b/python/setup/action.yml @@ -0,0 +1,41 @@ +name: Python Setup +description: Installs Python, uv, and just +inputs: + python-version: + description: Python version setup-python installs and uv then uses + default: "3.10" + allow-prereleases: + description: Select a prerelease when python-version has no stable release, instead of failing + default: "true" + enable-cache: + description: Cache the uv download cache between runs + default: "true" + +runs: + using: composite + steps: + # Runner images already ship these versions, so uv downloads no interpreter. + - name: Install Python + id: setup-python + uses: actions/setup-python@v7 + with: + python-version: ${{ inputs.python-version }} + allow-prereleases: ${{ inputs.allow-prereleases }} + + - name: Point uv at that interpreter + shell: bash + env: + PYTHON_PATH: ${{ steps.setup-python.outputs.python-path }} + # GITHUB_ENV is the only way a composite action exports to later steps. + run: | # zizmor: ignore[github-env] + # An absolute path pins uv here, ignoring .python-version. + echo "UV_PYTHON=$PYTHON_PATH" >> "$GITHUB_ENV" + + - name: Install uv + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 + with: + enable-cache: ${{ inputs.enable-cache }} + + - name: Install just + shell: bash + run: uv tool install rust-just