From ca40a2cdb53661f738a099f4573692c28c1e51ce Mon Sep 17 00:00:00 2001 From: Pan Luo Date: Wed, 21 Oct 2026 13:49:57 -0700 Subject: [PATCH] ADD GitHub Actions workflow to build and publish the Docker image Docker Hub autobuilds stopped working long ago (both webhooks on this repo fail with 403/502), so images have been built and pushed by hand. The workflow builds and smoke-tests the image on every push to master and every PR. Pushing a git tag also publishes ubcctlt/response-map:. The smoke test checks the PHP version, the mysqli/gd/imagick/OPcache extensions, php -l on every file, and that Apache serves PHP. The old `latest` tag is left alone. Also exclude .github/ from the image so the workflow file isn't served from the web root, and document the release steps in the README. --- .dockerignore | 1 + .github/workflows/docker-image.yml | 88 ++++++++++++++++++++++++++++++ README.md | 10 ++++ 3 files changed, 99 insertions(+) create mode 100644 .github/workflows/docker-image.yml diff --git a/.dockerignore b/.dockerignore index 16f59cf..2acc5a7 100644 --- a/.dockerignore +++ b/.dockerignore @@ -41,6 +41,7 @@ log diff .git/ +.github/ .dockerignore .gitignore files/*.png diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 0000000..dd987f0 --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,88 @@ +name: Docker image + +# Every run builds and smoke-tests the image. Pushing a git tag (e.g. 1.0.4) +# also publishes ubcctlt/response-map:, which is what the deployment +# config pins. +on: + push: + branches: [ master ] + tags: [ '*' ] + pull_request: + branches: [ master ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }} + +permissions: + contents: read + +env: + IMAGE: ubcctlt/response-map + TEST_TAG: response-map:test + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Docker meta + id: meta + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 + with: + images: ${{ env.IMAGE }} + # Don't move the old `latest` tag; deployments pin release tags. + flavor: latest=false + tags: type=ref,event=tag + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 + + - name: Build test image + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 + with: + context: . + platforms: linux/amd64 + load: true + tags: ${{ env.TEST_TAG }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Smoke test + run: | + docker run --rm --entrypoint php "$TEST_TAG" -v + + modules=$(docker run --rm --entrypoint php "$TEST_TAG" -m) + for ext in mysqli gd imagick 'Zend OPcache'; do + grep -qx "$ext" <<< "$modules" || { echo "::error::PHP extension missing: $ext"; exit 1; } + done + + docker run --rm --entrypoint sh "$TEST_TAG" -c \ + 'find /var/www/html -name "*.php" -print0 | xargs -0 -n1 php -l' + + docker run -d --name smoke -p 8080:80 "$TEST_TAG" + curl -sSI --retry 10 --retry-all-errors --retry-delay 1 http://localhost:8080/ \ + | grep -i '^x-powered-by: PHP/' \ + || { docker logs smoke; echo "::error::Apache did not serve PHP"; exit 1; } + + - name: Log in to Docker Hub + if: startsWith(github.ref, 'refs/tags/') + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + + - name: Push release image + if: startsWith(github.ref, 'refs/tags/') + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 + with: + context: . + platforms: linux/amd64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha diff --git a/README.md b/README.md index 0209942..cce47e1 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,16 @@ You will need have an Apache HTTP server which is configured to serve PHP files - Save a copy of `config.example.php` as `config.php` and edit the configuration variables. 3. Create a database with the name set in Step 2 and import `response_map.sql` to create the necessary tables. +## Releasing a Docker image +GitHub Actions builds and smoke-tests the image on every push to `master` and every pull request. Pushing a git tag also publishes `ubcctlt/response-map:`: + +``` +git tag -a 1.0.4 -m "short description" +git push origin 1.0.4 +``` + +Deployments pin the image tag, so bump it there to roll out the new image. + ## Integrating with edX 1. [Enable LTI component for your course](http://edx.readthedocs.io/projects/edx-partner-course-staff/en/latest/exercises_tools/lti_component.html#enabling-lti-components-for-a-course). 2. Also under Advanced Settings, the LTI Passports array must contain the LTI key and secret pair that is used by the tool (set in Step 2). You must add it to the array in the following format: ```"passport_id:key:secret"```. The id is later used when configuring the LTI component to obtain the key and secret.