Skip to content

Commit

Permalink
Merge pull request #72 from lkubb/release-pr-workflow
Browse files Browse the repository at this point in the history
Fixes #18
  • Loading branch information
lkubb authored Nov 2, 2024
2 parents 31c59e3 + 13a8a71 commit 9ac4b51
Show file tree
Hide file tree
Showing 13 changed files with 375 additions and 20 deletions.
1 change: 1 addition & 0 deletions changelog/+newsfrag.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added `breaking` news fragment type to towncrier
1 change: 1 addition & 0 deletions changelog/18.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improved release automation: Added workflow that builds the changelog and creates/updates a PR on pushes to the default branch. Added trigger for release workflow when this PR is merged.
9 changes: 9 additions & 0 deletions data/versions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ dorny/paths-filter: 'de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2'
# renovate: datasource=git-tags depName=https://github.com/geekyeggo/delete-artifact depType=action
geekyeggo/delete-artifact: '7ee91e82b4a7f3339cd8b14beace3d826a2aac39 # v5.1.0'

# renovate: datasource=git-tags depName=https://github.com/juliangruber/find-pull-request-action depType=action
juliangruber/find-pull-request-action: '2f36c5fe1abfda4745dfab4f38217ebad8ded4eb # v1.9.0'

# renovate: datasource=git-tags depName=https://github.com/mathieudutour/github-tag-action depType=action
mathieudutour/github-tag-action: 'd28fa2ccfbd16e871a4bdf35e11b3ad1bd56c0c1 # v6.2'

# renovate: datasource=git-tags depName=https://github.com/peter-evans/create-pull-request depType=action
peter-evans/create-pull-request: '5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5'

# renovate: datasource=git-tags depName=https://github.com/pypa/gh-action-pypi-publish depType=action
pypa/gh-action-pypi-publish: '1bb664cc2ddedbbfdde43d4ac135d5836b7bf40f # v1.11.0'

Expand Down
8 changes: 7 additions & 1 deletion docs/topics/documenting/changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
(changelog-target)=
# Keeping a changelog

Your Saltext project uses [towncrier](https://towncrier.readthedocs.io/en/stable/) to manage and render its {path}`CHANGELOG.md` file, which is included in the rendered documentation as well.
Expand All @@ -20,10 +21,15 @@ For every user-facing change, ensure your patch includes a corresponding news fr
* `changed`
* `removed`
* `deprecated`
* `breaking`
* `security`

4. The file contents should be written in Markdown.

:::{hint}
It's possible to create a news fragment that does not reference an issue by prefixing the file name with a `+`, e.g. `+foo.changed.md`.
:::

## Example

Suppose a PR fixes a crash when the `foo.bar` configuration value is missing. The news fragment can be created as follows:
Expand All @@ -36,4 +42,4 @@ Include this file in the PR.

## Building the changelog

Before tagging a release, the individual `changelog/*.md` files need to be compiled into the actual changelog. Refer to [Building the changelog](changelog-build-target) for instructions on how to do this.
Before tagging a release, the individual `changelog/*.md` files need to be compiled into the actual changelog. This is taken care of by the [release automation](release-automated-target). For [manual releases](release-manual-target), refer to [Building the changelog](changelog-build-target) for instructions on how to do this.
33 changes: 27 additions & 6 deletions docs/topics/publishing.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,32 @@ There are currently no included workflows for other Git hosting providers or CI

Once your Salt extension is ready, you can submit it to PyPI.

## 0: Prerequisites
Ensure you meet the following prerequisites:

* Your project is hosted on GitHub.
* It is either in the `salt-extensions` organization or you have set up the [required secrets](required-secrets-target).
* You have commit rights to the repository.

(release-automated-target)=
## Automated
Generated projects include a workflow that automatically detects the next version bump based on [news fragments](changelog-target) in {path}`changelog`, builds the changelog and submits a PR with these changes. Once you are ready to release a new version, simply merge this PR, which creates a new git tag and triggers the release workflow.

:::{important}
Before merging, ensure the PR is based on the current default branch HEAD.
:::

:::{hint}
To force a custom version or manually trigger an update to the release PR (e.g. to adjust the release date), go to `Actions` > `Prepare Release PR` > `Run workflow`.
:::

:::{note}
The generated PR is only created automatically if there is at least one news fragment to render. You can still trigger a manual run as described above.
:::

(release-manual-target)=
## Manual
### 0: Prerequisites

* You have added a git remote `upstream` to your local repository, pointing to the official repository via **SSH**.
* You have executed the [first steps](first-steps-target) to setup your repository and virtual environment in some way.
* You have activated your virtual environment.
Expand All @@ -25,7 +46,7 @@ git switch main && git fetch upstream && git rebase upstream/main
```

(changelog-build-target)=
## 1: Build the changelog
### 1: Build the changelog

Create and switch to a new branch:

Expand All @@ -41,15 +62,15 @@ towncrier build --yes --version v1.0.0

This command combines all news fragments into {path}`CHANGELOG.md` and clears them. Commit the change.

## 2: Submit the changelog
### 2: Submit the changelog

Submit this commit as a PR and merge it into the default branch on `upstream`.

:::{tip}
Squash-merging this PR results in a cleaner tag target.
:::

## 3: Tag a release
### 3: Tag a release

Ensure your `main` branch is up to date (again):

Expand All @@ -67,14 +88,14 @@ git tag v1.0.0
The tag must start with `v` for the default publishing workflows to work correctly.
:::

## 4: Push the tag
### 4: Push the tag

Push the new tag upstream to trigger the publishing workflow:

```bash
git push upstream v1.0.0
```

## 5: Check the result
### 5: Check the result

If CI passes, a new release should be available on both PyPI and your GitHub repository.
1 change: 1 addition & 0 deletions docs/topics/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The workflows currently:
* Ensure `pre-commit` checks pass
* Run the test suite and upload code coverage reports
* Build the documentation
* Build the changelog and submit a PR that triggers a release when merged
* Optionally deploy built documentation to GitHub Pages
* Optionally build and release your project to PyPI

Expand Down
5 changes: 5 additions & 0 deletions project/pyproject.toml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ title_format = "## {version} ({project_date})"
issue_format = "[#{issue}]({{ tracker_url }}/{issue})"
{%- endif %}

[[tool.towncrier.type]]
directory = "breaking"
name = "Breaking changes"
showcontent = true

[[tool.towncrier.type]]
directory = "removed"
name = "Removed"
Expand Down
92 changes: 92 additions & 0 deletions project/tools/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""
Very simple heuristic to generate the next version number
based on the current changelog news fragments.
This looks for the most recent version by parsing the
CHANGELOG.md file and increments a specific part,
depending on the fragment types present and their contents.
Major bumps are caused by:
* files named `.removed.md`
* files named `.breaking.md`
* files containing `BREAKING:`
Minor bumps are caused by:
* files named `.added.md`
Otherwise, only the patch version is bumped.
"""

import re
import sys
from pathlib import Path

PROJECT_ROOT = Path(".").resolve()
CHANGELOG_DIR = PROJECT_ROOT / "changelog"
CHANGELOG_FILE = PROJECT_ROOT / "CHANGELOG.md"


class Version:
def __init__(self, version):
match = re.search(r"v?(?P<release>[0-9]+(?:\.[0-9]+)*)", version)
if not match:
raise ValueError(f"Invalid version: '{version}'")
self.release = tuple(int(i) for i in match.group("release").split("."))

@property
def major(self):
return self._ret(0)

@property
def minor(self):
return self._ret(1)

@property
def patch(self):
return self._ret(2)

def __str__(self):
return ".".join(str(i) for i in self.release)

def _ret(self, cnt):
try:
return self.release[cnt]
except IndexError:
return 0


def last_release():
for line in CHANGELOG_FILE.read_text(encoding="utf-8").splitlines():
if line.startswith("## "):
return Version(line.split(" ")[1])
return Version("0.0.0")


def get_next_version(last):
major = minor = False

for fragment in CHANGELOG_DIR.glob("[!.]*"):
name = fragment.name.lower()
if ".added" in name:
minor = True
elif ".breaking" in name or ".removed" in name:
major = True
break
if "breaking:" in fragment.read_text(encoding="utf-8").lower():
major = True
break
if major:
return Version(f"{last.major + 1}.0.0")
if minor:
return Version(f"{last.major}.{last.minor + 1}.0")
return Version(f"{last.major}.{last.minor}.{last.patch + 1}")


if __name__ == "__main__":
try:
if sys.argv[1] == "next":
print(get_next_version(last_release()))
raise SystemExit(0)
except IndexError:
pass
print(last_release())
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,46 @@ jobs:
- pre-commit
uses: ./.github/workflows/docs-action.yml

check-prepare-release:
name: Check if we can prepare release PR
if: >-
github.event_name == 'push' &&
github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
needs:
- docs
- test
{%- endraw %}
runs-on: ubuntu-{{ versions["ubuntu"] }}
{%- raw %}
outputs:
news-fragments-available: ${{ steps.check-available.outputs.available }}

steps:
{%- endraw %}
- uses: actions/checkout@{{ versions["actions/checkout"] }}
{%- raw %}

- name: Check if news fragments are available
id: check-available
run: |
if [ -n "$(find changelog -type f -not -name '.*' -print -quit)" ]; then
echo "available=1" >> "$GITHUB_OUTPUT"
else
echo "available=0" >> "$GITHUB_OUTPUT"
fi

prepare-release:
name: Prepare Release PR
if: ${{ needs.check-prepare-release.outputs.news-fragments-available == '1' }}
needs:
- check-prepare-release
- docs
- test
permissions:
contents: write
pull-requests: write
uses: ./.github/workflows/prepare-release-action.yml

deploy-docs:
name: Deploy Docs
uses: ./.github/workflows/deploy-docs-action.yml
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "$GITHUB_REF_NAME" \
gh release create "v${{ inputs.version }}" \
--repo="$GITHUB_REPOSITORY" \
--title="${GITHUB_REPOSITORY#*/} ${{ inputs.version }}" \
--generate-notes \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ jobs:
contents: write
id-token: write
pages: write
pull-requests: read
pull-requests: write
{%- endraw %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{%- raw -%}
---
name: Prepare Release PR

on:
workflow_call:
workflow_dispatch:
inputs:
version:
description: Override the autogenerated version.
required: false
default: ''
type: string

jobs:
update-release:
name: Render changelog and create/update PR
{%- endraw %}
runs-on: ubuntu-{{ versions["ubuntu"] }}
{%- raw %}
if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
permissions:
contents: write
pull-requests: write

steps:
- name: Checkout code
{%- endraw %}
uses: actions/checkout@{{ versions["actions/checkout"] }}
{%- raw %}

- name: Set up Python 3.10
{%- endraw %}
uses: actions/setup-python@{{ versions["actions/setup-python"] }}
{%- raw %}
with:
python-version: '3.10'

- name: Install project
run: |
python -m pip install --upgrade pip
python -m pip install '.[changelog]' pre-commit

- name: Get next version
if: github.event_name == 'push' || inputs.version == ''
id: next-version
run: echo "version=$(python tools/version.py next)" >> "$GITHUB_OUTPUT"

- name: Update CHANGELOG.md
env:
NEXT_VERSION: ${{ (github.event_name == 'workflow_dispatch' && inputs.version != '') && inputs.version || steps.next-version.outputs.version }}
run: towncrier build --yes --version "${NEXT_VERSION}"

- name: Run pre-commit once to remove trailing whitespace
run: |
python -m pre_commit run --files=CHANGELOG.md || true

- name: Create/update release PR
{%- endraw %}
uses: peter-evans/create-pull-request@{{ versions["peter-evans/create-pull-request"] }}
{%- raw %}
with:
commit-message: Release v${{ (github.event_name == 'workflow_dispatch' && inputs.version != '') && inputs.version || steps.next-version.outputs.version }}
branch: release/auto
sign-commits: true
title: Release v${{ (github.event_name == 'workflow_dispatch' && inputs.version != '') && inputs.version || steps.next-version.outputs.version }}
body: |
This automated PR builds the latest changelog. When merged, a new release is published automatically.

Before merging, please ensure it's based on the most recent default branch HEAD.

If you want to rebuild this PR with a custom version or the current date, you can also trigger the corresponding workflow manually in `Actions` > `Prepare Release PR` > `Run workflow`.

You can still follow the manual release procedure outlined in: https://salt-extensions.github.io/salt-extension-copier/topics/publishing.html
{%- endraw %}
Loading

0 comments on commit 9ac4b51

Please sign in to comment.