From 5cf8046bccc37dc282a9cb3f6e2685de28992998 Mon Sep 17 00:00:00 2001 From: Pablo Prietz Date: Wed, 23 Jun 2021 16:05:59 +0200 Subject: [PATCH] Split version-bumping and deployment into separate workflows Due to security restrictions, pull requests from forked repositories do not have access to the workflow's secret. As a result, the "Deploy to PyPI" workflow will fail and not trigger a deployment (see https://github.com/zeromq/pyre/pull/157#issuecomment-862613841). In these cases, the deployment needs to be triggered again by manually dispatching the workflow. To allow for further flexibility, this PR splits the version-bumping from the deployment workflow. Specifically, this causes the deployment workflow to trigger on all pushed tags, not only those created by the "Bump version" workflow. --- .github/workflows/bump_version.yml | 105 ++++++++++++++++++++++++ .github/workflows/deploy.yml | 127 +---------------------------- MAINTENANCE.md | 33 +++++--- 3 files changed, 129 insertions(+), 136 deletions(-) create mode 100644 .github/workflows/bump_version.yml diff --git a/.github/workflows/bump_version.yml b/.github/workflows/bump_version.yml new file mode 100644 index 0000000..d01c82d --- /dev/null +++ b/.github/workflows/bump_version.yml @@ -0,0 +1,105 @@ +name: Bump version +on: + pull_request: + branches: [master] + types: [closed] + workflow_dispatch: + inputs: + version_part: + description: > + Version part to bump before deployment. + Possible options {none, major, minor, patch} + required: true + default: 'patch' + +jobs: + get_version_part_manually: + name: Bump version on manual workflow dispatch + if: github.event.inputs.version_part + runs-on: ubuntu-latest + env: + VERSION_PART: ${{ github.event.inputs.version_part }} + outputs: + # will be empty if validation fails + version_part: ${{ steps.validated_input.outputs.version_part }} + steps: + - name: Cancel on invalid input + if: > + !( + env.VERSION_PART == 'none' || + env.VERSION_PART == 'major' || + env.VERSION_PART == 'minor' || + env.VERSION_PART == 'patch' + ) + run: | + echo "::error:: \`$VERSION_PART\` is not a valid version part. Must be one of {none, major, minor, patch}" + exit 1 + - name: Set version part based on manual input + id: validated_input + run: echo "::set-output name=version_part::$VERSION_PART" + + get_version_part_on_pr_merge: + name: Bump version on pull reuqest merge + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + outputs: + version_part: ${{ join(steps.*.outputs.version_part, '') }} + steps: + - name: Cancel on bump:none + id: bump_none + if: contains(github.event.pull_request.labels.*.name, 'bump:none') + run: echo "::set-output name=version_part::none" + - name: Bump major + id: bump_major + if: > + steps.bump_none.conclusion == 'skipped' && + contains(github.event.pull_request.labels.*.name, 'bump:major') + run: echo "::set-output name=version_part::major" + - name: Bump minor + id: bump_minor + if: > + steps.bump_none.conclusion == 'skipped' && + steps.bump_major.conclusion == 'skipped' && + contains(github.event.pull_request.labels.*.name, 'bump:minor') + run: echo "::set-output name=version_part::minor" + - name: Bump patch + id: bump_patch + if: > + steps.bump_none.conclusion == 'skipped' && + steps.bump_major.conclusion == 'skipped' && + steps.bump_minor.conclusion == 'skipped' + run: echo "::set-output name=version_part::patch" + + bump_version: + name: Bump version + needs: [get_version_part_on_pr_merge, get_version_part_manually] + # always() needed to not automatically skip this job due to one of the + # get_version_part_* jobs being skipped and bump_version depending on both. + if: > + always() && + ( + needs.get_version_part_on_pr_merge.result == 'success' || + needs.get_version_part_manually.result == 'success' + ) && + join(needs.*.outputs.version_part, '') != 'none' + env: + VERSION_PART: ${{ join(needs.*.outputs.version_part, '') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v1 + with: + python-version: 3.7 + - name: Install bump2version + run: pip install bump2version + - uses: oleksiyrudenko/gha-git-credentials@v2-latest + with: + token: ${{ secrets.GITHUB_TOKEN }} + - name: Bump version + run: bump2version --verbose "$VERSION_PART" + - name: Push changes + uses: ad-m/github-push-action@master + with: + tags: true + branch: ${{ github.ref }} + github_token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d13ac2d..c881a3a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,133 +1,15 @@ name: Deploy to PyPI on: - pull_request: - branches: [master] - types: [closed] - workflow_dispatch: - inputs: - version_part: - description: > - Version part to bump before deployment. - Possible options {none, major, minor, patch} - required: true - default: 'patch' + push: + tags: + - "**" jobs: - get_version_part_manually: - name: Bump version on manual workflow dispatch - if: github.event.inputs.version_part - runs-on: ubuntu-latest - env: - VERSION_PART: ${{ github.event.inputs.version_part }} - outputs: - # will be empty if validation fails - version_part: ${{ steps.validated_input.outputs.version_part }} - steps: - - name: Cancel on invalid input - if: > - !( - env.VERSION_PART == 'none' || - env.VERSION_PART == 'major' || - env.VERSION_PART == 'minor' || - env.VERSION_PART == 'patch' - ) - run: | - echo "::error:: \`$VERSION_PART\` is not a valid version part. Must be one of {none, major, minor, patch}" - exit 1 - - name: Set version part based on manual input - id: validated_input - run: echo "::set-output name=version_part::$VERSION_PART" - - get_version_part_on_pr_merge: - name: Bump version on pull reuqest merge - if: github.event.pull_request.merged == true - runs-on: ubuntu-latest - outputs: - version_part: ${{ join(steps.*.outputs.version_part, '') }} - steps: - - name: Cancel on bump:none - id: bump_none - if: contains(github.event.pull_request.labels.*.name, 'bump:none') - run: echo "::set-output name=version_part::none" - - name: Bump major - id: bump_major - if: > - steps.bump_none.conclusion == 'skipped' && - contains(github.event.pull_request.labels.*.name, 'bump:major') - run: echo "::set-output name=version_part::major" - - name: Bump minor - id: bump_minor - if: > - steps.bump_none.conclusion == 'skipped' && - steps.bump_major.conclusion == 'skipped' && - contains(github.event.pull_request.labels.*.name, 'bump:minor') - run: echo "::set-output name=version_part::minor" - - name: Bump patch - id: bump_patch - if: > - steps.bump_none.conclusion == 'skipped' && - steps.bump_major.conclusion == 'skipped' && - steps.bump_minor.conclusion == 'skipped' - run: echo "::set-output name=version_part::patch" - - bump_version: - name: Bump version - needs: [get_version_part_on_pr_merge, get_version_part_manually] - # always() needed to not automatically skip this job due to one of the - # get_version_part_* jobs being skipped and bump_version depending on both. - if: > - always() && - ( - needs.get_version_part_on_pr_merge.result == 'success' || - needs.get_version_part_manually.result == 'success' - ) && - join(needs.*.outputs.version_part, '') != 'none' - env: - VERSION_PART: ${{ join(needs.*.outputs.version_part, '') }} - outputs: - bumped_version_sha: > - ${{ steps.save_bumped_version_sha.outputs.bumped_version_sha || github.sha }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v1 - with: - python-version: 3.7 - - name: Install bump2version - run: pip install bump2version - - uses: oleksiyrudenko/gha-git-credentials@v2-latest - with: - token: ${{ secrets.GITHUB_TOKEN }} - - name: Bump version - run: bump2version --verbose "$VERSION_PART" - - name: Push changes - uses: ad-m/github-push-action@master - with: - tags: true - branch: ${{ github.ref }} - github_token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - - name: Save new git commit SHA to job output - id: save_bumped_version_sha - run: | - echo "Setting bumped_version_sha=$(git rev-parse HEAD)" - echo "::set-output name=bumped_version_sha::$(git rev-parse HEAD)" - build_sdist: name: Build source distribution runs-on: ubuntu-latest - needs: [bump_version] - # always() needed to not automatically skip this job due to one of the - # get_version_part_* jobs being skipped and bump_version depending on both. - if: > - always() && - ( - needs.bump_version.result == 'success' || - needs.bump_version.result == 'skipped' - ) steps: - uses: actions/checkout@v2 - with: - ref: ${{ needs.bump_version.outputs.bumped_version_sha }} - uses: actions/setup-python@v1 with: python-version: 3.7 @@ -145,9 +27,6 @@ jobs: name: Deploy to PyPI runs-on: ubuntu-latest needs: [build_sdist] - # always() needed to not automatically skip this job due to one of the - # get_version_part_* jobs being skipped and bump_version depending on both. - if: always() && needs.build_sdist.result == 'success' steps: - uses: actions/checkout@v2 - name: Download source package diff --git a/MAINTENANCE.md b/MAINTENANCE.md index 303e8a9..1542358 100644 --- a/MAINTENANCE.md +++ b/MAINTENANCE.md @@ -3,8 +3,9 @@ Maintenance is based on extra tools that are defined as [optional dependencies](https://setuptools.readthedocs.io/en/latest/userguide/dependency_management.html#optional-dependencies) in [`setup.py`](setup.py). The sections below refer to them as "extra requirements". -To automate maintenance as much as possible, this repository features two Github Actions: +To automate maintenance as much as possible, this repository features three Github Actions: 1. [Test on push or pull request](.github/workflows/test.yml) - see [Testing](#testing) +1. [Bump version](.github/workflows/bump_version.yml) - see [Deployment](#deployment) 1. [Deploy to PyPI](.github/workflows/deploy.yml) - see [Deployment](#deployment) ## Testing @@ -28,8 +29,8 @@ triggers on: Deployment works by bumping the version, building a source distribution (`sdist`), and uploading it to [PyPI](https://pypi.org/project/zeromq-pyre/) using [`twine`](https://twine.readthedocs.io/). -These steps are automated as part of the ["Deploy to PyPI" Github Action](.github/workflows/deploy.yml). -See below for details. +These steps are automated as part of the ["Bump version"](.github/workflows/bump_version.yml) and ["Deploy to PyPI"](.github/workflows/deploy.yml) +Github Actions. See below for details. ### Versioning @@ -41,12 +42,18 @@ To avoid human error, it is recommended to use the the `deploy` extra requirements. To manually bump the version, run `bump2version `, where `` is either `major`, `minor`, or `patch`. -**Note:** It is **not** recommended to run this tool manually. Instead, this step has -been automated as part of the ["Deploy to PyPI" Github Action](.github/workflows/deploy.yml). +**Note 1:** It is **not** recommended to run this tool manually. Instead, this step has +been automated as part of the ["Bump version" Github Action](.github/workflows/bump_version.yml). To push the version-bumped commit back to the repo, the action requires more permissions than the [default `GITHUB_TOKEN` provides](https://github.com/zeromq/pyre/pull/155#issuecomment-861020168). Instead, it [requires a personal access token](https://docs.github.com/en/actions/reference/authentication-in-a-workflow#granting-additional-permissions) -(PAT; stored and accessed as the `PERSONAL_ACCESS_TOKEN` secret). See below for further details. +(PAT; stored and accessed as the `PERSONAL_ACCESS_TOKEN` secret). This allows writing +to the repository and triggering the ["Deploy to PyPI" Github Action](.github/workflows/deploy.yml). + +**Note 2:** Due to security restrictions, pull requests from forked repositories do not +have access to the workflow's secret. As a result, the "Bump version" workflow will fail +and not trigger a deployment. In these cases, the deployment needs to be triggered by +manually dispatching the workflow or pushing a tagged commit. ### Building a distribution @@ -69,8 +76,7 @@ package installer (`pip`)](https://pypi.org/project/pip/) is the easiest way for to install the project. It also allows other projects to easily define this project as a dependency. -When triggered, the ["Deploy to PyPI" Github Action](.github/workflows/deploy.yml) bumps -the version, builds a source distribution, and deploys it to PyPI. See [Github Action usage](#github-action-usage) +When triggered, the ["Deploy to PyPI" Github Action](.github/workflows/deploy.yml) builds a source distribution, and deploys it to PyPI. See [Github Action usage](#github-action-usage) below for information when the Github Action is triggered and how to control the version part that will be bumped. @@ -83,10 +89,13 @@ as an API token. ### Github Action usage -The ["Deploy to PyPI" Github Action](.github/workflows/deploy.yml) triggers on: -- merged pull requests -- commits being pushed to the `master` branch -- manual dispatch via [Github UI](https://github.com/zeromq/pyre/actions/workflows/deploy.yml) +The [Bump version](.github/workflows/bump_version.yml) Github Action triggers on: +- Merged pull requests to the `master` branch +- [Manual workflow dispatch](https://github.com/zeromq/pyre/actions/workflows/bump_version.yml) + + +The ["Deploy to PyPI"](.github/workflows/deploy.yml) Github Action triggers on: +- tags being pushed to the repository, including version bumps created by the [Bump version](.github/workflows/bump_version.yml) Github Action There are four version part values for automatic version bumping: `none`, `major`, `minor`, `patch` (default). For pull requests, you can assign one of the `bump:*`