name: ci-cd on: push: pull_request: workflow_dispatch: schedule: # Weekly test (on branch main) every Thursday at 12:00 UTC. # (Used to monitor compatibility with ESP APIs and other dependencies.) - cron: '0 12 * * 4' jobs: skip_duplicate_runs: # Avoid running the live integration tests twice on the same code # (to conserve limited sending quotas in the live ESP test accounts) runs-on: ubuntu-20.04 outputs: should_skip: ${{ steps.skip_check.outputs.should_skip }} steps: - id: skip_check # uses: fkirc/skip-duplicate-actions@v3.4.0 uses: fkirc/skip-duplicate-actions@4c656bbdb6906310fa6213604828008bc28fe55d with: concurrent_skipping: "same_content" skip_after_successful_duplicate: "true" cancel_others: "true" do_not_skip: '["pull_request", "workflow_dispatch", "schedule"]' test: name: ${{ matrix.config.tox }} ${{ matrix.config.options }} runs-on: ubuntu-20.04 needs: skip_duplicate_runs timeout-minutes: 15 strategy: fail-fast: false matrix: config: - { tox: "lint", python: "3.8" } - { tox: "docs", python: "3.8" } # Anymail supports the same Python versions as Django, plus PyPy. # https://docs.djangoproject.com/en/dev/faq/install/#what-python-version-can-i-use-with-django # Live API integration tests are only run on a few, representative Python/Django version # combinations, to avoid rapidly consuming the testing accounts' entire send allotments. # Django 2.0: Python 3.5+ - { tox: django20-py35-all, python: "3.5" } - { tox: django20-py36-all, python: "3.6" } - { tox: django20-pypy3-all, python: "pypy3" } # Django 2.1: Python 3.5, 3.6, or 3.7 - { tox: django21-py35-all, python: "3.5" } - { tox: django21-py36-all, python: "3.6" } - { tox: django21-py37-all, python: "3.7" } - { tox: django21-pypy3-all, python: pypy3 } # Django 2.2: Python 3.5, 3.6, 3.7, 3.8 (added in 2.2.8), 3.9 (added in 2.2.17) - { tox: django22-py35-all, python: "3.5" } - { tox: django22-py36-all, python: "3.6" } - { tox: django22-py37-all, python: "3.7" } - { tox: django22-py38-all, python: "3.8" } - { tox: django22-py39-all, python: "3.9" } - { tox: django22-pypy3-all, python: "pypy3" } # Django 3.0: Python 3.6, 3.7, 3.8, 3.9 (added in 3.0.11) - { tox: django30-py36-all, python: "3.6" } - { tox: django30-py37-all, python: "3.7" } - { tox: django30-py38-all, python: "3.8" } - { tox: django30-py39-all, python: "3.9" } - { tox: django30-pypy3-all, python: "pypy3" } # Django 3.1: Python 3.6, 3.7, 3.8, 3.9 (added in 3.1.3) - { tox: django31-py36-all, python: "3.6" } - { tox: django31-py37-all, python: "3.7" } - { tox: django31-py38-all, python: "3.8", options: run-live-tests } - { tox: django31-py39-all, python: "3.9" } - { tox: django31-pypy3-all, python: "pypy3" } # Django 3.2: Python 3.6, 3.7, 3.8, 3.9 - { tox: django32-py36-all, python: "3.6" } - { tox: django32-py37-all, python: "3.7" } - { tox: django32-py38-all, python: "3.8" } - { tox: django32-py39-all, python: "3.9" } - { tox: django32-pypy3-all, python: "pypy3" } # Django 4.0: Python 3.8, 3.9, 3.10 # Django current development (direct from GitHub source) - { tox: djangoDev-py38-all, python: "3.8", options: allow-failures } - { tox: djangoDev-py39-all, python: "3.9", options: allow-failures } - { tox: djangoDev-py310-all, python: "3.10.0-alpha - 3.10", options: allow-failures } # Install without optional extras (don't need to cover entire matrix) - { tox: django31-py37-none, python: "3.7" } - { tox: django31-py37-amazon_ses, python: "3.7" } - { tox: django31-py37-postal, python: "3.7" } # Test some specific older package versions - { tox: django22-py37-all-old_urllib3, python: "3.7" } steps: - name: Get code uses: actions/checkout@v2 - name: Setup Python ${{ matrix.config.python }} # Ensure matrix Python version is installed and available for tox uses: actions/setup-python@v2 with: python-version: ${{ matrix.config.python }} - name: Setup default Python # Change default Python version back to something consistent # for installing/running tox uses: actions/setup-python@v2 with: python-version: 3.9 - name: Install tox run: | set -x python --version pip install tox tox --version - name: Test ${{ matrix.config.tox }} run: | tox -e ${{ matrix.config.tox }} continue-on-error: ${{ contains( matrix.config.options, 'allow-failures' ) }} env: CONTINUOUS_INTEGRATION: true TOX_FORCE_IGNORE_OUTCOME: false ANYMAIL_RUN_LIVE_TESTS: >- ${{ contains( matrix.config.options, 'run-live-tests' ) && needs.skip_duplicate_runs.outputs.should_skip != 'true' }} ANYMAIL_TEST_AMAZON_SES_ACCESS_KEY_ID: ${{ secrets.ANYMAIL_TEST_AMAZON_SES_ACCESS_KEY_ID }} ANYMAIL_TEST_AMAZON_SES_REGION_NAME: ${{ secrets.ANYMAIL_TEST_AMAZON_SES_REGION_NAME }} ANYMAIL_TEST_AMAZON_SES_SECRET_ACCESS_KEY: ${{ secrets.ANYMAIL_TEST_AMAZON_SES_SECRET_ACCESS_KEY }} ANYMAIL_TEST_MAILGUN_API_KEY: ${{ secrets.ANYMAIL_TEST_MAILGUN_API_KEY }} ANYMAIL_TEST_MAILGUN_DOMAIN: ${{ secrets.ANYMAIL_TEST_MAILGUN_DOMAIN }} ANYMAIL_TEST_MAILJET_API_KEY: ${{ secrets.ANYMAIL_TEST_MAILJET_API_KEY }} ANYMAIL_TEST_MAILJET_SECRET_KEY: ${{ secrets.ANYMAIL_TEST_MAILJET_SECRET_KEY }} ANYMAIL_TEST_MANDRILL_API_KEY: ${{ secrets.ANYMAIL_TEST_MANDRILL_API_KEY }} ANYMAIL_TEST_POSTMARK_SERVER_TOKEN: ${{ secrets.ANYMAIL_TEST_POSTMARK_SERVER_TOKEN }} ANYMAIL_TEST_POSTMARK_TEMPLATE_ID: ${{ secrets.ANYMAIL_TEST_POSTMARK_TEMPLATE_ID }} ANYMAIL_TEST_SENDGRID_API_KEY: ${{ secrets.ANYMAIL_TEST_SENDGRID_API_KEY }} ANYMAIL_TEST_SENDGRID_TEMPLATE_ID: ${{ secrets.ANYMAIL_TEST_SENDGRID_TEMPLATE_ID }} ANYMAIL_TEST_SENDINBLUE_API_KEY: ${{ secrets.ANYMAIL_TEST_SENDINBLUE_API_KEY }} ANYMAIL_TEST_SPARKPOST_API_KEY: ${{ secrets.ANYMAIL_TEST_SPARKPOST_API_KEY }} identify_tag: # If the (exact) ref on which this workflow is running is tagged, # set outputs.tag to the tag; otherwise set it to an empty string runs-on: ubuntu-20.04 outputs: tag: ${{ steps.get_tag.outputs.tag }} steps: - name: Get code uses: actions/checkout@v2 - name: Identify git tag id: get_tag run: | TAG=$(git describe --tags --exact-match "$GITHUB_REF" 2>/dev/null || echo "") echo "::set-output name=tag::$TAG" echo "github ref: $GITHUB_REF" echo "tag: ${TAG:-(no tag)}" release: # Release on push or manual trigger of ref that is a v* tag runs-on: ubuntu-20.04 needs: - identify_tag - test if: >- startsWith(needs.identify_tag.outputs.tag, 'v') && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') steps: - name: Get code uses: actions/checkout@v2 - name: Setup Python uses: actions/setup-python@v2 with: python-version: 3.9 - name: Check package version id: version env: TAG: ${{ needs.identify_tag.outputs.tag }} run: | VERSION="v$(python setup.py --version)" if [ "$VERSION" != "$TAG" ]; then echo "::error ::package version '$VERSION' does not match git tag '$TAG'" exit 1 fi echo "::set-output name=version::$VERSION" echo "::set-output name=anchor::${VERSION//[^[:alnum:]]/-}" - name: Install build requirements run: | pip install twine wheel - name: Build run: | rm -rf build dist django_anymail.egg-info python setup.py sdist bdist_wheel twine check dist/* - name: Publish package to PyPI env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} run: | twine upload dist/* - name: Publish release to GitHub env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAG: ${{ steps.version.outputs.version }} TITLE: ${{ steps.version.outputs.version }} NOTES: | [Changelog](https://anymail.readthedocs.io/en/stable/changelog/#${{ steps.version.outputs.anchor }}) run: | # gh release create-or-edit "$TAG" --title "$TITLE" --notes "$NOTES" ./dist/* # (gh release doesn't support edit - 6/2021) # (hub requires separate --attach=FILE arg for each file) FILES=(./dist/*) if ! hub release edit --message "$TITLE" --message "$NOTES" "${FILES[@]/#/--attach=}" "$TAG"; then hub release create --message "$TITLE" --message "$NOTES" "${FILES[@]/#/--attach=}" "$TAG" fi