From 58856c43cd00be8cfddaca9a3c4ed88406b1a735 Mon Sep 17 00:00:00 2001 From: Sebastiaan Huber Date: Thu, 2 Feb 2023 14:51:48 +0100 Subject: [PATCH] DevOps: Add a continuous deployment workflow This Github Actions workflow is triggered when a new tag is pushed to the main repository. It runs the pre-commit and the unit test suite and if both pass, the package is built and automatically pushed to PyPI. --- .github/workflows/cd.yml | 115 ++++++++++++++++++++++ .github/workflows/validate_release_tag.py | 35 +++++++ 2 files changed, 150 insertions(+) create mode 100644 .github/workflows/cd.yml create mode 100644 .github/workflows/validate_release_tag.py diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..2cec296 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,115 @@ +name: cd + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+*' + +jobs: + + validate-release-tag: + + if: github.repository == 'sponce24/aiida-abinit' + runs-on: ubuntu-latest + + steps: + - name: Checkout source + uses: actions/checkout@v2 + + - name: Set up Python 3.9 + uses: actions/setup-python@v2 + with: + python-version: '3.9' + + - name: Validate the tag version against the package version + run: python .github/workflows/validate_release_tag.py $GITHUB_REF + + pre-commit: + + needs: [validate-release-tag] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Cache Python dependencies + uses: actions/cache@v1 + with: + path: ~/.cache/pip + key: pip-pre-commit-${{ hashFiles('**/setup.json') }} + restore-keys: + pip-pre-commit- + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' + + - name: Install Python dependencies + run: pip install -e .[pre-commit,tests] + + - name: Run pre-commit + run: pre-commit run --all-files || ( git status --short ; git diff ; exit 1 ) + + tests: + + needs: [validate-release-tag] + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: ['3.8', '3.9'] + + services: + postgres: + image: postgres:12 + rabbitmq: + image: rabbitmq:latest + ports: + - 5672:5672 + + steps: + - uses: actions/checkout@v2 + + - name: Cache Python dependencies + uses: actions/cache@v1 + with: + path: ~/.cache/pip + key: pip-${{ matrix.python-version }}-tests-${{ hashFiles('**/setup.json') }} + restore-keys: + pip-${{ matrix.python-version }}-tests + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Python dependencies + run: pip install -e .[tests] && reentry scan + + - name: Run pytest + run: pytest -sv tests + + publish: + + name: Publish to PyPI + needs: [pre-commit, tests] + runs-on: ubuntu-latest + + steps: + - name: Checkout source + uses: actions/checkout@v2 + + - name: Set up Python 3.9 + uses: actions/setup-python@v2 + with: + python-version: '3.9' + + - name: Install flit + run: pip install flit~=3.4 + + - name: Build and publish + run: flit publish + env: + FLIT_USERNAME: __token__ + FLIT_PASSWORD: ${{ secrets.PYPI_KEY }} diff --git a/.github/workflows/validate_release_tag.py b/.github/workflows/validate_release_tag.py new file mode 100644 index 0000000..425d1f6 --- /dev/null +++ b/.github/workflows/validate_release_tag.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +"""Validate that the version in the tag label matches the version of the package.""" +import argparse +import ast +from pathlib import Path + + +def get_version_from_module(content: str) -> str: + """Get the ``__version__`` attribute from a module. + + .. note:: This has been adapted from :mod:`setuptools.config`. + """ + try: + module = ast.parse(content) + except SyntaxError as exception: + raise IOError('Unable to parse module.') from exception + + try: + return next( + ast.literal_eval(statement.value) for statement in module.body if isinstance(statement, ast.Assign) + for target in statement.targets if isinstance(target, ast.Name) and target.id == '__version__' + ) + except StopIteration as exception: + raise IOError('Unable to find the `__version__` attribute in the module.') from exception + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('GITHUB_REF', help='The GITHUB_REF environmental variable') + args = parser.parse_args() + assert args.GITHUB_REF.startswith('refs/tags/v'), f'GITHUB_REF should start with "refs/tags/v": {args.GITHUB_REF}' + tag_version = args.GITHUB_REF[11:] + package_version = get_version_from_module(Path('aiida_abinit/__init__.py').read_text(encoding='utf-8')) + error_message = f'The tag version `{tag_version}` is different from the package version `{package_version}`' + assert tag_version == package_version, error_message