forked from github.com/pypiserver
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:
parent
e54270207d
commit
4645f7b10a
22
.github/workflows/ci.yml
vendored
22
.github/workflows/ci.yml
vendored
@ -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
|
||||
|
62
.github/workflows/rc.yml
vendored
62
.github/workflows/rc.yml
vendored
@ -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
58
.github/workflows/rt.yml
vendored
Normal 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
31
bin/README.md
Normal 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.
|
@ -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.
|
@ -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
|
||||
|
||||
|
||||
|
@ -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"
|
||||
|
113
docs/contents/repo-maintenance/release-work.md
Normal file
113
docs/contents/repo-maintenance/release-work.md
Normal 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.
|
Loading…
Reference in New Issue
Block a user