From f1e422fad268c5a35d4370809dbd0a043a09e85e Mon Sep 17 00:00:00 2001 From: Kevin Alberts Date: Mon, 9 Sep 2024 18:17:12 +0200 Subject: [PATCH 1/3] Prepare Alexia for Kubernetes deployment. - Add Dockerfile - Add helper scripts for management - Fallback settings to configure via environment variables - Add healthz view - Add GitHub Actions build pipeline - Remove old TravisCI config --- .editorconfig | 14 ++ .github/workflows/build_docker_image.yml | 83 ++++++++ .travis.yml | 32 --- Dockerfile | 42 ++++ alexia/apps/general/views.py | 6 +- alexia/conf/settings/__init__.py | 7 + alexia/conf/settings/environ.py | 250 +++++++++++++++++++++++ alexia/conf/settings/test.py | 12 +- alexia/conf/urls.py | 1 + alexia/conf/wsgi.py | 2 +- manage.py | 2 +- requirements.txt | 6 + scripts/merge_to_production.sh | 39 ++++ scripts/prepare.sh | 16 ++ scripts/run_mgmt.sh | 9 + scripts/run_tests.sh | 41 ++++ scripts/start_web_wsgi.sh | 5 + {bin => scripts}/wkhtmltopdf.sh | 0 18 files changed, 529 insertions(+), 38 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/workflows/build_docker_image.yml delete mode 100644 .travis.yml create mode 100644 Dockerfile create mode 100644 alexia/conf/settings/environ.py create mode 100755 scripts/merge_to_production.sh create mode 100755 scripts/prepare.sh create mode 100755 scripts/run_mgmt.sh create mode 100755 scripts/run_tests.sh create mode 100755 scripts/start_web_wsgi.sh rename {bin => scripts}/wkhtmltopdf.sh (100%) diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9947f59 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# top-most EditorConfig file +root = true + +# UNIX line endings, UTF-8 encoding, no trailing whitespace and a newline ending every file. +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# 4 space indentation in python source files +[*.py] +indent_style = space +indent_size = 4 diff --git a/.github/workflows/build_docker_image.yml b/.github/workflows/build_docker_image.yml new file mode 100644 index 0000000..40204b5 --- /dev/null +++ b/.github/workflows/build_docker_image.yml @@ -0,0 +1,83 @@ +name: Build docker image and run tests + +on: [push, pull_request] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + services: + mariadb: + image: mariadb:10.4 + env: + MARIADB_USER: alexia_test + MARIADB_PASSWORD: alexia_test + MYSQL_DATABASE: alexia_test + MYSQL_ROOT_PASSWORD: alexia_test + ports: ['3306:3306'] + options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Build and push Docker image + id: push + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + - name: Verify MariaDB connection + run: | + while ! mysqladmin ping -h"127.0.0.1" -P"3306" --silent; do + sleep 1 + done + + - name: Run alexia tests + run: | + docker run --rm --entrypoint "/alexia/scripts/run_tests.sh" ghcr.io/inter-actief/alexia@${{ steps.push.outputs.digest }} + + - name: Cleanup untagged images older than 1 week + uses: snok/container-retention-policy@v2 + with: + image-names: alexia + cut-off: 1 week ago UTC + account-type: org + org-name: Inter-Actief + token: ${{ secrets.GITHUB_TOKEN }} + token-type: github-token + untagged-only: true + + - name: Cleanup tagged images (except main and production) older than 1 month + uses: snok/container-retention-policy@v2 + with: + image-names: alexia + cut-off: 1 month ago UTC + account-type: org + org-name: Inter-Actief + token: ${{ secrets.GITHUB_TOKEN }} + token-type: github-token + skip-tags: main, production diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4c557a2..0000000 --- a/.travis.yml +++ /dev/null @@ -1,32 +0,0 @@ -language: python -# List of Python versions to test -matrix: - fast_finish: true - include: - - python: 3.7 - - python: 3.9 -# allow_failures: -# - python: 3.9 -# Cache pip packages for us so builds are faster -cache: pip -# We want to use MySQL -services: - - mysql -# Create databases before installing alexia -before_install: - - sudo apt-get update - - sudo apt-get install -y xmlsec1 - - mysql -e 'CREATE DATABASE alexia_test;' - - mysql -e 'CREATE DATABASE test_alexia_test;' - - mysql -u root -e "GRANT ALL PRIVILEGES ON alexia_test.* to 'travis'@'%';" - - mysql -u root -e "GRANT ALL PRIVILEGES ON test_alexia_test.* to 'travis'@'%';" - - mysql -u root -e "FLUSH PRIVILEGES;" -# Set django settings module in environment variable -env: - global: - - DJANGO_SETTINGS_MODULE=alexia.conf.settings.test -# Install the requirements during installation step -install: - - pip install -r requirements.txt -# command to run tests -script: python manage.py test --keepdb diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7929500 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,42 @@ +# Build the alexia docker image based on Debian 11 (Bullseye) +FROM debian:bullseye + +# Copy alexia sources +COPY . /alexia + +# Set /alexia as startup working directory +WORKDIR /alexia + +# Install required packages for alexia and prepare the system to run alexia +RUN echo "Updating repostitories..." && \ + apt-get update -y && \ + echo "Upgrading base debian system..." && \ + apt-get upgrade -y && \ + echo "Installing alexia required packages..." && \ + apt-get install -y apt-utils git net-tools python3 python3-pip mariadb-client libmariadb-dev xmlsec1 libssl-dev libldap-dev libsasl2-dev libjpeg-dev zlib1g-dev gettext locales acl && \ + echo "Enabling 'nl_NL' and 'en_US' locales..." && \ + sed -i -e 's/# nl_NL.UTF-8 UTF-8/nl_NL.UTF-8 UTF-8/' /etc/locale.gen && \ + sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \ + echo "Rebuilding locales..." && \ + dpkg-reconfigure --frontend=noninteractive locales && \ + echo "Creating directories for alexia..." && \ + mkdir -p /alexia /config /static /media /var/log /var/run && \ + echo "Installing python requirements..." && \ + pip3 install -r requirements.txt && \ + echo "Correcting permissions on directories..." && \ + chown -R 1000:1000 /alexia /config /static /media /var/log + +# Switch back to a local user +USER 1000:1000 + +# Check if Django can run +RUN python3 manage.py check + +# Expose volumes +VOLUME ["/config", "/static", "/media"] + +# Expose the web port +EXPOSE 8000 + +# Start the website +CMD ["/alexia/scripts/start_web_wsgi.sh"] diff --git a/alexia/apps/general/views.py b/alexia/apps/general/views.py index 7b256cd..578a0e3 100644 --- a/alexia/apps/general/views.py +++ b/alexia/apps/general/views.py @@ -7,7 +7,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.sites.shortcuts import get_current_site from django.core.exceptions import PermissionDenied -from django.http import HttpResponseRedirect +from django.http import HttpResponseRedirect, HttpResponse from django.shortcuts import get_object_or_404, resolve_url from django.template.response import TemplateResponse from django.urls import reverse_lazy @@ -154,3 +154,7 @@ def get_context_data(self, **kwargs): class HelpView(TemplateView): template_name = 'general/help.html' + + +def healthz_view(request): + return HttpResponse('ok', content_type="text/plain") diff --git a/alexia/conf/settings/__init__.py b/alexia/conf/settings/__init__.py index e69de29..eaacda2 100644 --- a/alexia/conf/settings/__init__.py +++ b/alexia/conf/settings/__init__.py @@ -0,0 +1,7 @@ +from alexia.conf.settings.base import * + +# Try to import local settings, fallback to config via environment variables if that fails +try: + from alexia.conf.settings.local import * +except ImportError: + from alexia.conf.settings.environ import * diff --git a/alexia/conf/settings/environ.py b/alexia/conf/settings/environ.py new file mode 100644 index 0000000..cb94213 --- /dev/null +++ b/alexia/conf/settings/environ.py @@ -0,0 +1,250 @@ +# This is the configuration file for Alexia. +# This configuration file will load configuration from environment variables. + +# Keep these imports here! +import warnings +import os +import json + +import environ +from pathlib import Path + +from email.utils import getaddresses +from django.core.management.utils import get_random_secret_key + +from alexia.conf.settings.base import * + +# Initialize an env object for `django-environ` +env = environ.Env() + +# Proxy function for get_random_secret_key that replaces $ with % (because $ has a special function in django-environ) +def get_random_secret_key_no_dollar(): + s = get_random_secret_key() + return s.replace('$', '%') + +# Set base path of the project, to build paths with. +BASE_DIR = Path(__file__).resolve(strict=True).parent.parent.parent.parent + +# Configure database +DATABASES = { + 'default': env.db_url("DATABASE_URL", default=f"sqlite:////{BASE_DIR}/alexia.db") +} + +# Override database options if environment variable is given (unsupported by django-environ's env.db() function) +DATABASE_OPTIONS = env.json('DATABASE_OPTIONS', default={}) +if DATABASE_OPTIONS: + DATABASES['default']['OPTIONS'] = DATABASE_OPTIONS + +# Make sure these are set correctly in production +ENV = env('DJANGO_ENVIRONMENT', default='PRODUCTION') +DEBUG = env.bool('DJANGO_DEBUG', default=False) +DEBUG_TOOLBAR = env.bool('DJANGO_DEBUG', default=False) +TEMPLATE_DEBUG = env.bool('DJANGO_DEBUG', default=False) +MY_DEBUG_IN_TEMPLATES = False +IGNORE_REQUIRE_SECURE = False +PYDEV_DEBUGGER = False +PYDEV_DEBUGGER_IP = None + +# Do not redirect to HTTPS, because the nginx proxy container only listens on HTTP +SECURE_SSL_REDIRECT = False + +# Add allow cidr middleware as first middleware +MIDDLEWARE = ["allow_cidr.middleware.AllowCIDRMiddleware"] + MIDDLEWARE + +# Allowed hosts -- localhost and 127.0.0.1 are always allowed, the rest comes from an environment variable. +ALLOWED_HOSTS = [ + "localhost", "127.0.0.1" +] + env.list("DJANGO_ALLOWED_HOSTS", default=[]) + +# Allowed CIDR nets -- for kubernetes internal services +ALLOWED_CIDR_NETS = ['172.30.0.0/16'] +ALLOWED_CIDR_NETS.extend(env.list("DJANGO_ALLOWED_CIDR_NETS", default=[])) + +# Add Kubernetes POD IP, if running in Kubernetes +KUBE_POD_IP = env("THIS_POD_IP", default="") +if KUBE_POD_IP: + ALLOWED_CIDR_NETS.append(KUBE_POD_IP) + +# Example: DJANGO_ADMINS="Jan Janssen , Bob de Bouwer " +ADMINS = getaddresses([env("DJANGO_ADMINS", default="WWW-committee ")]) +MANAGERS = ADMINS + +### +# Logging settings +### +LOG_LEVEL = env("DJANGO_LOG_LEVEL", default="INFO") + +LOG_TO_CONSOLE = env.bool("DJANGO_LOG_TO_CONSOLE", default=True) +LOG_TO_FILE = env.bool("DJANGO_LOG_TO_FILE", default=False) +LOG_MAIL_ERRORS = env.bool("DJANGO_MAIL_ERRORS", default=False) + +ENABLED_HANDLERS = [] +if LOG_TO_CONSOLE: + ENABLED_HANDLERS.append('console') +if LOG_TO_FILE: + ENABLED_HANDLERS.append('alexia-file') +if LOG_MAIL_ERRORS: + ENABLED_HANDLERS.append('mail_admins') + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'verbose': { + 'format': '[%(asctime)s] %(levelname)s %(name)s %(funcName)s (%(filename)s:%(lineno)d) %(message)s', + }, + }, + 'filters': { + 'require_debug_false': { + '()': 'django.utils.log.RequireDebugFalse', + } + }, + 'handlers': { + 'alexia-file': { + 'level': LOG_LEVEL, + 'class': 'logging.handlers.WatchedFileHandler', + 'filename': f'{BASE_DIR}/alexia.log', + 'formatter': 'verbose', + }, + 'console': { + 'level': LOG_LEVEL, + 'class': 'logging.StreamHandler', + 'formatter': 'verbose' + }, + 'mail_admins': { + 'level': 'ERROR', + 'filters': ['require_debug_false'], + 'class': 'django.utils.log.AdminEmailHandler', + 'include_html': False, + }, + }, + 'root': { # all other errors go to the console, the general log file and sentry + 'level': 'DEBUG', + 'handlers': ENABLED_HANDLERS, + }, + 'loggers': { + 'alexia': { # Log all Alexia errors + 'level': 'DEBUG', + }, + 'django': { # Reset default settings for django + 'handlers': [], + }, + 'django.request': { # Reset default settings for django.request + 'handlers': [], + 'level': 'ERROR', + 'propagate': True, + }, + 'py.warnings': { # Reset default settings for py.warnings + 'handlers': [], + }, + 'sentry.errors': { # do not propagate sentry errors to sentry + 'level': 'DEBUG', + 'handlers': ENABLED_HANDLERS, + 'propagate': False, + }, + 'tornado.access': { # Ignore tornado.access INFO logging + 'handlers': [], + 'level': 'WARNING', + }, + 'amqp': { # Set AMQP to something else than debug (log spam) + 'level': 'WARNING', + }, + 'urllib3': { # Set urllib3 to something else than debug + 'level': 'WARNING', + }, + 'daphne': { # Set daphne logging to INFO + 'level': 'INFO' + }, + # Set OIDC logging to at least info due to process_request log flooding + 'mozilla_django_oidc.middleware': {'level': 'INFO'}, + }, +} + +# Sentry SDK configuration +DJANGO_SENTRY_DSN = env("DJANGO_SENTRY_DSN", default="") +DJANGO_SENTRY_ENVIRONMENT = env("DJANGO_SENTRY_ENVIRONMENT", default="production") +if DJANGO_SENTRY_DSN: + import sentry_sdk + from sentry_sdk.integrations.django import DjangoIntegration + sentry_sdk.init( + dsn=DJANGO_SENTRY_DSN, + integrations=[ + DjangoIntegration(), + ], + # Proportion of requests that are traced for performance monitoring. + # Keep at (or very very very close to) 0 in production! + traces_sample_rate=0, + # Send user details of request to Sentry + send_default_pii=True, + auto_session_tracking=False, + environment=DJANGO_SENTRY_ENVIRONMENT, + ) + + +### +# Authentication settings +### + +# Django authentication backends +# Login settings -- only allow login using specified backends +AUTHENTICATION_BACKENDS = env.list("DJANGO_AUTHENTICATION_BACKENDS", default=["django.contrib.auth.backends.ModelBackend"]) + +# OIDC Single sign-on configuration +OIDC_RP_CLIENT_ID = env("OIDC_RP_CLIENT_ID", default="alexia") +OIDC_RP_CLIENT_SECRET = env("OIDC_RP_CLIENT_SECRET", default="") + + +### +# Security settings +### + +# Only use cookies for HTTPS +CSRF_COOKIE_SECURE = True +SESSION_COOKIE_SECURE = True + +# If the proxy tells us the external side is HTTPS, use that +USE_X_FORWARDED_HOST = True +SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') + +# Make this unique, and don't share it with anybody. +SECRET_KEY = env('DJANGO_SECRET_KEY', default=get_random_secret_key_no_dollar()) + + +### +# Internationalization +### +LOCALE_PATHS = ('/alexia/locale', ) + + +### +# URL and Media settings +### +# Path to alexia static files +STATIC_ROOT = '/static' +STATIC_URL = env("ALEXIA_STATIC_URL", default="/static/") + +# Path to alexia media +MEDIA_ROOT = '/media' +MEDIA_URL = env("ALEXIA_MEDIA_URL", default="/media/") + +# Path to website (needed for pictures via the API among other things) +ABSOLUTE_PATH_TO_SITE = env("ALEXIA_ABSOLUTE_PATH_TO_SITE", default="http://localhost:8080/") + + +### +# E-mail settings +### +EMAIL_BACKEND = env("ALEXIA_EMAIL_BACKEND", default="django.core.mail.backends.filebased.EmailBackend") +EMAIL_HOST = env("ALEXIA_EMAIL_HOST", default="smtp.snt.utwente.nl") +EMAIL_PORT = env.int("ALEXIA_EMAIL_PORT", default=25) +EMAIL_FROM = env("ALEXIA_EMAIL_FROM", default='Alexia ') +EMAIL_SUBJECT_PREFIX = env("ALEXIA_EMAIL_SUBJECT_PREFIX", default='[Alexia] ') +DEFAULT_FROM_EMAIL = EMAIL_FROM +SERVER_EMAIL = EMAIL_FROM + + +### +# Alexia-specific settings +### +# HTML to PDF script +WKHTMLTOPDF_CMD = '/alexia/scripts/wkhtmltopdf.sh' diff --git a/alexia/conf/settings/test.py b/alexia/conf/settings/test.py index 28ed960..2d76ac1 100644 --- a/alexia/conf/settings/test.py +++ b/alexia/conf/settings/test.py @@ -4,11 +4,17 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', - 'HOST': '127.0.0.1', + 'HOST': '172.17.0.1', 'NAME': 'alexia_test', - 'USER': 'travis', - 'PASSWORD': '', + 'USER': 'alexia_test', + 'PASSWORD': 'alexia_test', + 'TEST': { + 'NAME': 'alexia_test', + } } } SECRET_KEY = 'zBCMvM1BwLtlkoXf1mbgCo3W60j2UgIPhevmEJ9cMPft2JtUk5' + +# Disable secure redirects to allow testing without SSL +SECURE_SSL_REDIRECT = False diff --git a/alexia/conf/urls.py b/alexia/conf/urls.py index ceadb98..cd23f5e 100644 --- a/alexia/conf/urls.py +++ b/alexia/conf/urls.py @@ -32,6 +32,7 @@ url(r'^api/', include('alexia.api.urls')), # "Static" general_views + url(r'^healthz/$', general_views.healthz_view, name='healthz_simple'), url(r'^about/$', general_views.AboutView.as_view(), name='about'), url(r'^help/$', general_views.HelpView.as_view(), name='help'), url(r'^login_complete/$', general_views.login_complete, name='login_complete'), diff --git a/alexia/conf/wsgi.py b/alexia/conf/wsgi.py index b571da0..05f6883 100644 --- a/alexia/conf/wsgi.py +++ b/alexia/conf/wsgi.py @@ -11,6 +11,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "alexia.conf.settings.local") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "alexia.conf.settings") application = get_wsgi_application() diff --git a/manage.py b/manage.py index 0e16bc6..9d158a5 100755 --- a/manage.py +++ b/manage.py @@ -3,7 +3,7 @@ import sys if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "alexia.conf.settings.local") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "alexia.conf.settings") from django.core.management import execute_from_command_line diff --git a/requirements.txt b/requirements.txt index bfc66cd..6232498 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,3 +18,9 @@ django-rosetta>=0.7.6,<0.9.7 mozilla-django-oidc==2.0.0 # Sentry error logging sentry-sdk>=1.22.1,<1.23 +# UWSGI - to run the website in WSGI mode on a proper webserver +uwsgi>=2.0,<2.1 +# Allow access based on CIDR subnets +django-allow-cidr>=0.5.0,<0.6 +# Configure Django via environment variables +django-environ>=0.11.2,<0.12 diff --git a/scripts/merge_to_production.sh b/scripts/merge_to_production.sh new file mode 100755 index 0000000..c33932d --- /dev/null +++ b/scripts/merge_to_production.sh @@ -0,0 +1,39 @@ +#!/bin/bash +set -e +# Script to nicely pull the latest 'main' branch and merge it into the 'production' branch. + +# Checks to make sure this isn't run on a dirty repository +if [[ $(git rev-parse --abbrev-ref HEAD) != "production" ]] +then + echo "This script should only run in the production branch, which you don't seem to be using!" + exit 1 +fi +if [[ -n $(git status --porcelain) ]] +then + echo "There are uncommitted local changes. Please commit these changes before updating production." + exit 1 +fi + +# Fetch the latest changes on the 'main' branch and show comparisons between 'main' and 'production'. +echo "### Pulling changes" +git pull +changeset=`git rev-parse --short origin/main` +echo "### origin/main changeset to merge is [$changeset]" +echo "### Changes:" +git log --oneline ..origin/main +git diff --stat ...origin/main + +# Ask for confirmation +read -p "Are you sure you want to merge and push these changes? (y/n)" -r +if [[ ! $REPLY =~ ^[Yy]$ ]] +then + echo + exit 1 +fi + +# Merge changes into production +git merge -m "Merge [$changeset] to production (ac)" origin/main +git push origin production +echo "### Changes pulled, merged/fast-forwarded and committed" + +echo "### Done!" diff --git a/scripts/prepare.sh b/scripts/prepare.sh new file mode 100755 index 0000000..036c331 --- /dev/null +++ b/scripts/prepare.sh @@ -0,0 +1,16 @@ +#!/bin/bash +echo "Preparing to run Alexia..." + +# Make sure staticfiles are collected into the static volume +echo "Collecting static files..." +python3 manage.py collectstatic --noinput + +# Make sure database is migrated +echo "Migrating database..." +python3 manage.py migrate + +# Check if Django can run +echo "Checking if Django can run..." +python3 manage.py check + +echo "Done!" diff --git a/scripts/run_mgmt.sh b/scripts/run_mgmt.sh new file mode 100755 index 0000000..de9e4dc --- /dev/null +++ b/scripts/run_mgmt.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# Check if Django can run +echo "Checking if Django can run..." +python3 manage.py check + +# Run the manage.py command +echo "Running management command '$@'..." +python3 manage.py "$@" diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh new file mode 100755 index 0000000..1011b12 --- /dev/null +++ b/scripts/run_tests.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# Print some debugging information about the environment +# ------------------------------------------------------ +# Python version +echo "Python version:" +python3 -V +# Path to the python binary being used +echo "Python binary:" +which python +# Pip version +echo "Pip version:" +pip -V +# Path to the pip binary being used +echo "Pip binary:" +which pip +# Installed pip package list +echo "Pip installed packages:" +pip freeze + +# Configure Django and run the tests +# ---------------------------------- +# Copy the test settings to local.py +echo "Copying test settings to local.py" +cp ./alexia/conf/settings/test.py ./alexia/conf/settings/local.py + +# Run Django initial checks +echo "Checking if Django can run..." +python3 manage.py check + +# Make sure staticfiles are collected into the static volume +echo "Collecting static files..." +python3 manage.py collectstatic --noinput + +# Make sure database is migrated +echo "Executing Django migrations..." +python3 manage.py migrate + +# Run Django tests +echo "Running Alexia tests..." +python3 manage.py test --keepdb diff --git a/scripts/start_web_wsgi.sh b/scripts/start_web_wsgi.sh new file mode 100755 index 0000000..1da2d49 --- /dev/null +++ b/scripts/start_web_wsgi.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +# Start django server (WSGI) +echo "Starting uWSGI server..." +uwsgi --module=alexia.conf.wsgi:application --http 0.0.0.0:8000 --workers 4 --threads 4 -b 32768 --master --vacuum --enable-threads --log-x-forwarded-for diff --git a/bin/wkhtmltopdf.sh b/scripts/wkhtmltopdf.sh similarity index 100% rename from bin/wkhtmltopdf.sh rename to scripts/wkhtmltopdf.sh From 63ff40c1b7b568f95f5e1c90854ded965bff60b8 Mon Sep 17 00:00:00 2001 From: Kevin Alberts Date: Mon, 9 Sep 2024 19:50:44 +0200 Subject: [PATCH 2/3] Fix tests to use newer OIDC backend for temporary users. Enable OIDC backend by default in environ settings. --- alexia/conf/settings/environ.py | 4 +++- alexia/test/testcases.py | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/alexia/conf/settings/environ.py b/alexia/conf/settings/environ.py index cb94213..7740d99 100644 --- a/alexia/conf/settings/environ.py +++ b/alexia/conf/settings/environ.py @@ -187,7 +187,9 @@ def get_random_secret_key_no_dollar(): # Django authentication backends # Login settings -- only allow login using specified backends -AUTHENTICATION_BACKENDS = env.list("DJANGO_AUTHENTICATION_BACKENDS", default=["django.contrib.auth.backends.ModelBackend"]) +AUTHENTICATION_BACKENDS = env.list("DJANGO_AUTHENTICATION_BACKENDS", default=[ + "django.contrib.auth.backends.ModelBackend", "alexia.auth.backends.IAOIDCAuthenticationBackend" +]) # OIDC Single sign-on configuration OIDC_RP_CLIENT_ID = env("OIDC_RP_CLIENT_ID", default="alexia") diff --git a/alexia/test/testcases.py b/alexia/test/testcases.py index c17c504..407aeef 100644 --- a/alexia/test/testcases.py +++ b/alexia/test/testcases.py @@ -17,7 +17,7 @@ AuthenticationData, Location, Organization, Profile, ) from alexia.apps.scheduling.models import Availability, Event -from alexia.auth.backends import RADIUS_BACKEND_NAME +from alexia.auth.backends import OIDC_BACKEND_NAME class SimpleTestCase(testcases.SimpleTestCase): @@ -78,7 +78,7 @@ def load_organization_data(self): data['user1'].profile = Profile() data['user1'].profile.save() - data['authenticationdata1'] = AuthenticationData(backend=RADIUS_BACKEND_NAME, username=username1, + data['authenticationdata1'] = AuthenticationData(backend=OIDC_BACKEND_NAME, username=username1, user=data['user1']) data['authenticationdata1'].save() @@ -89,7 +89,7 @@ def load_organization_data(self): data['user2'].profile = Profile() data['user2'].profile.save() - data['authenticationdata2'] = AuthenticationData(backend=RADIUS_BACKEND_NAME, username=username2, + data['authenticationdata2'] = AuthenticationData(backend=OIDC_BACKEND_NAME, username=username2, user=data['user2']) data['authenticationdata2'].save() From dfd6fd344f1e18b21e3f3697fdf45528769d2c57 Mon Sep 17 00:00:00 2001 From: Kevin Alberts Date: Mon, 9 Sep 2024 19:59:19 +0200 Subject: [PATCH 3/3] Add required packages for wkhtmltopdf and xvfb to Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7929500..97ece8f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ RUN echo "Updating repostitories..." && \ echo "Upgrading base debian system..." && \ apt-get upgrade -y && \ echo "Installing alexia required packages..." && \ - apt-get install -y apt-utils git net-tools python3 python3-pip mariadb-client libmariadb-dev xmlsec1 libssl-dev libldap-dev libsasl2-dev libjpeg-dev zlib1g-dev gettext locales acl && \ + apt-get install -y apt-utils git net-tools python3 python3-pip mariadb-client libmariadb-dev xmlsec1 libssl-dev libldap-dev libsasl2-dev libjpeg-dev zlib1g-dev gettext locales acl wkhtmltopdf xvfb && \ echo "Enabling 'nl_NL' and 'en_US' locales..." && \ sed -i -e 's/# nl_NL.UTF-8 UTF-8/nl_NL.UTF-8 UTF-8/' /etc/locale.gen && \ sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \