aboutsummaryrefslogtreecommitdiffstats
path: root/.github/workflows/lint-test-build-push.yaml
blob: 92ca53e19de2dc172fcf2a2220071b1d4ee04c6e (plain) (blame)
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