Skip to content

Commit e0617d1

Browse files
committed
Add GitHub Actions workflow to build run-aplus-front images
1 parent fd3c2ad commit e0617d1

22 files changed

Lines changed: 2378 additions & 0 deletions

File tree

.github/workflows/build.yml

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
name: Build and push Docker image
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v[0-9]*'
7+
8+
jobs:
9+
build:
10+
name: Build (${{ matrix.platform }})
11+
runs-on: ${{ matrix.runner }}
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
include:
16+
- platform: linux/amd64
17+
runner: ubuntu-24.04
18+
- platform: linux/arm64
19+
runner: ubuntu-24.04-arm
20+
21+
steps:
22+
- uses: actions/checkout@v4
23+
24+
- name: Log in to Docker Hub
25+
uses: docker/login-action@v3
26+
with:
27+
username: ${{ secrets.DOCKERHUB_USERNAME }}
28+
password: ${{ secrets.DOCKERHUB_TOKEN }}
29+
30+
- name: Set up Docker Buildx
31+
uses: docker/setup-buildx-action@v3
32+
33+
# Build and push platform-specific digest (no manifest tag yet)
34+
- name: Build and push by digest
35+
id: build
36+
uses: docker/build-push-action@v6
37+
with:
38+
context: .
39+
file: docker/Dockerfile
40+
platforms: ${{ matrix.platform }}
41+
push: true
42+
outputs: type=image,name=apluslms/run-aplus-front,push-by-digest=true,name-canonical=true
43+
44+
# Save the digest so the merge job can find it
45+
- name: Export digest
46+
run: |
47+
mkdir -p /tmp/digests
48+
digest="${{ steps.build.outputs.digest }}"
49+
touch "/tmp/digests/${digest#sha256:}"
50+
51+
- name: Upload digest artifact
52+
uses: actions/upload-artifact@v4
53+
with:
54+
name: digest-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
55+
path: /tmp/digests/*
56+
retention-days: 1
57+
58+
merge:
59+
name: Merge manifests
60+
runs-on: ubuntu-24.04
61+
needs: build
62+
63+
steps:
64+
- name: Download digests
65+
uses: actions/download-artifact@v4
66+
with:
67+
path: /tmp/digests
68+
pattern: digest-*
69+
merge-multiple: true
70+
71+
- name: Log in to Docker Hub
72+
uses: docker/login-action@v3
73+
with:
74+
username: ${{ secrets.DOCKERHUB_USERNAME }}
75+
password: ${{ secrets.DOCKERHUB_TOKEN }}
76+
77+
- name: Set up Docker Buildx
78+
uses: docker/setup-buildx-action@v3
79+
80+
- name: Determine tags
81+
id: meta
82+
uses: docker/metadata-action@v5
83+
with:
84+
images: apluslms/run-aplus-front
85+
tags: |
86+
type=raw,value=latest
87+
type=match,pattern=v(\d+\.\d+)\.\d+$,group=1
88+
89+
- name: Create and push multi-arch manifest
90+
working-directory: /tmp/digests
91+
run: |
92+
docker buildx imagetools create \
93+
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
94+
$(printf 'apluslms/run-aplus-front@sha256:%s ' *)

docker/Dockerfile

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
FROM apluslms/service-base:django-1.19
2+
3+
# Set container related configuration via environment variables
4+
ENV CONTAINER_TYPE="aplus" \
5+
APLUS_LOCAL_SETTINGS="/srv/aplus-cont-settings.py" \
6+
APLUS_SECRET_KEY_FILE="/local/aplus/secret_key.py" \
7+
USE_GITMANAGER="false"
8+
9+
RUN : \
10+
&& apt_install \
11+
python3-lxml \
12+
python3-lz4 \
13+
python3-pillow \
14+
redis \
15+
\
16+
# create user
17+
&& adduser --system --no-create-home --disabled-password --gecos "A+ webapp server,,," --home /srv/aplus --ingroup nogroup aplus \
18+
&& mkdir /srv/aplus && chown aplus.nogroup /srv/aplus \
19+
&& git config --global --add safe.directory /srv/aplus \
20+
&& :
21+
22+
COPY docker/rootfs /
23+
COPY . /srv/aplus
24+
25+
RUN cd /srv/aplus \
26+
# prebuild .pyc files
27+
&& python3 -m compileall -q . \
28+
# install requirements, remove the file, remove unrequired locales and tests
29+
&& pip_install \
30+
-r requirements.txt \
31+
"django-debug-toolbar >= 6.1.0, < 7" \
32+
flower \
33+
&& rm requirements.txt \
34+
&& find /usr/local/lib/python* -type d -regex '.*/locale/[a-z_A-Z]+' -not -regex '.*/\(en\|fi\|sv\)' -print0 | xargs -0 rm -rf \
35+
&& find /usr/local/lib/python* -type d -name 'tests' -print0 | xargs -0 rm -rf \
36+
\
37+
# preprocess
38+
&& export \
39+
APLUS_SECRET_KEY="-" \
40+
APLUS_CACHES="{\"default\": {\"BACKEND\": \"django.core.cache.backends.dummy.DummyCache\"}}" \
41+
&& python3 manage.py compilemessages 2>&1 \
42+
&& create-db.sh aplus aplus django-migrate.sh \
43+
\
44+
&& mkdir -p /var/celery/results \
45+
&& chown -R aplus:nogroup /var/celery \
46+
&& :
47+
48+
49+
WORKDIR /srv/aplus
50+
EXPOSE 8000
51+
EXPOSE 5555
52+
CMD [ "manage", "runserver", "0.0.0.0:8000" ]

docker/README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# run-aplus-front
2+
3+
A Docker container that runs the A-plus
4+
Learning Management System exposed to port 8000.
5+
6+
Note that the A-plus front service alone does not provide
7+
capability to implement or host learning material or exercises.
8+
The front connects to different material or assessment services
9+
that provide interactive content to learners. A common
10+
counterpart for content services is the
11+
[A-plus MOOC-grader](https://hub.docker.com/r/apluslms/run-mooc-grader/).
12+
13+
See into the [A-plus manual course](https://github.com/apluslms/aplus-manual)
14+
that includes a Docker compose configuration to develop and test course content.
15+
16+
## Usage
17+
18+
A-plus is installed in `/srv/aplus`.
19+
You can mount development version of the source code to `/src/aplus`.
20+
The container will then copy it to `/srv/aplus` and compile
21+
the translation file (django.mo). If you mount directly to
22+
`/srv/aplus`, you need to manually compile the translation file beforehand,
23+
but on the other hand, Django can reload the code and restart the server
24+
without restarting the whole container when you edit the source code files.
25+
26+
You can mount development version of the A+ source code on top of that, if you wish.
27+
28+
Location `/data` is a volume and contains submission files, database and secret key.
29+
It is world writable, so you can run this container as normal user.
30+
31+
The environment variable `APLUS_ENABLE_DJANGO_DEBUG_TOOLBAR` can be set to `'true'` or `'false'` in `docker-compose.yml`.
32+
It controls the Django Debug Toolbar, which provides useful debug data in a set panels on the right side of the web pages.
33+
The debug data comes from the a-plus code, not from course contents or exercise graders.
34+
Therefore, the toolbar is useful to A+ platform developers.
35+
If there is a reason to disable the toolbar (e.g., in some cases, it might slow down the performance),
36+
then it is easy to switch it on or off with the environment variable.
37+
By default when the env variable is not defined, the toolbar is disabled.
38+
39+
Partial example of `docker-compose.yml` (volumes are optional of course):
40+
41+
```yaml
42+
services:
43+
plus:
44+
image: apluslms/run-aplus-front
45+
environment:
46+
APLUS_ENABLE_DJANGO_DEBUG_TOOLBAR: 'true'
47+
volumes:
48+
# named persistent volume (until removed)
49+
# - data:/data
50+
# mount development version to /src/aplus
51+
# - /home/user/a-plus/:/src/aplus/:ro
52+
# or to /srv/aplus
53+
# - /home/user/a-plus/:/srv/aplus/:ro
54+
ports:
55+
- "8000:8000"
56+
depends_on:
57+
- grader
58+
volumes:
59+
data:
60+
```
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/bin/sh
2+
3+
. /usr/local/lib/cont-init-functions.sh
4+
ENSURE_DIR_MODE=2755
5+
ENSURE_DIR_USER=aplus
6+
ENSURE_DIR_GROUP=nogroup
7+
8+
ensure_dir /run/aplus
9+
ensure_dir /local/aplus
10+
ensure_dir /local/aplus/static
11+
ensure_dir /local/aplus/media
12+
13+
ensure_dir /local/lti-services-in 2777 root root
14+
15+
# Take this container's IP (v4) address, change the last component to .1 and
16+
# output it in a JSON list (like ["127.0.0.1"]) to Aplus settings.py.
17+
# The IP address with the .1 ending should be the host machine's IP address in
18+
# the Docker network, in other words, the IP address of the client (web browser).
19+
echo '["'$(hostname -i | cut -s -d. -f1-3)'.1"]' > /var/run/s6/container_environment/APLUS_INTERNAL_IPS
20+
21+
echo "127.0.0.1 redis" >> /etc/hosts

docker/rootfs/etc/services.d/aplus-course-update/down

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/usr/local/lib/prefix-logs
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/bin/execlineb -P
2+
3+
define home /srv/aplus
4+
define run /run/aplus
5+
fdmove -c 2 1
6+
7+
# Conditionally define celery
8+
# (-s is undocumented magic, that seems to move _prog_ to end of _then_ or
9+
# _else_ blocks and thus makes _define_ work)
10+
ifthenelse -s { test -e /local/venv_aplus/bin/python3 }
11+
{ define python3 /local/venv_aplus/bin/python3 }
12+
{ define python3 /usr/bin/python3 }
13+
14+
# Use container environment
15+
with-contenv
16+
17+
# user and workdir
18+
s6-setuidgid aplus
19+
s6-env HOME=${home}
20+
cd ${home}
21+
22+
# Loop with 1s delay until course update succeeds
23+
loopwhilex -x 0
24+
if -n -t { ${python3} manage.py reload_course_configuration def/current }
25+
s6-sleep 1
26+
exit 1

docker/rootfs/etc/services.d/aplus-lti-services/down

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/usr/local/lib/prefix-logs
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/bin/execlineb -P
2+
3+
define user aplus
4+
define home /srv/${user}
5+
fdmove -c 2 1
6+
7+
# Conditionally define celery
8+
# (-s is undocumented magic, that seems to move _prog_ to end of _then_ or
9+
# _else_ blocks and thus makes _define_ work)
10+
ifthenelse -s { test -e /local/venv_aplus/bin/python3 }
11+
{ define python3 /local/venv_aplus/bin/python3 }
12+
{ define python3 /usr/bin/python3 }
13+
14+
# Use container environment
15+
with-contenv
16+
17+
# Loop with 1s delay until course update succeeds
18+
loopwhilex -o 0
19+
foreground {
20+
elglob -0 -- lti_jsons /local/lti-services-in/*.json
21+
forx -- lti_json { ${lti_jsons} }
22+
importas -u lti_json lti_json
23+
if {
24+
# user, workdir and exec django command
25+
s6-setuidgid ${user}
26+
s6-env HOME=${home}
27+
cd ${home}
28+
#${python3} manage.py configure_external_service_from_json -c all ${lti_json}
29+
${python3} manage.py configure_external_service_from_json -c def/current ${lti_json}
30+
}
31+
rm -rf ${lti_json}
32+
}
33+
s6-sleep 1
34+
exit 0 # continue the loop

0 commit comments

Comments
 (0)