digitalfabrik/integreat-cms

View on GitHub
.circleci/config.yml

Summary

Maintainability
Test Coverage
version: 2.1

orbs:
  shellcheck: circleci/shellcheck@3.2.0
jobs:
  pip-install:
    docker:
      - image: cimg/python:3.11.7
    resource_class: small
    steps:
      - checkout
      - restore_cache:
          key: pip-{{ checksum "pyproject.toml" }}-v1
      - run:
          name: Install pip dependencies
          command: |
            if [[ -d ".venv" ]]; then
              echo "Virtual environment restored from cache, skipping pip install"
            else
              python3 -m venv .venv
              source .venv/bin/activate
              pip install -e .[dev-pinned,pinned]
            fi
      - save_cache:
          key: pip-{{ checksum "pyproject.toml" }}-v1
          paths:
            - .venv
            - integreat_cms.egg-info
            - /home/circleci/.cache/pip
      - persist_to_workspace:
          root: .
          paths:
            - .venv
            - integreat_cms.egg-info
  black:
    docker:
      - image: cimg/python:3.11.7
    resource_class: small
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Enable virtual environment
          command: echo "source .venv/bin/activate" >> $BASH_ENV
      - run:
          name: Check black code style
          command: black --check .
  djlint:
    docker:
      - image: cimg/python:3.11.7
    resource_class: small
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Enable virtual environment
          command: echo "source .venv/bin/activate" >> $BASH_ENV
      - run:
          name: Check formatting of Django templates
          command: djlint --check --lint integreat_cms
  pylint:
    docker:
      - image: cimg/python:3.11.7
    resource_class: small
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Run pylint
          command: ./tools/pylint.sh
  ruff:
    docker:
      - image: cimg/python:3.11.7
    resource_class: small
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Enable virtual environment
          command: echo "source .venv/bin/activate" >> $BASH_ENV
      - run:
          name: Run ruff
          command: |
            ruff check
            # ruff format
  mypy:
    docker:
      - image: cimg/python:3.11.7
    resource_class: small
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Run my[py]
          command: ./tools/mypy.sh
  check-release-notes:
    docker:
      - image: cimg/python:3.11.7
    resource_class: small
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Install requirements
          command: sudo apt-get update && sudo apt-get install pcregrep
      - run:
          name: Check release notes in Markdown
          command: ./tools/make_release_notes.sh --format=md --all
      - run:
          name: Check release notes in reStructuredText
          command: ./tools/make_release_notes.sh --format=rst --all
      - run:
          name: Check release notes in raw format
          command: ./tools/make_release_notes.sh --format=raw --all
  check-translations:
    docker:
      - image: cimg/python:3.11.7
    resource_class: small
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Install translation requirements
          command: sudo apt-get update && sudo apt-get install gettext pcregrep
      - run:
          name: Check translation file for missing or empty entries
          command: ./tools/check_translations.sh
  compile-translations:
    docker:
      - image: cimg/python:3.11.7
    resource_class: small
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Install gettext
          command: sudo apt-get update && sudo apt-get install gettext
      - run:
          name: Enable virtual environment
          command: echo "source .venv/bin/activate" >> $BASH_ENV
      - run:
          name: Compile translation file
          command: |
            cd integreat_cms
            integreat-cms-cli compilemessages --settings=integreat_cms.core.circleci_settings
      - persist_to_workspace:
          root: .
          paths:
            - integreat_cms/locale/*/LC_MESSAGES/django.mo
  npm-install:
    docker:
      - image: "cimg/node:lts"
    resource_class: small
    steps:
      - checkout
      - restore_cache:
          keys:
            - npm-{{ checksum "package-lock.json" }}-v2
      - run:
          name: Install npm dependencies
          command: |
            if [[ -d "node_modules" ]]; then
              echo "Node modules restored from cache, skipping npm install"
            else
              npm ci
              if [[ -n $(git diff --shortstat package-lock.json) ]]; then
                echo "package-lock.json is out of date:"
                git --no-pager diff package-lock.json
                exit 1
              fi
            fi
      - save_cache:
          key: npm-{{ checksum "package-lock.json" }}-v2
          paths:
            - node_modules
      - persist_to_workspace:
          root: .
          paths:
            - node_modules
  prettier:
    docker:
      - image: "cimg/node:lts"
    resource_class: small
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Check formatting of CSS & JS files
          command: npx prettier --check .
  vitest:
    docker:
      - image: "cimg/node:lts"
    resource_class: small
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Run frontend tests
          command: npm run test
  eslint:
    docker:
      - image: "cimg/node:lts"
    resource_class: small
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Lint static CSS, JS & YAML files
          command: npx eslint .
  shellcheck:
    docker:
      - image: cimg/base:stable
    steps:
      - checkout
      - shellcheck/install:
          version: 0.9.0
      - shellcheck/check:
          dir: ./tools
          external_sources: true
  webpack:
    docker:
      - image: "cimg/node:lts"
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Compile and bundle CSS and JS
          command: npm run prod
      - persist_to_workspace:
          root: .
          paths:
            - integreat_cms/static/dist
            - integreat_cms/webpack-stats.json
  check-migrations:
    docker:
      - image: cimg/python:3.11.7
      - image: cimg/postgres:14.1
        environment:
          POSTGRES_USER: integreat
          POSTGRES_DB: integreat
          POSTGRES_PASSWORD: password
    resource_class: small
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Enable virtual environment
          command: echo "source .venv/bin/activate" >> $BASH_ENV
      - run:
          name: Check for missing migrations
          command: integreat-cms-cli makemigrations cms --check --settings=integreat_cms.core.circleci_settings
  setup-test-reporter:
    docker:
      - image: cimg/base:stable
    resource_class: small
    steps:
      - attach_workspace:
          at: .
      - run:
          name: Install CodeClimate Test Reporter
          command: |
            curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
            chmod +x ./cc-test-reporter
      - run:
          name: Notify CodeClimate of a pending report
          command: ./cc-test-reporter before-build
      - persist_to_workspace:
          root: .
          paths:
            - cc-test-reporter
  test:
    docker:
      - image: cimg/python:3.11.7
      - image: cimg/postgres:14.1
        environment:
          POSTGRES_USER: integreat
          POSTGRES_DB: integreat
          POSTGRES_PASSWORD: password
    resource_class: large
    parallelism: 16
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Enable virtual environment
          command: echo "source .venv/bin/activate" >> $BASH_ENV
      - run:
          name: Migrate database
          command: integreat-cms-cli migrate --settings=integreat_cms.core.circleci_settings
      - run:
          name: Run tests
          command: pytest --circleci-parallelize --disable-warnings --cov=integreat_cms --cov-report xml --junitxml=test-results/junit.xml  --ds=integreat_cms.core.circleci_settings
      - run:
          name: Format test coverage
          command: ./cc-test-reporter format-coverage -t coverage.py -o "coverage/codeclimate.$CIRCLE_NODE_INDEX.json"
      - store_test_results:
          path: test-results
      - store_artifacts:
          path: test-results
      - persist_to_workspace:
          root: .
          paths:
            - cc-test-reporter
            - coverage
      - run:
          name: Copy CMS log
          command: cp integreat_cms/integreat-cms.log integreat-cms.log
          when: on_fail
      - store_artifacts:
          path: integreat-cms.log
  upload-test-coverage:
    docker:
      - image: cimg/base:stable
    resource_class: small
    steps:
      - attach_workspace:
          at: .
      - run:
          name: Install CodeClimate Test Reporter
          command: |
            curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
            chmod +x ./cc-test-reporter
      - run:
          name: Sum coverage data and upload to CodeClimate
          command: |
            ./cc-test-reporter sum-coverage -o - coverage/codeclimate.*.json | ./cc-test-reporter upload-coverage --debug --input -
  build-package:
    docker:
      - image: cimg/python:3.11.7
    resource_class: small
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Enable virtual environment
          command: echo "source .venv/bin/activate" >> $BASH_ENV
      - run:
          name: Use alternative README.md file
          command: mv integreat_cms/README.md .
      - run:
          name: Build integreat-cms package
          command: python3 -m build
      - persist_to_workspace:
          root: .
          paths:
            - dist
  publish-package:
    docker:
      - image: cimg/python:3.11.7
    resource_class: small
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Enable virtual environment
          command: echo "source .venv/bin/activate" >> $BASH_ENV
      - run:
          name: Publish integreat-cms package to (Test-)PyPI
          command: twine upload --non-interactive ./dist/integreat_cms-*.tar.gz
  build-documentation:
    docker:
      - image: cimg/python:3.11.7
    resource_class: large
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Install requirements
          command: sudo apt-get update && sudo apt-get install pcregrep
      - run:
          name: Generate documentation
          command: ./tools/make_docs.sh
      - persist_to_workspace:
          root: .
          paths:
            - docs/dist
  deploy-documentation:
    docker:
      - image: cimg/python:3.11.7
    resource_class: small
    environment:
      BRANCH: gh-pages
      DOC_DIR: docs/dist
      TMP_DIR: .gh-pages
    steps:
      - attach_workspace:
          at: .
      - add_ssh_keys:
          fingerprints: aa:6c:35:24:8a:80:d8:6a:f3:d7:b1:b3:49:42:a2:b3
      - run:
          name: Add GitHub's Public SSH Key to known hosts
          command: echo 'github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=' >> ~/.ssh/known_hosts
      - run:
          name: Prepare git config
          command: |
            git config --global user.name DigitalfabrikMember
            git config --global user.email 41921676+DigitalfabrikMember@users.noreply.github.com
      - run:
          name: Clone existing gh-pages branch into temporary directory
          command: git clone --depth=1 $CIRCLE_REPOSITORY_URL -b $BRANCH $TMP_DIR
      - run:
          when: on_fail
          name: Initialize gh-pages branch in new temporary git directory
          command: |
            git init $TMP_DIR
            cd $TMP_DIR
            git remote add origin $CIRCLE_REPOSITORY_URL
            git checkout -b $BRANCH
      - run:
          name: Copy documentation into temporary directory
          command: |
            rm -rfv ${TMP_DIR}/*
            cp -Rv ${TMP_DIR}/../${DOC_DIR}/. $TMP_DIR
      - run:
          name: Push documentation to GitHub Pages
          command: |
            cd $TMP_DIR
            git add --all
            git commit -m "Update documentation of commit ${CIRCLE_SHA1} [skip ci]" || true
            git push origin $BRANCH
  bump-dev-version:
    docker:
      - image: cimg/python:3.11.7
    resource_class: small
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Enable virtual environment
          command: echo "source .venv/bin/activate" >> $BASH_ENV
      - run:
          name: Bump version
          command: |
            # Install recent version of pip
            echo "Upgrade pip to make sure 'pip index' is available"
            pip install --upgrade pip
            # Check which versions of integreat-cms are available on the TestPyPI repository
            AVAILABLE_VERSIONS=$(pip index versions integreat-cms --pre -i https://test.pypi.org/simple/)
            echo "Current available versions on TestPyPI: ${AVAILABLE_VERSIONS}"
            CURRENT_ALPHA_VERSION=$(echo "${AVAILABLE_VERSIONS}" | head -n 1)
            echo "Most recent version on TestPyPI: ${CURRENT_ALPHA_VERSION}"
            CURRENT_ALPHA_VERSION=$(echo "${CURRENT_ALPHA_VERSION}" | sed "s/integreat-cms (\([^()]*\)a0)/\1-alpha/")
            echo "Version converted to alternative format: ${CURRENT_ALPHA_VERSION}"
            # Get current prod version
            CURRENT_VERSION=$(python -c "import integreat_cms; print(integreat_cms.__version__)")
            echo "Current production version: ${CURRENT_VERSION}"
            # Bump version to current alpha version if it is newer
            # Attention: exit(True) in Python means exit(1) which is False in Bash :)
            if python -c "from bumpver.version import parse_version; exit(parse_version('${CURRENT_VERSION}') > parse_version('${CURRENT_ALPHA_VERSION}'))"; then
              echo "Bump to the currently existing version"
              bumpver update -n --set-version="${CURRENT_ALPHA_VERSION}" --no-commit
            fi
            # Bump version to next alpha version
            echo "Bump to the next version"
            bumpver update -n -t alpha --no-commit
      - persist_to_workspace:
          root: .
          paths:
            - pyproject.toml
            - integreat_cms/__init__.py
  bump-version:
    docker:
      - image: cimg/python:3.11.7
    resource_class: small
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Install requirements
          command: sudo apt-get update && sudo apt-get install pcregrep
      - run:
          name: Enable virtual environment
          command: echo "source .venv/bin/activate" >> $BASH_ENV
      - run:
          name: Request installation access token to authorize as Deliverino app
          command: echo "export DELIVERINO_ACCESS_TOKEN=$(./.circleci/scripts/get_access_token.py)" >> $BASH_ENV
      - run:
          name: Config git repository to commit & push as Deliverino app
          command: |
            git config user.name "deliverino[bot]"
            git config user.email "62934656+deliverino[bot]@users.noreply.github.com"
            git remote set-url origin "https://x-access-token:$DELIVERINO_ACCESS_TOKEN@github.com/digitalfabrik/integreat-cms.git"
      - run:
          name: Bump version
          command: bumpver update --tag=final
      - run:
          name: Get tag of newly created version
          command: |
            eval $(bumpver show --env -n)
            echo "export CURRENT_VERSION=$CURRENT_VERSION" >> $BASH_ENV
      - run:
          name: Update release notes
          command: |
            # Move "unreleased" entries to new version directory
            OLD_DIR="integreat_cms/release_notes/current/unreleased"
            NEW_DIR="integreat_cms/release_notes/$(echo "${CURRENT_VERSION}" | cut -c1-4)"
            mkdir -p "${NEW_DIR}"
            git mv "${OLD_DIR}" "${NEW_DIR}/${CURRENT_VERSION}"
            git commit --amend --no-edit
      - run:
          name: Tag and push commit
          command: |
            # Get most recent release notes
            RELEASE_NOTES=$(./tools/make_release_notes.sh --format=raw --no-subheading)
            git tag --annotate "${CURRENT_VERSION}" --message "${RELEASE_NOTES}"
            git push origin --follow-tags "${CURRENT_VERSION}" HEAD
      - run:
          name: Merge version bump into develop
          command: git checkout develop && git merge main --commit --no-edit && git push
  create-release:
    docker:
      - image: cimg/python:3.11.7
    resource_class: small
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Install requirements
          command: sudo apt-get update && sudo apt-get install pcregrep
      - run:
          name: Enable virtual environment
          command: echo "source .venv/bin/activate" >> $BASH_ENV
      - run:
          name: Request installation access token to authorize as Deliverino app
          command: echo "export DELIVERINO_ACCESS_TOKEN=$(./.circleci/scripts/get_access_token.py)" >> $BASH_ENV
      - run:
          name: Get previous version tag
          command: |
            PREV_TAG=$(git describe --abbrev=0 --tags "${CIRCLE_TAG}^") || true
            echo "export PREV_TAG=\"${PREV_TAG}\"" >> $BASH_ENV
      - run:
          name: Get release notes
          command: |
            RELEASE_NOTES=$(./tools/make_release_notes.sh --format=raw --no-heading --no-subheading | sed "s/\"/'/g")
            echo "export RELEASE_NOTES=\"${RELEASE_NOTES}\"" >> $BASH_ENV
      - run:
          name: Get contributors
          command: |
            CONTRIBUTORS=$(./.circleci/scripts/get_contributors.py "${DELIVERINO_ACCESS_TOKEN}" "${PREV_TAG}" "${CIRCLE_TAG}" -v)
            echo "export CONTRIBUTORS=\"${CONTRIBUTORS}\"" >> $BASH_ENV
      - run:
          name: Create release as Deliverino app
          command: ./.circleci/scripts/create_release.py "${DELIVERINO_ACCESS_TOKEN}" "${CIRCLE_TAG}" "${PREV_TAG}" "${RELEASE_NOTES}" "${CONTRIBUTORS}" ./dist/integreat_cms-*.tar.gz
  notify-mattermost:
    docker:
      - image: cimg/base:stable
    resource_class: small
    steps:
      - checkout
      - run:
          name: Install requirements
          command: sudo apt-get update && sudo apt-get install pcregrep
      - run:
          name: Notify mattermost about release
          command: |
            # Get most recent release notes
            RELEASE_NOTES=$(./tools/make_release_notes.sh --format=md --no-heading --no-subheading | sed "s/\"/'/g")
            # Build notification message
            MM_MESSAGE="##### Integreat CMS version [${CIRCLE_TAG}](https://github.com/digitalfabrik/integreat-cms/releases/tag/${CIRCLE_TAG}) has been released successfully :tada:\n\n###### **Release Notes:**\n\n${RELEASE_NOTES}"
            # Send message to mattermost
            STATUS=$(curl -o /dev/null -s -w "%{http_code}\n" -X POST -H 'Content-type: application/json' \
              --data \
              "{
                \"channel\": \"releases\",
                \"username\": \"circleci\",
                \"icon_emoji\": \":integreat:\",
                \"text\": \"${MM_MESSAGE}\"
              }" "${MM_WEBHOOK}")
            if [ "$STATUS" -ne "200" ]; then
              echo "Notification not sent due to an error (HTTP status: ${STATUS})."
              exit 1
            fi
            echo "Notification sent!"

workflows:
  develop:
    jobs:
      - pip-install:
          filters:
            branches:
              ignore: main
      - npm-install:
          filters:
            branches:
              ignore: main
      - prettier:
          requires:
            - npm-install
      - vitest:
          requires:
            - npm-install
      - webpack:
          requires:
            - npm-install
      - check-migrations:
          requires:
            - pip-install
      - setup-test-reporter:
          context: codeclimate
          filters:
            branches:
              ignore: main
      - test:
          requires:
            - pip-install
            - webpack
            - compile-translations
            - setup-test-reporter
      - upload-test-coverage:
          context: codeclimate
          requires:
            - test
          filters:
            branches:
              only: /^(?!pull\/).*$/
      - check-release-notes:
          filters:
            branches:
              ignore: main
      - check-translations:
          requires:
            - pip-install
      - compile-translations:
          requires:
            - pip-install
      - bump-dev-version:
          filters:
            branches:
              only:
                - develop
                - /.*-publish-dev-package/
          requires:
            - pip-install
      - build-package:
          name: build-dev-package
          requires:
            - webpack
            - compile-translations
            - bump-dev-version
      - publish-package:
          name: publish-dev-package
          context: pypi-test
          filters:
            branches:
              only:
                - develop
                - /.*-publish-dev-package/
          requires:
            - build-dev-package
      - black:
          requires:
            - pip-install
      - djlint:
          requires:
            - pip-install
      - pylint:
          requires:
            - pip-install
      - ruff:
          requires:
            - pip-install
      - mypy:
          requires:
            - pip-install
      - eslint:
          requires:
            - npm-install
      - shellcheck:
          filters:
            branches:
              ignore: main
      - build-documentation:
          requires:
            - pip-install
      - deploy-documentation:
          requires:
            - build-documentation
          filters:
            branches:
              only: develop
  main:
    jobs:
      - pip-install:
          name: pip-install-main
          filters:
            branches:
              only: main
      - bump-version:
          context: deliverino
          requires:
            - pip-install-main
  deploy:
    jobs:
      - pip-install:
          name: pip-install-deploy
          filters:
            tags:
              only: /.*/
            branches:
              ignore: /.*/
      - npm-install:
          name: npm-install-deploy
          filters:
            tags:
              only: /.*/
            branches:
              ignore: /.*/
      - webpack:
          name: webpack-deploy
          requires:
            - npm-install-deploy
          filters:
            tags:
              only: /.*/
      - compile-translations:
          name: compile-translations-deploy
          requires:
            - pip-install-deploy
          filters:
            tags:
              only: /.*/
      - build-package:
          requires:
            - webpack-deploy
            - compile-translations-deploy
          filters:
            tags:
              only: /.*/
      - publish-package:
          context: pypi
          requires:
            - build-package
          filters:
            tags:
              only: /.*/
      - create-release:
          context: deliverino
          requires:
            - publish-package
          filters:
            tags:
              only: /.*/
      - notify-mattermost:
          context: mattermost
          requires:
            - create-release
          filters:
            tags:
              only: /.*/