From 8a13f6a19a51eeea05ad3a166145f8a46dfd86bf Mon Sep 17 00:00:00 2001 From: tphan025 Date: Fri, 27 Mar 2026 23:16:56 +0100 Subject: [PATCH 01/15] 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" From d2eaf1b834626b1d3d1b8f5c8614fda1eadeac8c Mon Sep 17 00:00:00 2001 From: tphan025 Date: Mon, 30 Mar 2026 08:04:05 +0200 Subject: [PATCH 02/15] fix build issue --- haproxy-route-policy/pyproject.toml | 7 +++++++ haproxy-route-policy/snap/scripts/bin/gunicorn-start | 2 +- haproxy-route-policy/snap/scripts/bin/manage | 2 +- haproxy-route-policy/snap/scripts/bin/prepare | 10 +++++----- haproxy-route-policy/snap/snapcraft.yaml | 2 -- haproxy-route-policy/uv.lock | 2 +- 6 files changed, 15 insertions(+), 10 deletions(-) diff --git a/haproxy-route-policy/pyproject.toml b/haproxy-route-policy/pyproject.toml index 02299906..210730f1 100644 --- a/haproxy-route-policy/pyproject.toml +++ b/haproxy-route-policy/pyproject.toml @@ -14,6 +14,13 @@ dependencies = [ "whitenoise>=6.12.0", ] +[build-system] +requires = ["uv_build>=0.7.2,<1"] +build-backend = "uv_build" + +[tool.uv.build-backend] +module-root = "" + [dependency-groups] auth = [ "djangorestframework-simplejwt>=5.5.1", diff --git a/haproxy-route-policy/snap/scripts/bin/gunicorn-start b/haproxy-route-policy/snap/scripts/bin/gunicorn-start index b6996ca1..1faa7184 100755 --- a/haproxy-route-policy/snap/scripts/bin/gunicorn-start +++ b/haproxy-route-policy/snap/scripts/bin/gunicorn-start @@ -27,5 +27,5 @@ 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 \ +exec gunicorn --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 index 3829fcfe..8a7ef2d6 100755 --- a/haproxy-route-policy/snap/scripts/bin/manage +++ b/haproxy-route-policy/snap/scripts/bin/manage @@ -22,4 +22,4 @@ export DJANGO_DATABASE_USER DJANGO_DATABASE_NAME="$(snapctl get database-name)" export DJANGO_DATABASE_NAME -exec uv run "$SNAP_DATA/app/manage.py" "$@" +exec python3 -m django "$@" --settings=haproxy_route_policy.settings diff --git a/haproxy-route-policy/snap/scripts/bin/prepare b/haproxy-route-policy/snap/scripts/bin/prepare index 650b93ea..a05b5aa5 100755 --- a/haproxy-route-policy/snap/scripts/bin/prepare +++ b/haproxy-route-policy/snap/scripts/bin/prepare @@ -10,17 +10,17 @@ 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" +# Create the writable app directory for runtime data +mkdir -p "$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 + +export DJANGO_STATIC_ROOT="$SNAP_DATA/app/static" +python3 -m django collectstatic --noinput --settings=haproxy_route_policy.settings # Change ownership of some snap directories to allow snap_daemon to read/write # https://snapcraft.io/docs/system-usernames diff --git a/haproxy-route-policy/snap/snapcraft.yaml b/haproxy-route-policy/snap/snapcraft.yaml index f3e782bd..007fe125 100644 --- a/haproxy-route-policy/snap/snapcraft.yaml +++ b/haproxy-route-policy/snap/snapcraft.yaml @@ -23,8 +23,6 @@ parts: source: . build-snaps: - astral-uv - stage-packages: - - gunicorn stage-snaps: - astral-uv diff --git a/haproxy-route-policy/uv.lock b/haproxy-route-policy/uv.lock index 932e3511..c46a486c 100644 --- a/haproxy-route-policy/uv.lock +++ b/haproxy-route-policy/uv.lock @@ -246,7 +246,7 @@ wheels = [ [[package]] name = "haproxy-route-policy" version = "0.1.0" -source = { virtual = "." } +source = { editable = "." } dependencies = [ { name = "django" }, { name = "djangorestframework" }, From 73858c15e94469138172e002f26c12ef3801669e Mon Sep 17 00:00:00 2001 From: tphan025 Date: Mon, 30 Mar 2026 08:21:33 +0200 Subject: [PATCH 03/15] update module-name --- haproxy-route-policy/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/haproxy-route-policy/pyproject.toml b/haproxy-route-policy/pyproject.toml index 210730f1..e076f2a5 100644 --- a/haproxy-route-policy/pyproject.toml +++ b/haproxy-route-policy/pyproject.toml @@ -20,6 +20,7 @@ build-backend = "uv_build" [tool.uv.build-backend] module-root = "" +module-name = ["haproxy_route_policy", "policy"] [dependency-groups] auth = [ From c837b7805ae36880d500dbb0fc8c52fba84d792d Mon Sep 17 00:00:00 2001 From: tphan025 Date: Mon, 30 Mar 2026 08:33:23 +0200 Subject: [PATCH 04/15] set secret key as default, drop fetching from file --- .../haproxy_route_policy/settings.py | 10 +------ haproxy-route-policy/snap/hooks/install | 6 ++--- .../snap/scripts/bin/gunicorn-start | 2 ++ haproxy-route-policy/snap/scripts/bin/manage | 5 ++++ haproxy-route-policy/snap/scripts/bin/prepare | 27 ------------------- 5 files changed, 11 insertions(+), 39 deletions(-) delete mode 100755 haproxy-route-policy/snap/scripts/bin/prepare diff --git a/haproxy-route-policy/haproxy_route_policy/settings.py b/haproxy-route-policy/haproxy_route_policy/settings.py index 685f5412..05b70ee7 100644 --- a/haproxy-route-policy/haproxy_route_policy/settings.py +++ b/haproxy-route-policy/haproxy_route_policy/settings.py @@ -20,15 +20,7 @@ # 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", "") -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() +SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY") DEBUG = os.environ.get("DJANGO_DEBUG", "").lower() == "true" ALLOWED_HOSTS = json.loads(os.getenv("DJANGO_ALLOWED_HOSTS", "[]")) diff --git a/haproxy-route-policy/snap/hooks/install b/haproxy-route-policy/snap/hooks/install index 1a849ae7..94bcbfbe 100755 --- a/haproxy-route-policy/snap/hooks/install +++ b/haproxy-route-policy/snap/hooks/install @@ -5,9 +5,9 @@ set -xe -# Create some directories -mkdir -p "$SNAP_DATA/app" - # set default configuration values snapctl set debug='false' snapctl set log-level='INFO' +snapctl set allowed-hosts='["localhost", "127.0.0.1", "0.0.0.0"]' +SECRET_KEY=$(python3 -c 'import secrets; print(secrets.token_urlsafe(50))') +snapctl set secret-key="$SECRET_KEY" diff --git a/haproxy-route-policy/snap/scripts/bin/gunicorn-start b/haproxy-route-policy/snap/scripts/bin/gunicorn-start index 1faa7184..19de8ea5 100755 --- a/haproxy-route-policy/snap/scripts/bin/gunicorn-start +++ b/haproxy-route-policy/snap/scripts/bin/gunicorn-start @@ -5,6 +5,8 @@ set -xe +DJANGO_SECRET_KEY="$(snapctl get secret-key)" +export DJANGO_SECRET_KEY DJANGO_DEBUG="$(snapctl get debug)" export DJANGO_DEBUG DJANGO_ALLOWED_HOSTS="$(snapctl get allowed-hosts)" diff --git a/haproxy-route-policy/snap/scripts/bin/manage b/haproxy-route-policy/snap/scripts/bin/manage index 8a7ef2d6..fd5853fd 100755 --- a/haproxy-route-policy/snap/scripts/bin/manage +++ b/haproxy-route-policy/snap/scripts/bin/manage @@ -5,9 +5,14 @@ set -e +DJANGO_SECRET_KEY="$(snapctl get secret-key)" +export DJANGO_SECRET_KEY DJANGO_DEBUG="$(snapctl get debug)" export DJANGO_DEBUG DJANGO_ALLOWED_HOSTS="$(snapctl get allowed-hosts)" +if [ -z "$DJANGO_ALLOWED_HOSTS" ]; then + DJANGO_ALLOWED_HOSTS="[]" +fi export DJANGO_ALLOWED_HOSTS DJANGO_LOG_LEVEL="$(snapctl get log-level)" export DJANGO_LOG_LEVEL diff --git a/haproxy-route-policy/snap/scripts/bin/prepare b/haproxy-route-policy/snap/scripts/bin/prepare deleted file mode 100755 index a05b5aa5..00000000 --- a/haproxy-route-policy/snap/scripts/bin/prepare +++ /dev/null @@ -1,27 +0,0 @@ -#!/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) - -# Create the writable app directory for runtime data -mkdir -p "$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" - -export DJANGO_STATIC_ROOT="$SNAP_DATA/app/static" -python3 -m django collectstatic --noinput --settings=haproxy_route_policy.settings - -# 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" From 572302fb2ef5df638178a4fe3b7bdd32387c27c2 Mon Sep 17 00:00:00 2001 From: tphan025 Date: Mon, 30 Mar 2026 09:18:02 +0200 Subject: [PATCH 05/15] update readme, fix secret key generation --- haproxy-route-policy/README.md | 24 ++++++++++++++++++++ haproxy-route-policy/snap/hooks/install | 4 ++-- haproxy-route-policy/snap/scripts/bin/manage | 2 +- haproxy-route-policy/snap/snapcraft.yaml | 5 ++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/haproxy-route-policy/README.md b/haproxy-route-policy/README.md index e69de29b..f97e7ced 100644 --- a/haproxy-route-policy/README.md +++ b/haproxy-route-policy/README.md @@ -0,0 +1,24 @@ +#### Basic setup + +Start a PostgreSQL database: + +``` +docker run -d --name postgres -p 127.0.0.1:5432:5432 -e POSTGRES_PASSWORD=postgres -e POSTGRES_USERNAME=postgres postgres:latest +``` + +Basic snap configurations: + +``` +sudo snap set haproxy-route-policy database-password=postgres +sudo snap set haproxy-route-policy database-host=localhost +sudo snap set haproxy-route-policy database-port=5432 +sudo snap set haproxy-route-policy database-user=postgres +sudo snap set haproxy-route-policy database-name=postgres +``` + +## Learn more +* [Read more](https://charmhub.io/haproxy-operator/docs) + +## Project and community +* [Issues](https://github.com/canonical/haproxy-operator/issues) +* [Matrix](https://matrix.to/#/#charmhub-charmdev:ubuntu.com) diff --git a/haproxy-route-policy/snap/hooks/install b/haproxy-route-policy/snap/hooks/install index 94bcbfbe..81c5db0c 100755 --- a/haproxy-route-policy/snap/hooks/install +++ b/haproxy-route-policy/snap/hooks/install @@ -8,6 +8,6 @@ set -xe # set default configuration values snapctl set debug='false' snapctl set log-level='INFO' -snapctl set allowed-hosts='["localhost", "127.0.0.1", "0.0.0.0"]' -SECRET_KEY=$(python3 -c 'import secrets; print(secrets.token_urlsafe(50))') +snapctl set allowed-hosts='["*"]' +SECRET_KEY="$(tr -dc a-zA-Z0-9 < /dev/urandom | head -c 50)" snapctl set secret-key="$SECRET_KEY" diff --git a/haproxy-route-policy/snap/scripts/bin/manage b/haproxy-route-policy/snap/scripts/bin/manage index fd5853fd..f2543820 100755 --- a/haproxy-route-policy/snap/scripts/bin/manage +++ b/haproxy-route-policy/snap/scripts/bin/manage @@ -27,4 +27,4 @@ export DJANGO_DATABASE_USER DJANGO_DATABASE_NAME="$(snapctl get database-name)" export DJANGO_DATABASE_NAME -exec python3 -m django "$@" --settings=haproxy_route_policy.settings +exec $SNAP/bin/uv run $SNAP/app/manage.py "$@" --settings=haproxy_route_policy.settings diff --git a/haproxy-route-policy/snap/snapcraft.yaml b/haproxy-route-policy/snap/snapcraft.yaml index 007fe125..813eac1f 100644 --- a/haproxy-route-policy/snap/snapcraft.yaml +++ b/haproxy-route-policy/snap/snapcraft.yaml @@ -25,6 +25,11 @@ parts: - astral-uv stage-snaps: - astral-uv + override-build: | + # Also copy the source code to the install directory for the manage.py script + cp -r . $SNAPCRAFT_PART_INSTALL/app + chown -R 584792:584792 $SNAPCRAFT_PART_INSTALL/app + craftctl default scripts: plugin: dump From 0c2f206bb56e5f44e5c3e358988b35937426f568 Mon Sep 17 00:00:00 2001 From: tphan025 Date: Mon, 30 Mar 2026 09:43:37 +0200 Subject: [PATCH 06/15] add change artifact --- docs/release-notes/artifacts/pr0415.yaml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 docs/release-notes/artifacts/pr0415.yaml diff --git a/docs/release-notes/artifacts/pr0415.yaml b/docs/release-notes/artifacts/pr0415.yaml new file mode 100644 index 00000000..8bb7166c --- /dev/null +++ b/docs/release-notes/artifacts/pr0415.yaml @@ -0,0 +1,20 @@ +version_schema: 2 + +changes: + - title: Added snap packaging and runtime scripts for haproxy-route-policy + author: tphan025 + type: minor + description: > + Added snap packaging for the haproxy-route-policy app, including + `snap/snapcraft.yaml`, install/configure hooks, and helper scripts to run + Gunicorn and Django management commands with snap configuration values. + Added Gunicorn as a dependency and configured uv build metadata in + `pyproject.toml` for packaging. Updated the app README with a basic setup + flow for PostgreSQL and snap configuration. + urls: + pr: + - https://github.com/canonical/haproxy-operator/pull/415 + related_doc: + related_issue: + visibility: public + highlight: false From abf3699d75763c8da018b22534c4e36cb72cf646 Mon Sep 17 00:00:00 2001 From: tphan025 Date: Mon, 30 Mar 2026 09:52:21 +0200 Subject: [PATCH 07/15] add build snap workflow --- .github/workflows/test.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 23e5cc81..551ace35 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -24,3 +24,17 @@ jobs: self-hosted-runner-image: "noble" working-directory: ${{ matrix.charm.working-directory }} with-uv: true + build-snap-haproxy-route-policy: + name: Build Snap + runs-on: ubuntu-latest + steps: + - name: Build Snap + id: snapcraft + uses: snapcore/action-build@v1 + with: + path: ./haproxy-route-policy + - name: Upload Snap Artifact + uses: actions/upload-artifact@v3 + with: + name: snap + path: ${{ steps.snapcraft.outputs.snap }} From b4ef74765aa148b8ce906e9315262fbb33392859 Mon Sep 17 00:00:00 2001 From: tphan025 Date: Mon, 30 Mar 2026 10:06:08 +0200 Subject: [PATCH 08/15] use upload-artifact v4 --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 551ace35..66fba512 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -34,7 +34,7 @@ jobs: with: path: ./haproxy-route-policy - name: Upload Snap Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: snap path: ${{ steps.snapcraft.outputs.snap }} From b1e838a728f2e26a0c1cc8361a232ff11da65663 Mon Sep 17 00:00:00 2001 From: tphan025 Date: Mon, 30 Mar 2026 15:02:29 +0200 Subject: [PATCH 09/15] update path --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 66fba512..978a7fd1 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -32,7 +32,7 @@ jobs: id: snapcraft uses: snapcore/action-build@v1 with: - path: ./haproxy-route-policy + path: haproxy-route-policy - name: Upload Snap Artifact uses: actions/upload-artifact@v4 with: From 8f50f401d50e79aa297219a644aac15639521949 Mon Sep 17 00:00:00 2001 From: tphan025 Date: Mon, 30 Mar 2026 16:00:32 +0200 Subject: [PATCH 10/15] add checkout step --- .github/workflows/test.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 978a7fd1..b106d68d 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -28,13 +28,14 @@ jobs: name: Build Snap runs-on: ubuntu-latest steps: + - uses: actions/checkout@v6 - name: Build Snap id: snapcraft uses: snapcore/action-build@v1 with: path: haproxy-route-policy - name: Upload Snap Artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: snap path: ${{ steps.snapcraft.outputs.snap }} From 5fb476225ae14251edcc9c18d30b4e2f4285a67a Mon Sep 17 00:00:00 2001 From: tphan025 Date: Mon, 30 Mar 2026 17:28:50 +0200 Subject: [PATCH 11/15] change working dir for build action --- .github/workflows/test.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b106d68d..d3e8280c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -32,8 +32,6 @@ jobs: - name: Build Snap id: snapcraft uses: snapcore/action-build@v1 - with: - path: haproxy-route-policy - name: Upload Snap Artifact uses: actions/upload-artifact@v5 with: From 21d7eeaf7ba788397255aa10d4b0711b63321377 Mon Sep 17 00:00:00 2001 From: tphan025 Date: Mon, 30 Mar 2026 17:38:02 +0200 Subject: [PATCH 12/15] sparse checkout the policy directory --- .github/workflows/test.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d3e8280c..ad6bcf2b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -29,6 +29,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + with: + sparse-checkout: haproxy-route-policy - name: Build Snap id: snapcraft uses: snapcore/action-build@v1 From ab94eb0bae14c54f2e4d85094f10c8d9f9f0aa8c Mon Sep 17 00:00:00 2001 From: tphan025 Date: Mon, 30 Mar 2026 17:47:14 +0200 Subject: [PATCH 13/15] debug --- .github/workflows/test.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index ad6bcf2b..0eba2ab4 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -31,6 +31,9 @@ jobs: - uses: actions/checkout@v6 with: sparse-checkout: haproxy-route-policy + - run: | + pwd + ls -la - name: Build Snap id: snapcraft uses: snapcore/action-build@v1 From c2a95b691dc252238763d39a8da069d89492a3c0 Mon Sep 17 00:00:00 2001 From: tphan025 Date: Mon, 30 Mar 2026 17:50:33 +0200 Subject: [PATCH 14/15] debug path --- .github/workflows/test.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0eba2ab4..8c6ff04f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -34,9 +34,12 @@ jobs: - run: | pwd ls -la + ls -la haproxy-route-policy - name: Build Snap id: snapcraft uses: snapcore/action-build@v1 + with: + path: haproxy-route-policy - name: Upload Snap Artifact uses: actions/upload-artifact@v5 with: From 9ea13299cc03d43e55ebf5ae07fe0a6a44c4b729 Mon Sep 17 00:00:00 2001 From: tphan025 Date: Mon, 30 Mar 2026 18:01:20 +0200 Subject: [PATCH 15/15] remove debug --- .github/workflows/test.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 8c6ff04f..fcc1406d 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -31,10 +31,6 @@ jobs: - uses: actions/checkout@v6 with: sparse-checkout: haproxy-route-policy - - run: | - pwd - ls -la - ls -la haproxy-route-policy - name: Build Snap id: snapcraft uses: snapcore/action-build@v1