1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
|
name: Lint, Test, Build, Push
on:
push:
branches:
- main
pull_request:
jobs:
lint-test-build-push:
runs-on: ubuntu-latest
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
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
run: >-
docker exec snekbox_dev /bin/bash -c
'PIP_USER=0 SKIP=flake8 pre-commit run --all-files'
# 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
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: always() && steps.run_tests.outputs.started == 'true'
id: python
uses: actions/setup-python@v2
with:
python-version: '3.9'
# 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: 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: env.production_build == 'true'
uses: azure/k8s-set-context@v1
with:
method: kubeconfig
kubeconfig: ${{ secrets.KUBECONFIG }}
- name: Deploy to Kubernetes
if: 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: 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: 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
|