aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.coveragerc1
-rw-r--r--.github/workflows/build.yaml96
-rw-r--r--.github/workflows/deploy.yaml96
-rw-r--r--.github/workflows/lint-test-build-push.yaml242
-rw-r--r--.github/workflows/lint.yaml73
-rw-r--r--.github/workflows/main.yaml26
-rw-r--r--.github/workflows/test.yaml97
7 files changed, 389 insertions, 242 deletions
diff --git a/.coveragerc b/.coveragerc
index cc2a148..ce475d7 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,5 +1,6 @@
[run]
branch = true
+data_file = ${COVERAGE_DATAFILE-.coverage}
include = snekbox/*
omit =
snekbox/api/app.py
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
new file mode 100644
index 0000000..28e5b69
--- /dev/null
+++ b/.github/workflows/build.yaml
@@ -0,0 +1,96 @@
+on:
+ workflow_call:
+ outputs:
+ artifact:
+ description: The name of the uploaded image aretfact.
+ value: ${{ jobs.build.outputs.artifact }}
+ tag:
+ description: The tag used for the built image.
+ value: ${{ jobs.build.outputs.tag }}
+
+jobs:
+ build:
+ name: Build snekbox-venv image
+ runs-on: ubuntu-latest
+ outputs:
+ artifact: ${{ env.artifact }}
+ tag: ${{ steps.sha_tag.outputs.tag }}
+ env:
+ artifact: image_artifact_snekbox-venv
+
+ steps:
+ # Create a short SHA with which to tag built images.
+ - name: Create SHA Container Tag
+ id: sha_tag
+ run: |
+ tag=$(cut -c 1-7 <<< $GITHUB_SHA)
+ echo "::set-output name=tag::$tag"
+
+ - name: Checkout code
+ uses: actions/checkout@v2
+
+ # The current version (v2) of Docker's build-push action uses buildx,
+ # which comes with BuildKit. It has cache features which can speed up
+ # the builds. See https://github.com/docker/build-push-action
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v1
+
+ - name: Log in to GitHub Container Registry
+ uses: docker/login-action@v1
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ # The image built for PRs may start to deviate from the "latest" image
+ # currently in GHCR. Configure the subsequent build step to cache the
+ # layers in GitHub Actions for PRs.
+ # See https://github.com/moby/buildkit#github-actions-cache-experimental
+ #
+ # Because the cache is scoped to the branch, it will not be available
+ # on the main branch when the PR is merged. Furthermore, using this cache
+ # on main is redundant since the previous build's images are already
+ # cached on GHCR. Thus, this step is only used for PRs.
+ - name: Configure cache
+ id: cache_config
+ run: |
+ set -eu
+ if [ "$GITHUB_EVENT_NAME" = 'pull_request' ]; then
+ cache_from="type=gha,scope=buildkit-${GITHUB_REF}"
+ cache_to="${cache_from},mode=max"
+ fi
+ echo "::set-output name=cache_from::${cache_from:-}"
+ echo "::set-output name=cache_to::${cache_to:-}"
+
+ # Build the "DEV" version of the image, which targets the `venv` stage
+ # and includes development dependencies.
+ #
+ # Include an inline cache manifest in the image to support caching from
+ # GHCR. This enables subsequent builds to pull reusable layers from GHCR.
+ # If configured by the cache_config step, also cache the layers in
+ # GitHub Actions.
+ - name: Build image for linting and testing
+ uses: docker/build-push-action@v2
+ with:
+ context: .
+ file: ./Dockerfile
+ push: false
+ target: venv
+ build-args: DEV=1
+ outputs: type=docker,dest=${{ env.artifact }}.tar
+ cache-from: |
+ ${{ steps.cache_config.outputs.cache_from }}
+ ghcr.io/python-discord/snekbox-base:latest
+ ghcr.io/python-discord/snekbox-venv:latest
+ cache-to: ${{ steps.cache_config.outputs.cache_to }}
+ tags: ghcr.io/python-discord/snekbox-venv:${{ steps.sha_tag.outputs.tag }}
+
+ # Make the image available as an artifact so other jobs will be able to
+ # download it.
+ - name: Upload image archive as an artifact
+ uses: actions/upload-artifact@v2
+ with:
+ name: ${{ env.artifact }}
+ path: ${{ env.artifact }}.tar
+ retention-days: 1 # It's only needed for the duration of the workflow.
+ if-no-files-found: error
diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml
new file mode 100644
index 0000000..c4020b9
--- /dev/null
+++ b/.github/workflows/deploy.yaml
@@ -0,0 +1,96 @@
+on:
+ workflow_call:
+ inputs:
+ artifact:
+ required: true
+ type: string
+ tag:
+ required: true
+ type: string
+
+jobs:
+ deploy:
+ name: Build, push, & deploy
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Download image artifact
+ uses: actions/download-artifact@v2
+ with:
+ name: ${{ inputs.artifact }}
+
+ # Load the image to make use of common layers during the final build.
+ - name: Load image from archive
+ run: docker load -i ${{ inputs.artifact }}.tar
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v1
+
+ - name: Log in to GitHub Container Registry
+ uses: docker/login-action@v1
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ # The Dockerfile will be needed.
+ - name: Checkout code
+ uses: actions/checkout@v2
+
+ # Build the final production image and push it to GHCR.
+ # Tag it with both the short commit SHA and 'latest'.
+ - name: Build final image
+ uses: docker/build-push-action@v2
+ with:
+ context: .
+ file: ./Dockerfile
+ push: true
+ cache-from: |
+ ghcr.io/python-discord/snekbox-base:latest
+ ghcr.io/python-discord/snekbox-venv:latest
+ ghcr.io/python-discord/snekbox:latest
+ cache-to: type=inline
+ tags: |
+ ghcr.io/python-discord/snekbox:latest
+ ghcr.io/python-discord/snekbox:${{ inputs.tag }}
+ build-args: git_sha=${{ github.sha }}
+
+ # Deploy to Kubernetes.
+ - name: Authenticate with Kubernetes
+ uses: azure/k8s-set-context@v1
+ with:
+ method: kubeconfig
+ kubeconfig: ${{ secrets.KUBECONFIG }}
+
+ - name: Deploy to Kubernetes
+ uses: Azure/k8s-deploy@v1
+ with:
+ manifests: deployment.yaml
+ images: 'ghcr.io/python-discord/snekbox:${{ inputs.tag }}'
+ kubectl-version: 'latest'
+
+ # Push the base image to GHCR, with an inline cache manifest.
+ - name: Push base image
+ uses: docker/build-push-action@v2
+ with:
+ context: .
+ file: ./Dockerfile
+ target: base
+ push: true
+ cache-from: ghcr.io/python-discord/snekbox-base:latest
+ cache-to: type=inline
+ tags: ghcr.io/python-discord/snekbox-base:latest
+
+ # Push the venv image to GHCR, with an inline cache manifest.
+ - name: Push venv image
+ uses: docker/build-push-action@v2
+ with:
+ context: .
+ file: ./Dockerfile
+ target: venv
+ push: true
+ cache-from: |
+ ghcr.io/python-discord/snekbox-base:latest
+ ghcr.io/python-discord/snekbox-venv:latest
+ cache-to: type=inline
+ tags: ghcr.io/python-discord/snekbox-venv:latest
diff --git a/.github/workflows/lint-test-build-push.yaml b/.github/workflows/lint-test-build-push.yaml
deleted file mode 100644
index 0df0b32..0000000
--- a/.github/workflows/lint-test-build-push.yaml
+++ /dev/null
@@ -1,242 +0,0 @@
-name: Lint, Test, Build, Push
-
-on:
- push:
- branches:
- - main
- pull_request:
-
-
-jobs:
- lint-test-build-push:
- runs-on: ${{ matrix.os }}
- strategy:
- matrix:
- os: [ubuntu-20.04, self-hosted]
- include:
- - os: ubuntu-20.04
- full: true
- - os: self-hosted
- full: false # Only run tests.
-
- env:
- # Determine whether or not we should build the
- # final production image and push it to GHCR.
- production_build: ${{ github.event_name != 'pull_request' &&
- github.ref == 'refs/heads/main' }}
-
- steps:
- # Create a short SHA-tag to tag built images
- - name: Create SHA Container Tag
- id: sha_tag
- run: |
- tag=$(cut -c 1-7 <<< $GITHUB_SHA)
- echo "::set-output name=tag::$tag"
-
- - name: Checkout code
- uses: actions/checkout@v2
-
- # The current version (v2) of Docker's build-push action uses
- # buildx, which comes with BuildKit features that help us speed
- # up our builds using additional cache features. Buildx also
- # has a lot of other features that are not as relevant to us.
- #
- # See https://github.com/docker/build-push-action
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v1
-
- - name: Login to Github Container Registry
- uses: docker/login-action@v1
- with:
- registry: ghcr.io
- username: ${{ github.repository_owner }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- # Create a local cache directory for PR builds, as the image
- # we build for PRs may start to deviate from the "latest" image
- # currently registered in the GHCR. For main, the best we can
- # do is use the previous main build, which can be cached from
- # the GHCR.
- - name: Cache Image Layers
- if: github.event_name == 'pull_request'
- uses: actions/cache@v2
- with:
- path: /tmp/.buildx-cache
- key: ${{ runner.os }}-v0-buildx-${{ github.ref }}-${{ github.sha }}
- restore-keys: |
- ${{ runner.os }}-v0-buildx-${{ github.ref }}-
-
- # Build the image we need for linting and testing using the
- # `venv` target stage within our Dockerfile. We load the image
- # into the runner's Docker image collection so we can run it
- # later.
- #
- # The image includes an inline cache manifest to support caching
- # from the GHCR, which means that a build can pull the layers it
- # can reuse instead of building them from scratch.
- - name: Build image for linting and testing
- uses: docker/build-push-action@v2
- with:
- context: .
- file: ./Dockerfile
- push: false
- load: true
- target: venv
- build-args: DEV=1
- cache-from: |
- type=local,src=/tmp/.buildx-cache
- ghcr.io/python-discord/snekbox-base:latest
- ghcr.io/python-discord/snekbox-venv:latest
- cache-to: type=local,dest=/tmp/.buildx-cache,mode=max
- tags: ghcr.io/python-discord/snekbox-venv:${{ steps.sha_tag.outputs.tag }}
-
- - name: Start Container
- run: |
- export IMAGE_SUFFIX='-venv:${{ steps.sha_tag.outputs.tag }}'
- docker-compose up --no-build -d
-
- # Required by pre-commit.
- - name: Install git
- if: matrix.full
- run: >-
- docker exec snekbox_dev /bin/bash -c
- 'apt-get -y update && apt-get install -y git=1:2.20.*'
-
- # pre-commit's venv doesn't work with user installs.
- # Skip the flake8 hook because the following step will run it.
- - name: Run pre-commit hooks
- id: run-pre-commit-hooks
- if: matrix.full
- run: >-
- docker exec snekbox_dev /bin/bash -c
- 'PIP_USER=0 SKIP=flake8 pre-commit run --all-files'
-
- - name: Show pre-commit logs
- if: matrix.full && always() && steps.run-pre-commit-hooks.outcome != 'success'
- run: >-
- docker exec snekbox_dev /bin/bash -c
- 'cat /root/.cache/pre-commit/pre-commit.log'
-
- # This runs `flake8` in the container and asks `flake8` to output
- # linting errors in the format of the command for registering workflow
- # error messages/annotations. This means that Github Actions will pick
- # up on this output to generate nice annotations to indicate what went
- # wrong where.
- - name: Run linter
- if: matrix.full
- run: >-
- docker exec snekbox_dev /bin/bash -c
- 'flake8 --format
- "::error file=%(path)s,line=%(row)d,col=%(col)d::[flake8] %(code)s: %(text)s"'
-
- # Memory limit tests would fail if this isn't disabled.
- - name: Disable swap memory
- run: sudo swapoff -a
-
- # Run unittests and generate coverage report in the container
- - name: Run unit tests
- id: run_tests
- run: |
- echo '::set-output name=started::true'
- docker exec snekbox_dev /bin/bash -c 'coverage run -m unittest'
-
- - name: Generate coverage report
- if: always() && steps.run_tests.outputs.started == 'true'
- run: docker exec snekbox_dev /bin/bash -c 'coverage report -m'
-
- # Set-up a Python version to process the coverage reports
- # Note: This step runs even if the test step failed to make
- # sure we process the coverage reports.
- - name: Setup python
- if: matrix.os != 'self-hosted' && always() && steps.run_tests.outputs.started == 'true'
- id: python
- uses: actions/setup-python@v2
- with:
- python-version: '3.10'
-
- # We'll only ever need a single dependency in this python
- # environment and we'll only use it in the CI, so let's
- # install it directly here and run it.
- #
- # This step will publish the coverage results to coveralls.io
- # print a job link in the output. It will also register a
- # step in the check suite visible in the PR with a link to
- # the job.
- - name: Publish coverage report to coveralls.io
- if: always() && steps.run_tests.outputs.started == 'true'
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
- pip install coveralls~=2.1
- coveralls
-
- # Final build stage. This is run in the same job with conditions
- # in order to use the local build cache generated by buildx while
- # building the `venv` image in the lint/test phase.
-
- # Build the final production image and push it to GHCR, tagging it
- # both with the short commit SHA and 'latest'. This step should use
- # the local build cache of the current run.
- - name: Build final image
- if: matrix.full && env.production_build == 'true'
- uses: docker/build-push-action@v2
- with:
- context: .
- file: ./Dockerfile
- push: true
- cache-from: |
- ghcr.io/python-discord/snekbox-base:latest
- ghcr.io/python-discord/snekbox-venv:latest
- ghcr.io/python-discord/snekbox:latest
- cache-to: type=inline
- tags: |
- ghcr.io/python-discord/snekbox:latest
- ghcr.io/python-discord/snekbox:${{ steps.sha_tag.outputs.tag }}
- build-args: |
- git_sha=${{ github.sha }}
-
- # Deploy to Kubernetes
- - name: Authenticate with Kubernetes
- if: matrix.full && env.production_build == 'true'
- uses: azure/k8s-set-context@v1
- with:
- method: kubeconfig
- kubeconfig: ${{ secrets.KUBECONFIG }}
-
- - name: Deploy to Kubernetes
- if: matrix.full && env.production_build == 'true'
- uses: Azure/k8s-deploy@v1
- with:
- manifests: |
- deployment.yaml
- images: 'ghcr.io/python-discord/snekbox:${{ steps.sha_tag.outputs.tag }}'
- kubectl-version: 'latest'
-
- # Push the base image to GHCR, with an inline cache manifest
- - name: Push base image
- if: matrix.full && env.production_build == 'true'
- uses: docker/build-push-action@v2
- with:
- context: .
- file: ./Dockerfile
- target: base
- push: true
- cache-from: |
- ghcr.io/python-discord/snekbox-base:latest
- cache-to: type=inline
- tags: ghcr.io/python-discord/snekbox-base:latest
-
- # Push the venv image to GHCR, with an inline cache manifest
- - name: Push venv image
- if: matrix.full && env.production_build == 'true'
- uses: docker/build-push-action@v2
- with:
- context: .
- file: ./Dockerfile
- target: venv
- push: true
- cache-from: |
- ghcr.io/python-discord/snekbox-base:latest
- ghcr.io/python-discord/snekbox-venv:latest
- cache-to: type=inline
- tags: ghcr.io/python-discord/snekbox-venv:latest
diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml
new file mode 100644
index 0000000..8f530ab
--- /dev/null
+++ b/.github/workflows/lint.yaml
@@ -0,0 +1,73 @@
+on:
+ workflow_call:
+
+jobs:
+ lint:
+ name: Lint
+ runs-on: ubuntu-latest
+ env:
+ PIP_DISABLE_PIP_VERSION_CHECK: 1
+ PIP_NO_CACHE_DIR: false
+ PIP_USER: 1 # Make dependencies install into PYTHONUSERBASE.
+
+ PIPENV_DONT_USE_PYENV: 1
+ PIPENV_HIDE_EMOJIS: 1
+ PIPENV_NOSPIN: 1
+
+ PYTHONUSERBASE: ${{ github.workspace }}/.cache/py-user-base
+ PRE_COMMIT_HOME: ${{ github.workspace }}/.cache/pre-commit-cache
+
+ steps:
+ - name: Add custom PYTHONUSERBASE to PATH
+ run: echo '${{ env.PYTHONUSERBASE }}/bin/' >> $GITHUB_PATH
+
+ - name: Checkout code
+ uses: actions/checkout@v2
+
+ - name: Set up Python
+ id: python
+ uses: actions/setup-python@v2
+ with:
+ python-version: "3.10"
+
+ - name: Python dependency cache
+ uses: actions/cache@v2
+ id: python_cache
+ with:
+ path: ${{ env.PYTHONUSERBASE }}
+ key: "python-0-${{ runner.os }}-${{ env.PYTHONUSERBASE }}-\
+ ${{ steps.python.outputs.python-version }}-\
+ ${{ hashFiles('./Pipfile', './Pipfile.lock') }}"
+
+ # Install dependencies if there was a cache miss.
+ - name: Install Python dependencies
+ if: steps.python_cache.outputs.cache-hit != 'true'
+ run: >-
+ pip install pipenv==2021.11.23
+ && pipenv install --deploy --system --dev
+
+ - name: Pre-commit environment cache
+ uses: actions/cache@v2
+ with:
+ path: ${{ env.PRE_COMMIT_HOME }}
+ key: "precommit-0-${{ runner.os }}-${{ env.PRE_COMMIT_HOME }}-\
+ ${{ steps.python.outputs.python-version }}-\
+ ${{ hashFiles('./.pre-commit-config.yaml') }}"
+
+ # pre-commit's venv doesn't work with user installs.
+ # Skip the flake8 hook because the following step will run it.
+ - name: Run pre-commit hooks
+ id: run-pre-commit-hooks
+ run: PIP_USER=0 SKIP=flake8 pre-commit run --all-files
+
+ # Show the log to debug failures.
+ - name: Show pre-commit log
+ if: always() && steps.run-pre-commit-hooks.outcome == 'failure'
+ run: cat "${PRE_COMMIT_HOME}/pre-commit.log"
+
+ # Output linting errors in the format GitHub Actions recognises for
+ # annotations.
+ - name: Run flake8
+ run: >-
+ flake8 --format "::error
+ file=%(path)s,line=%(row)d,col=%(col)d::[flake8] %(code)s: %(text)s"
diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
new file mode 100644
index 0000000..516a1d6
--- /dev/null
+++ b/.github/workflows/main.yaml
@@ -0,0 +1,26 @@
+name: main
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+
+jobs:
+ build:
+ uses: ./.github/workflows/build.yaml
+ lint:
+ uses: ./.github/workflows/lint.yaml
+ test:
+ uses: ./.github/workflows/test.yaml
+ needs: build
+ with:
+ artifact: ${{ needs.build.outputs.artifact }}
+ tag: ${{ needs.build.outputs.tag }}
+ deploy:
+ uses: ./.github/workflows/deploy.yaml
+ if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' }}
+ needs: [build, lint, test]
+ with:
+ artifact: ${{ needs.build.outputs.artifact }}
+ tag: ${{ needs.build.outputs.tag }}
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
new file mode 100644
index 0000000..77ef1fc
--- /dev/null
+++ b/.github/workflows/test.yaml
@@ -0,0 +1,97 @@
+on:
+ workflow_call:
+ inputs:
+ artifact:
+ required: true
+ type: string
+ tag:
+ required: true
+ type: string
+
+jobs:
+ test:
+ name: Test with coverage
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-20.04, self-hosted]
+
+ steps:
+ - name: Download image artifact
+ uses: actions/download-artifact@v2
+ with:
+ name: ${{ inputs.artifact }}
+
+ - name: Load image from archive
+ run: docker load -i ${{ inputs.artifact }}.tar
+
+ # Needed for the Docker Compose file.
+ - name: Checkout code
+ uses: actions/checkout@v2
+
+ # Memory limit tests would fail if this isn't disabled.
+ - name: Disable swap memory
+ run: sudo swapoff -a
+
+ # Run tests with coverage within the container.
+ # Suffix the generated coverage datafile with the name of the runner's OS.
+ - name: Run tests
+ id: run_tests
+ run: |
+ export IMAGE_SUFFIX='-venv:${{ inputs.tag }}'
+ docker-compose run \
+ --rm -T -e COVERAGE_DATAFILE=.coverage.${{ matrix.os }} \
+ snekbox \
+ coverage run -m unittest
+
+ # Upload it so the coverage from all matrix jobs can be combined later.
+ - name: Upload coverage data
+ uses: actions/upload-artifact@v2
+ with:
+ name: coverage
+ path: .coverage.*
+ retention-days: 1
+
+ # Self-hosted runner needs containers, images, networks, volumes, etc.
+ # removed because they persist across runs and may interfere.
+ - name: Docker cleanup
+ if: matrix.os == 'self-hosted' && always()
+ run: |
+ export IMAGE_SUFFIX='-venv:${{ inputs.tag }}'
+ docker-compose down --rmi all --remove-orphans -v -t 0
+
+ report:
+ name: Report coverage
+ runs-on: ubuntu-20.04
+ needs: test
+
+ steps:
+ # Needed for the .coveragerc file.
+ - name: Checkout code
+ uses: actions/checkout@v2
+
+ - name: Set up Python
+ uses: actions/setup-python@v2
+ with:
+ python-version: "3.10"
+
+ - name: Install dependencies
+ run: pip install coverage~=6.0 coveralls~=3.3
+
+ - name: Download coverage data
+ uses: actions/download-artifact@v2
+ with:
+ name: coverage
+
+ - name: Combine coverage data
+ run: coverage combine .coverage.*
+
+ - name: Display coverage report
+ run: coverage report -m
+
+ # Comment on the PR with the coverage results and register a GitHub check
+ # which links to the coveralls.io job.
+ - name: Publish coverage report to coveralls.io
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: coveralls --service=github