chore: cleanup release process (#516)

* chore: convert bin/README to md

* feat: replace all dates with bumpver

* chore: date in README is now changed by `bumpver`

* chore: clarify the RC workflow

* chore: use global `/tmp` in RC

* chore: slightly prettier description

* chore: move changes file to env

* chore: introduce release_tag workflow

chore(wip): test trigger on PR

chore(wip): add filtered diff

chore(wip): switch to PATTERNS

chore(wip): up to bumpver

chore(wip): temporary disable check

chore(wip): setup python

chore(wip): test with a specific version

chore(wip): test bumpver commit

chore(wip): fix the bumpver usage

chore: cleanup the release_tag workflow

* chore: create draft release from CI

* chore: update the docs

* chore: provide details on the release process

* chore: tiny header update

* chore: remove commented-out code
This commit is contained in:
Mitja O 2023-08-27 16:11:54 +02:00 committed by GitHub
parent e54270207d
commit 4645f7b10a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 289 additions and 72 deletions

View File

@ -130,10 +130,10 @@ jobs:
./bin/package.sh
- name: Publish distribution 📦 to PyPI.
uses: pypa/gh-action-pypi-publish@master
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_API_TOKEN }}
print_hash: true
print-hash: true
## DOCKER
@ -214,3 +214,21 @@ jobs:
- name: "Image digest"
run: "echo ${{ steps.docker_build.outputs.digest }}"
## GITHUB RELEASE DRAFT
create_release:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
runs-on: "ubuntu-latest"
needs:
- "tests"
steps:
- uses: actions/checkout@v3
- uses: softprops/action-gh-release@v1
with:
body: 👋 This is a draft release. Please update it manually.
prerelease: false
draft: true
files: |
CHANGES.rst

View File

@ -2,37 +2,61 @@
name: release_candidate
# TODO(actions):
# Performed actions:
# - [x] create a new AUTO-RC-<DATE> branch
# - [x] update CHANGES.rst
# - [x] prepare RC metadata and description
# - [x] update CHANGES.rst (+ rc-title, + date)
# - [x] create changes commit
# - [x] push to GH
# - [ ] update README.md
# - [ ] create readme commit
# - [ ] push to GH
# - [ ] open a PR to `master`
# TODO(general):
# - [ ] setup the action
# - [ ] cleanup the action
# - [x] open a PR to `master`
on:
schedule:
- cron: '0 0 1 * *' # each 1st day of the month
workflow_dispatch: # on manual trigger
- cron: "0 0 1 * *" # each 1st day of the month
workflow_dispatch: # on manual trigger
jobs:
new-rc:
runs-on: ubuntu-latest
env:
CHANGES_FILE: CHANGES.rst
PR_BODY_FILE: /tmp/pr-body.md
RF_DOCS_FILE: ./docs/contents/repo-maintenance/release-work.md
steps:
- uses: actions/checkout@v3
with:
# Flag to fetch all history.
# @see https://github.com/marketplace/actions/checkout#Fetch-all-history-for-all-tags-and-branches
fetch-depth: 0
- run: |
RC_DATE=$(date +'%m-%d-%Y')
- id: get-rc-date
run: echo "RC_DATE=$(date +'%Y-%m-%d')" >> "$GITHUB_OUTPUT"
- id: make-pr-body-file
run: |
PR_BODY_FILE=${{ env.PR_BODY_FILE }}
RC_DATE=${{ steps.get-rc-date.outputs.RC_DATE }}
touch ${PR_BODY_FILE}
echo "📦 Automated release candidate for ${RC_DATE}." >> ${PR_BODY_FILE}
echo "" >> ${PR_BODY_FILE}
echo "_TODO:_" >> ${PR_BODY_FILE}
echo "- [ ] Manually adjust generated CHANGES lines" >> ${PR_BODY_FILE}
echo "- [ ] Manually adjust generated CHANGES title" >> ${PR_BODY_FILE}
echo "- [ ] Manually adjust generated CHANGES date" >> ${PR_BODY_FILE}
echo "- [ ] Approve and merge this PR" >> ${PR_BODY_FILE}
echo "- [ ] See \`${{ env.RF_DOCS_FILE }}\` to continue" >> ${PR_BODY_FILE}
echo "${PR_BODY_FILE}:"
cat ${PR_BODY_FILE}
- id: propose-rc
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
CHANGES_FILE=${{ env.CHANGES_FILE }}
PR_BODY_FILE=${{ env.PR_BODY_FILE }}
RC_DATE=${{ steps.get-rc-date.outputs.RC_DATE }}
git config user.name github-actions
git config user.email github-actions@github.com
git checkout -b auto-release-candidate-${RC_DATE}
@ -43,13 +67,11 @@ jobs:
./bin/update_changelog.sh
git add CHANGES.rst
git commit -m "chore(rc-changes): update Changes.rst"
git add ${CHANGES_FILE}
git commit -m "chore(rc-changes): update ${CHANGES_FILE}"
git push
gh pr create --title "chore(auto-release-candidate-${RC_DATE})" \
--body "Automated release candidate for ${RC_DATE}." \
--body-file ${PR_BODY_FILE} \
--base master \
--draft
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

58
.github/workflows/rt.yml vendored Normal file
View File

@ -0,0 +1,58 @@
# Release Tag workflow
name: release_tag
# Performed actions:
# - [x] check that last commit is the CHANGES edit
# - [x] infer the last RC version
# - [x] run bumpver.py with the new version
# - [x] push the commit and new tag
on:
workflow_dispatch: # on manual trigger
jobs:
new-tag:
if: ${{ github.ref_name == 'master' }}
runs-on: ubuntu-latest
env:
CHANGES_FILE: CHANGES.rst
EXPECTED_DIFF_COUNT: 1
steps:
- uses: actions/checkout@v3
- id: get-diff
uses: technote-space/get-diff-action@v6
with:
PATTERNS: |
${{ env.CHANGES_FILE }}
SET_ENV_NAME_COUNT: true
- id: check-changes
run: |
echo "${{ steps.get-diff.outputs.count }}/${{ env.EXPECTED_DIFF_COUNT }} changes in ${{ env.CHANGES_FILE }}."
exit ${{ steps.get-diff.outputs.count == env.EXPECTED_DIFF_COUNT && 0 || 1 }}
- id: get-version
run: |
LAST_VERSION=$(grep -m1 -E ' \([0-9]+-[0-9]+-[0-9]+\)$' ${CHANGE_FILE} | awk '{ print $1 }')
echo "LAST_VERSION=${LAST_VERSION}" >> "$GITHUB_OUTPUT"
- uses: actions/setup-python@v4
with:
python-version: "3.x"
- id: install-requirements
run: pip install -r "requirements/dev.pip"
- name: run `bumpver`
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
CHANGE_FILE=${{ env.CHANGES_FILE }}
LAST_VERSION=${{ steps.get-version.outputs.LAST_VERSION }}
git config user.name github-actions
git config user.email github-actions@github.com
python3 bin/bumpver.py -t "Automated release ${LAST_VERSION}" ${LAST_VERSION}
git push --follow-tags

31
bin/README.md Normal file
View File

@ -0,0 +1,31 @@
# Build scripts folder
## Highlight files
- `bumpver.py` : Bump, commit and tag new project versions
- `package.sh` : Build deployable artifact (wheel) in `/dist/` folder.
## Fully manual release check-list
1. Update `/CHANGES.rst` (+ Title + Date).
2. Push to GitHub to run all TCs once more.
3. Bump version: commit & tag it with `/bin/bumpver.py`. Use `--help`.
> 💡 Read [PEP-440](https://www.python.org/dev/peps/pep-0440/) to decide the version.
4. Push it in GitHub with `--follow-tags`.
### Manually publishing a new package
1. Generate package *wheel* with `/bin/package.sh`.
2. Upload to PyPi with `twine upload -s -i <gpg-user> dist/*`
3. Ensure that the new tag is built on
[`hub.docker.com`](https://hub.docker.com/r/pypiserver/pypiserver)
as `latest` and as a direct tag reference.
4. Copy release notes from `/CHANGES.rst` in GitHub as new *"release"*
page on the new tag.
> 💡 Check syntactic differences between `.md` and `.rst` files.

View File

@ -1,36 +0,0 @@
====================
Build scripts folder
====================
Files:
======
- ``bumpver.py`` : Bump, commit and tag new project versions
- ``check_readme.sh`` : Check that README has no RsT-syntactic errors.
- ``package.sh`` : Build deployable artifact (wheel) in ``/dist/`` folder.
- ``README.rst`` : This file.
Release check-list:
===================
1. Update ``/CHANGES.rst`` (+ Title + Date) & ``/README.md`` (Date,
not version).
2. Push to GitHub to run all TCs once more.
3. Bump version: commit & tag it with ``/bin/bumpver.py``.
Use ``--help``.
Read `PEP-440 <https://www.python.org/dev/peps/pep-0440/>`_ to decide the version.
4. Push it in GitHub with ``--follow-tags``.
5. Generate package *wheel* with ``/bin/package.sh``.
6. Upload to PyPi with ``twine upload -s -i <gpg-user> dist/*``
7. Ensure that the new tag is built on hub.docker.com as ``latest`` and as a
direct tag reference.
8. Copy release notes from ``/CHANGES.rst`` in GitHub as new *"release"* page
on the new tag. Check syntactic differences between ``.md`` and ``.rst`` files.

View File

@ -35,19 +35,20 @@ EXAMPLE:
"""
import os.path as osp
import sys
import re
import functools as fnt
import os.path as osp
import re
import sys
from datetime import datetime
import docopt
my_dir = osp.dirname(__file__)
VFILE = osp.join(my_dir, "..", "pypiserver", "__init__.py")
VFILE_regex_v = re.compile(r'version *= *__version__ *= *"([^"]+)"')
VFILE_regex_d = re.compile(r'__updated__ *= *"([^"]+)"')
VFILE_regex_version = re.compile(r'version *= *__version__ *= *"([^"]+)"')
VFILE_regex_datetime = re.compile(r'__updated__ *= *"([^"]+)"')
VFILE_regex_date = re.compile(r'__updated__ *= *"([^"\s]+)\s')
RFILE = osp.join(my_dir, "..", "README.md")
@ -58,6 +59,13 @@ class CmdException(Exception):
pass
def get_current_date_info() -> (str, str):
now = datetime.now()
new_datetime = now.strftime("%Y-%m-%d %H:%M:%S%z")
new_date = now.strftime("%Y-%m-%d")
return (new_datetime, new_date)
@fnt.lru_cache()
def read_txtfile(fpath):
with open(fpath, "rt", encoding="utf-8") as fp:
@ -138,7 +146,6 @@ def exec_cmd(cmd):
def do_commit(new_ver, old_ver, dry_run, amend, ver_files):
import pathlib
# new_ver = strip_ver2_commonprefix(old_ver, new_ver)
cmt_msg = "chore(ver): bump %s-->%s" % (old_ver, new_ver)
ver_files = [pathlib.Path(f).as_posix() for f in ver_files]
@ -183,11 +190,12 @@ def bumpver(
cmd.append(RFILE)
exec_cmd(cmd)
regexes = [VFILE_regex_v, VFILE_regex_d]
old_ver, old_date = extract_file_regexes(VFILE, regexes)
regexes = [VFILE_regex_version, VFILE_regex_datetime, VFILE_regex_date]
old_ver, old_datetime, old_date = extract_file_regexes(VFILE, regexes)
if not new_ver:
yield old_ver
yield old_datetime
yield old_date
else:
if new_ver == old_ver:
@ -199,12 +207,13 @@ def bumpver(
msg += "!\n Use of --force recommended."
raise CmdException(msg % new_ver)
from datetime import datetime
new_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S%z")
new_datetime, new_date = get_current_date_info()
ver_files = [osp.normpath(f) for f in [VFILE, RFILE]]
subst_pairs = [(old_ver, new_ver), (old_date, new_date)]
subst_pairs = [
(old_ver, new_ver),
(old_datetime, new_datetime),
(old_date, new_date),
]
for repl in replace_substrings(ver_files, subst_pairs):
new_txt, fpath, replacements = repl
@ -258,6 +267,7 @@ def main(*args):
except CmdException as ex:
sys.exit(str(ex))
except Exception as ex:
print("Unexpected error happened.")
raise ex

View File

@ -32,6 +32,7 @@ mkdir -p $WORKSPACE_DIR
echo "Updating $CHANGE_FILE:"
# TODO(tech-debt): get `LAST_VERSION` with a separate bash script
LAST_VERSION=$(grep -m1 -E ' \([0-9]+-[0-9]+-[0-9]+\)$' $CHANGE_FILE | awk '{ print $1 }')
echo "Detected last release version: $LAST_VERSION"

View File

@ -0,0 +1,113 @@
# `Pypi-server` Release Workflow Reference
The official `pypi-server` releases are handled using
[GitHub Actions workflows](../../../.github/workflows/).
## General release process
```mermaid
flowchart LR
rc["release-candidate ⭐️"]
rn["release-notes 📝"]
rm["confirmed-tag ✅"]
ci["code-checks 🧪"]
pk["build-and-pack 📦"]
py["pypi-index 🗃️"]
do["docker-hub 🐳"]
gr["github-release 📣"]
subgraph "Preparation 🌱"
rc-->rn-->rm
end
subgraph "Integration 🪴"
rm-->ci-->pk
end
subgraph "Deploy 🌳"
pk--> py & do & gr
end
```
## Process walkthrough
> 🗺️ ***This description approximates the real GitHub workflows and steps.***
> 👀 *For a more detailed view, do check out the linked resources as you read.*
### Preparation 🌱
> 🛠️ *These step are applicable only for maintainers.*
#### Release candidate ⭐️
A new release candidate can be initiated ***manually** or **on a monthly schedule***.
This is done via the [`rc.yml`](../../../.github/workflows/rc.yml) GH
Workflow's `workflow_dispatch` or `schedule` trigger.
The workflow automatically prepares a list of changes for the `CHANGES.rst` and
creates a new Pull Request *(rc PR)* named
`chore(auto-release-candidate-YYY-MM-DD)` including these draft change notes.
#### Release notes 📝
In the created rc PR, open the `CHANGES.rst` and:
1. ***adjust the suggested changelog items***
2. ***choose & set the next released version***
3. ***set the right release date***
Commit the changes and push them to the head branch of the rc PR.
#### Confirmed tag ✅
1. Once everything is looking good, ***approve and merge*** the rc PR.
It will create the new *commit* with the updated `CHANGES.rst`
on the default branch.
2. Next, to create a release tag, ***manually run*** the
[`rt.yml`](../../../.github/workflows/rt.yml) GH Workflow.
First, it executes all the [`bumpver`](../../../bin/README.md) procedures.
Next, it commits and pushes the new **version tag** to the default branch.
### Integration 🪴
#### Code checks 🧪
Once any *commmit* or *tag* is pushed to the default branch,
[`ci.yml`](../../../.github/workflows/ci.yml) GH Workflow automatically
executes diverse code checks: e.g. *linting*, *formatting*, *tests*.
#### Build and pack 📦
If all the checks are successful, [`ci.yml`](../../../.github/workflows/ci.yml)
builds all the code artifacts: e.g. *wheels*, *docker images*.
### Deploy 🌳
#### Publish to PyPi 🗃️
> 🏷️ This happens only on new *version tags*.
Once everythig is built, [`ci.yml`](../../../.github/workflows/ci.yml) uploads
the wheels to the [`pypiserver` PyPi project](https://pypi.org/project/pypiserver/).
#### Publish to Docker Hub 🐳
> 🏷️ Docker image *tags* are determined on the fly.
If all is successful so far, [`ci.yml`](../../../.github/workflows/ci.yml) tags
the built docker images and pushes them to the
[`pypiserver` Docker Hub repository](https://hub.docker.com/r/pypiserver/pypiserver).
#### Publish a GitHub Release draft 📣
> 🛠️ *This step is applicable only for maintainers.*
> 🏷️ This happens only on new *version tags*.
To make the release noticeable, [`ci.yml`](../../../.github/workflows/ci.yml)
also creates a *draft*
[GitHub Release entry in the `pypiserver` repository](https://github.com/pypiserver/pypiserver/releases).
> 📝 Since it is a *draft*, the entry should be *manually* adjusted further.