From 8a13f6a19a51eeea05ad3a166145f8a46dfd86bf Mon Sep 17 00:00:00 2001 From: tphan025 Date: Fri, 27 Mar 2026 23:16:56 +0100 Subject: [PATCH] Add snap files --- .../haproxy_route_policy/settings.py | 10 +++- haproxy-route-policy/pyproject.toml | 1 + haproxy-route-policy/snap/hooks/configure | 52 +++++++++++++++++++ haproxy-route-policy/snap/hooks/install | 13 +++++ .../snap/scripts/bin/gunicorn-start | 31 +++++++++++ haproxy-route-policy/snap/scripts/bin/manage | 25 +++++++++ haproxy-route-policy/snap/scripts/bin/prepare | 27 ++++++++++ haproxy-route-policy/snap/snapcraft.yaml | 51 ++++++++++++++++++ haproxy-route-policy/uv.lock | 23 ++++++++ 9 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 haproxy-route-policy/snap/hooks/configure create mode 100755 haproxy-route-policy/snap/hooks/install create mode 100755 haproxy-route-policy/snap/scripts/bin/gunicorn-start create mode 100755 haproxy-route-policy/snap/scripts/bin/manage create mode 100755 haproxy-route-policy/snap/scripts/bin/prepare create mode 100644 haproxy-route-policy/snap/snapcraft.yaml diff --git a/haproxy-route-policy/haproxy_route_policy/settings.py b/haproxy-route-policy/haproxy_route_policy/settings.py index 05b70ee7..685f5412 100644 --- a/haproxy-route-policy/haproxy_route_policy/settings.py +++ b/haproxy-route-policy/haproxy_route_policy/settings.py @@ -20,7 +20,15 @@ # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent -SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY") +SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "") +if SECRET_KEY == "": + # Read the secret key from a file to have it persist between + # the manage.py script calls and the gunicorn process + # (it needs to be the same for the JWT tokens). + secret_path = Path(BASE_DIR, ".secret") + if secret_path.exists(): + with open(secret_path, "r", encoding="utf-8") as secretfile: + SECRET_KEY = secretfile.read().strip() DEBUG = os.environ.get("DJANGO_DEBUG", "").lower() == "true" ALLOWED_HOSTS = json.loads(os.getenv("DJANGO_ALLOWED_HOSTS", "[]")) diff --git a/haproxy-route-policy/pyproject.toml b/haproxy-route-policy/pyproject.toml index 886b7f29..02299906 100644 --- a/haproxy-route-policy/pyproject.toml +++ b/haproxy-route-policy/pyproject.toml @@ -8,6 +8,7 @@ dependencies = [ "django>=6.0.3", "djangorestframework>=3.16.1", "djangorestframework-simplejwt>=5.5.1", + "gunicorn>=23.0.0", "psycopg2-binary>=2.9.11", "validators>=0.35.0", "whitenoise>=6.12.0", diff --git a/haproxy-route-policy/snap/hooks/configure b/haproxy-route-policy/snap/hooks/configure new file mode 100644 index 00000000..75634aa4 --- /dev/null +++ b/haproxy-route-policy/snap/hooks/configure @@ -0,0 +1,52 @@ +#!/bin/sh + +# Copyright 2026 Canonical Ltd. +# See LICENSE file for licensing details. + +DJANGO_DEBUG="$(snapctl get debug)" +export DJANGO_DEBUG + +case "$DJANGO_DEBUG" in + "true") ;; + "false") ;; + *) + >&2 echo "'$DJANGO_DEBUG is not a supported value for django_debug. Possible values are true, false" + return 1 + ;; +esac + +DJANGO_LOG_LEVEL="$(snapctl get log-level)" +export DJANGO_LOG_LEVEL + +case "$DJANGO_LOG_LEVEL" in + "debug") ;; + "info") ;; + "warning") ;; + "error") ;; + "critical") ;; + "DEBUG") ;; + "INFO") ;; + "WARNING") ;; + "ERROR") ;; + "CRITICAL") ;; + *) + >&2 echo "'$DJANGO_LOG_LEVEL is not a supported value for debug. Possible values are debug, info, warning, error, critical" + return 1 + ;; +esac + +DJANGO_ALLOWED_HOSTS="$(snapctl get allowed-hosts)" +export DJANGO_ALLOWED_HOSTS +DJANGO_DATABASE_PASSWORD="$(snapctl get database-password)" +export DJANGO_DATABASE_PASSWORD +DJANGO_DATABASE_HOST="$(snapctl get database-host)" +export DJANGO_DATABASE_HOST +DJANGO_DATABASE_PORT="$(snapctl get database-port)" +export DJANGO_DATABASE_PORT +DJANGO_DATABASE_USER="$(snapctl get database-user)" +export DJANGO_DATABASE_USER +DJANGO_DATABASE_NAME="$(snapctl get database-name)" +export DJANGO_DATABASE_NAME + +snapctl stop "$SNAP_INSTANCE_NAME" +snapctl start "$SNAP_INSTANCE_NAME" diff --git a/haproxy-route-policy/snap/hooks/install b/haproxy-route-policy/snap/hooks/install new file mode 100755 index 00000000..1a849ae7 --- /dev/null +++ b/haproxy-route-policy/snap/hooks/install @@ -0,0 +1,13 @@ +#!/bin/sh + +# Copyright 2026 Canonical Ltd. +# See LICENSE file for licensing details. + +set -xe + +# Create some directories +mkdir -p "$SNAP_DATA/app" + +# set default configuration values +snapctl set debug='false' +snapctl set log-level='INFO' diff --git a/haproxy-route-policy/snap/scripts/bin/gunicorn-start b/haproxy-route-policy/snap/scripts/bin/gunicorn-start new file mode 100755 index 00000000..b6996ca1 --- /dev/null +++ b/haproxy-route-policy/snap/scripts/bin/gunicorn-start @@ -0,0 +1,31 @@ +#!/bin/sh + +# Copyright 2026 Canonical Ltd. +# See LICENSE file for licensing details. + +set -xe + +DJANGO_DEBUG="$(snapctl get debug)" +export DJANGO_DEBUG +DJANGO_ALLOWED_HOSTS="$(snapctl get allowed-hosts)" +export DJANGO_ALLOWED_HOSTS +DJANGO_LOG_LEVEL="$(snapctl get log-level)" +export DJANGO_LOG_LEVEL +DJANGO_DATABASE_PASSWORD="$(snapctl get database-password)" +export DJANGO_DATABASE_PASSWORD +DJANGO_DATABASE_HOST="$(snapctl get database-host)" +export DJANGO_DATABASE_HOST +DJANGO_DATABASE_PORT="$(snapctl get database-port)" +export DJANGO_DATABASE_PORT +DJANGO_DATABASE_USER="$(snapctl get database-user)" +export DJANGO_DATABASE_USER +DJANGO_DATABASE_NAME="$(snapctl get database-name)" +export DJANGO_DATABASE_NAME + +LOG_LEVEL="info" +if [ "$DJANGO_DEBUG" = "true" ]; then + LOG_LEVEL="debug" +fi + +exec gunicorn --chdir "$SNAP_DATA/app" --bind 0.0.0.0:8080 haproxy_route_policy.wsgi \ + --capture-output --log-level="$LOG_LEVEL" diff --git a/haproxy-route-policy/snap/scripts/bin/manage b/haproxy-route-policy/snap/scripts/bin/manage new file mode 100755 index 00000000..3829fcfe --- /dev/null +++ b/haproxy-route-policy/snap/scripts/bin/manage @@ -0,0 +1,25 @@ +#!/bin/sh + +# Copyright 2026 Canonical Ltd. +# See LICENSE file for licensing details. + +set -e + +DJANGO_DEBUG="$(snapctl get debug)" +export DJANGO_DEBUG +DJANGO_ALLOWED_HOSTS="$(snapctl get allowed-hosts)" +export DJANGO_ALLOWED_HOSTS +DJANGO_LOG_LEVEL="$(snapctl get log-level)" +export DJANGO_LOG_LEVEL +DJANGO_DATABASE_PASSWORD="$(snapctl get database-password)" +export DJANGO_DATABASE_PASSWORD +DJANGO_DATABASE_HOST="$(snapctl get database-host)" +export DJANGO_DATABASE_HOST +DJANGO_DATABASE_PORT="$(snapctl get database-port)" +export DJANGO_DATABASE_PORT +DJANGO_DATABASE_USER="$(snapctl get database-user)" +export DJANGO_DATABASE_USER +DJANGO_DATABASE_NAME="$(snapctl get database-name)" +export DJANGO_DATABASE_NAME + +exec uv run "$SNAP_DATA/app/manage.py" "$@" diff --git a/haproxy-route-policy/snap/scripts/bin/prepare b/haproxy-route-policy/snap/scripts/bin/prepare new file mode 100755 index 00000000..650b93ea --- /dev/null +++ b/haproxy-route-policy/snap/scripts/bin/prepare @@ -0,0 +1,27 @@ +#!/bin/sh + +# Copyright 2026 Canonical Ltd. +# See LICENSE file for licensing details. + +# The goal of this script is to prepare the snap environment +# for the Django application. + +set -xe + +# ---- +# API (django and gunicorn) +# The only thing that should be kept between refreshes is the database + +# Create the static directory for django +cp -r "$SNAP/app" "$SNAP_DATA/" +chmod -R 755 "$SNAP_DATA/app" + +# Prepare the django app +DJANGO_SECRET_KEY="$(python3 -c 'import secrets; print(secrets.token_urlsafe(50))')" +export DJANGO_SECRET_KEY +printf "%s" "$DJANGO_SECRET_KEY" > "$SNAP_DATA/app/.secret" +python3 "$SNAP_DATA/app/manage.py" collectstatic --noinput + +# Change ownership of some snap directories to allow snap_daemon to read/write +# https://snapcraft.io/docs/system-usernames +chown -R 584788:root "$SNAP_DATA/app" diff --git a/haproxy-route-policy/snap/snapcraft.yaml b/haproxy-route-policy/snap/snapcraft.yaml new file mode 100644 index 00000000..f3e782bd --- /dev/null +++ b/haproxy-route-policy/snap/snapcraft.yaml @@ -0,0 +1,51 @@ +# Copyright 2026 Canonical Ltd. +# See LICENSE file for licensing details. + +name: haproxy-route-policy +base: core24 +version: "0.1" +license: Apache-2.0 +summary: HAProxy Route Policy API +description: | + This snap bundles the HAProxy Route Policy Django application to be included in the haproxy-route-policy-operator. +confinement: strict +platforms: + amd64: + build-on: [amd64] + build-for: [amd64] + +system-usernames: + _daemon_: shared + +parts: + haproxy-route-policy: + plugin: uv + source: . + build-snaps: + - astral-uv + stage-packages: + - gunicorn + stage-snaps: + - astral-uv + + scripts: + plugin: dump + source: ./snap/scripts + override-prime: | + craftctl default + chmod -R +rx $CRAFT_PRIME/bin + +apps: + gunicorn: + command: bin/gunicorn-start + daemon: simple + restart-condition: always + plugs: + - network + - network-bind + + manage: + command: bin/manage + plugs: + - network + - network-bind diff --git a/haproxy-route-policy/uv.lock b/haproxy-route-policy/uv.lock index 924df501..932e3511 100644 --- a/haproxy-route-policy/uv.lock +++ b/haproxy-route-policy/uv.lock @@ -231,6 +231,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/5f/d908ce938356b209d4d27a7fb159ab9100b8814396a69c0204bb66e38703/djangorestframework_types-0.9.0-py3-none-any.whl", hash = "sha256:5e4258fe43774d0a3d018780170bd702bf615407fe244453ea5ec6e6676b98c4", size = 54947, upload-time = "2024-10-10T00:42:02.311Z" }, ] +[[package]] +name = "gunicorn" +version = "25.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dd/13/dd3f8e40ea3ee907a6cbf3d1f1f81afcc3ecd0087d313baabfe95372f15c/gunicorn-25.2.0.tar.gz", hash = "sha256:10bd7adb36d44945d97d0a1fdf9a0fb086ae9c7b39e56b4dece8555a6bf4a09c", size = 632709, upload-time = "2026-03-24T22:49:54.433Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/53/fb024445837e02cd5cf989cf349bfac6f3f433c05184ea5d49c8ade751c6/gunicorn-25.2.0-py3-none-any.whl", hash = "sha256:88f5b444d0055bf298435384af7294f325e2273fd37ba9f9ff7b98e0a1e5dfdc", size = 211659, upload-time = "2026-03-24T22:49:52.528Z" }, +] + [[package]] name = "haproxy-route-policy" version = "0.1.0" @@ -239,6 +251,7 @@ dependencies = [ { name = "django" }, { name = "djangorestframework" }, { name = "djangorestframework-simplejwt" }, + { name = "gunicorn" }, { name = "psycopg2-binary" }, { name = "validators" }, { name = "whitenoise" }, @@ -272,6 +285,7 @@ requires-dist = [ { name = "django", specifier = ">=6.0.3" }, { name = "djangorestframework", specifier = ">=3.16.1" }, { name = "djangorestframework-simplejwt", specifier = ">=5.5.1" }, + { name = "gunicorn", specifier = ">=23.0.0" }, { name = "psycopg2-binary", specifier = ">=2.9.11" }, { name = "validators", specifier = ">=0.35.0" }, { name = "whitenoise", specifier = ">=6.12.0" }, @@ -415,6 +429,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + [[package]] name = "pathspec" version = "1.0.4"