diff options
Diffstat (limited to '.github/workflows/lint-test-build-push.yaml')
-rw-r--r-- | .github/workflows/lint-test-build-push.yaml | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/.github/workflows/lint-test-build-push.yaml b/.github/workflows/lint-test-build-push.yaml new file mode 100644 index 0000000..a2ebbb5 --- /dev/null +++ b/.github/workflows/lint-test-build-push.yaml @@ -0,0 +1,206 @@ +name: Lint, Test, Build, Push + +on: + push: + branches: + - sebastiaan/backend/cache-docker-images + + +jobs: + lint-test: + 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/sebastiaan/backend/cache-docker-images' }} + + 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 + + - 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.GHCR_TOKEN }} + + # Set up a caching directory for image layers. According to the docker + # documentation, it's recommended to use a SHA-based key to get the + # greatest change of finding the most relevant cached layer. We fall + # down to more generic containers by then matching by GitHub branch, + # to use cache generated earlier in the same branch, and finally to + # the latest cache in general. The `v0` is purely a cache version + # indicator that can be incremented manually if we want to invalidate + # old caches completely. + - name: Cache Image Layers + 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 }}- + ${{ runner.os }}-v0-buildx- + + # Build the image we need for testing/linting the current codebase, + # without pushing the image to the GHCR. Instead, we load it into + # the runner's docker environment so we can run it later. The + # target of this build is the `venv` stage of the Dockerfile, as we + # don't want to include the final production entry point stage. + # + # This build caches to our GitHub Actions cache and uses that cache + # during the build process as well. If no GitHub Actions cache was + # available, it will use the latest intermediate images pushed to + # the GHCR as a cache source. + - name: Build image for linting and testing + uses: docker/build-push-action@v2 + with: + context: . + file: ./docker/Dockerfile + push: false + load: true + target: venv + 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: "docker run \ + --tty \ + --detach \ + --name snekbox_test \ + --privileged \ + --hostname pdsnk-dev \ + -e PYTHONDONTWRITEBYTECODE=1 \ + -e PIPENV_PIPFILE='/snekbox/Pipfile\' \ + -e ENV=\"${PWD}/scripts/.profile\" \ + --volume \"${PWD}\":\"${PWD}\" \ + --workdir \"${PWD}\" \ + --entrypoint /bin/bash \ + ghcr.io/python-discord/snekbox-venv:${{ steps.sha_tag.outputs.tag }}" + + - name: Install dependencies + run: "docker exec snekbox_test /bin/bash -c \ + 'pipenv install --system --deploy --dev'" + + # 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_test /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 and generate coverage report + id: run_tests + run: | + echo '::set-output name=started::true' + cmd='coverage run -m unittest; coverage report -m' + docker exec snekbox_test /bin/bash -c "${cmd}" + + # 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 + # to prevent us from having to reload the caching directory. We + # already built a huge chunk of the image before this point in + # the run, so it does not make sense to drop down to a completely + # fresh build environment in a new worker/runner. + + # 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 cache that was just generated when we built the test container. + - name: Build final image + if: env.production_build == 'true' + uses: docker/build-push-action@v2 + with: + context: . + file: ./docker/Dockerfile + push: true + cache-from: | + type=local,src=/tmp/.buildx-cache + ghcr.io/python-discord/snekbox-base:latest + ghcr.io/python-discord/snekbox-venv:latest + ghcr.io/python-discord/snekbox:latest + cache-to: type=local,dest=/tmp/.buildx-cache + tags: | + ghcr.io/python-discord/snekbox:latest + ghcr.io/python-discord/snekbox:${{ steps.sha_tag.outputs.tag }} + + # Push the base image to GHCR, *with* an inline cache manifest to + # ensure we can use this image as a cache source if our GitHub Actions + # "local" cache failed to be restored. GHCR does not support pushing a + # separate cache manifest, meaning we have to use an "inline" manifest. + - name: Push base image + if: env.production_build == 'true' + uses: docker/build-push-action@v2 + with: + context: . + file: ./docker/Dockerfile + target: base + push: true + cache-from: | + type=local,src=/tmp/.buildx-cache + 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. See + # the comment attached to the previous step for more information. + - name: Push venv image + if: env.production_build == 'true' + uses: docker/build-push-action@v2 + with: + context: . + file: ./docker/Dockerfile + target: venv + push: true + 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=inline + tags: ghcr.io/python-discord/snekbox-venv:latest |