Skip to content

Commit d58caae

Browse files
justb4tomkralidis
andauthored
#510 migrate to pixi Python environment manager (#513)
* #510 forst PoC - create and run GHC with pixi * #510 second PoC - GHC with pixi for Docker using pyproject.toml * #510 3rd PoC - GHC with pixi in GitHub Workflows CI * #510 4rd PoC - GHC with pixi in GitHub Workflows CI - fixes - use more pixi tasks * #510 4rd PoC - GHC with pixi in GitHub Workflows CI - fixes in pixi tasks * #510 5th PoC - GHC with pixi in GitHub Workflows CI - fixes in pixi tasks * #510 6th PoC - fix DB Alembic upgrade mgnt with Flask-Migrate latest * #510 6th PoC - fix DB Alembic upgrade mgnt with Flask-Migrate latest - remove up-downgrade tests * #510 6th PoC - fixes and improvements DB actions manage.py and friends * #510 6th PoC - fixes DB action support - no db-action for now in Worksflows * #510 fixes to allow install in plain Python venv * #510 add Docker Multi Stage build, slim image from 977 to 561MB, run Containers non-root * #510 add Docker Multi Stage build, slim image from 977 to 561MB, run Containers non-root - fix docker GH Task * #510 add comments to Dockerfile * #510 update documentation * Update install.rst * Update admin.rst --------- Co-authored-by: Tom Kralidis <tomkralidis@gmail.com>
1 parent 96f16db commit d58caae

31 files changed

Lines changed: 4874 additions & 336 deletions

.dockerignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,24 @@
1+
# VCS
12
.git
3+
.gitignore
4+
.github
5+
6+
# Local pixi environment (built inside the image via `pixi install`)
7+
.pixi
8+
9+
# Local dev / bootstrap artifacts (regenerated in the image)
10+
instance
11+
report.md
12+
docs/_build
13+
GeoHealthCheck/static/lib
14+
!GeoHealthCheck/static/lib/jqueryui
15+
!GeoHealthCheck/static/lib/jspark
16+
GeoHealthCheck/static/docs
17+
**/data.db
18+
*.log
19+
**/*.log
20+
21+
# Tooling / caches
22+
.pytest_cache
23+
**/__pycache__
24+
*.pyc

.flake8

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[flake8]
22
max-line-length = 79
3-
exclude = .git,.cache,docs,docker,build,dist,GeoHealthCheck/migrations
3+
exclude = .git,.cache,docs,docker,build,dist,.pixi,GeoHealthCheck/migrations
44
# max-complexity = 38

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# SCM syntax highlighting & preventing 3-way merges
2+
pixi.lock merge=binary linguist-language=YAML linguist-generated=true -diff

.github/workflows/docker.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ jobs:
7777
org.opencontainers.image.revision=${{ github.sha }}
7878
7979
- name: GHC Unit Tests with Docker Image ⚙️
80-
run: docker run --entrypoint "/run-tests.sh" ${{ steps.prep.outputs.image }}:${{ steps.prep.outputs.version }}
80+
run: docker run --entrypoint "/app/docker/scripts/run-tests.sh" ${{ steps.prep.outputs.image }}:${{ steps.prep.outputs.version }}
8181

8282
- name: Push to Docker repo (on GH Push only) ☁️
8383
if: ${{ github.event_name == 'push' }}

.github/workflows/main.yml

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# https://github.com/geopython/pycsw/blob/master/.github/workflows/main.yml
33
#
44
# Author: Just van den Broecke - 2021
5+
# Migrated to pixi - 2026 by @francbartoli
56
#
67
name: Main GHC CI ⚙️
78

@@ -10,44 +11,50 @@ on: [ push, pull_request ]
1011
jobs:
1112
main:
1213
runs-on: ubuntu-24.04
13-
strategy:
14-
matrix:
15-
include:
16-
- python-version: 3.12.3
1714
steps:
1815
- name: Checkout ✅
19-
uses: actions/checkout@v2
16+
uses: actions/checkout@v4
2017

21-
- name: Setup Python ${{ matrix.python-version }} 🐍
22-
uses: actions/setup-python@v2
18+
# Installs pixi (pinned) and the `dev` environment from the committed
19+
# pixi.lock (locked: true fails if the lock is out of sync). Python and
20+
# all dependencies come from the lock, so no separate setup-python step.
21+
- name: Setup pixi 🧰
22+
uses: prefix-dev/setup-pixi@v0.10.0
2323
with:
24-
python-version: ${{ matrix.python-version }}
25-
26-
- name: Install Requirements 📦
27-
run: |
28-
python -m pip install --upgrade pip
29-
pip3 install -r requirements.txt
30-
pip3 install -r requirements-dev.txt
24+
pixi-version: v0.72.0
25+
environments: dev
26+
locked: true
27+
cache: true
3128

3229
- name: Setup GHC App and init DB 🗃️
3330
run: |
34-
invoke setup
35-
echo -e "admin\ntest\ntest\nyou@example.com\nyou@example.com" | python3 GeoHealthCheck/models.py create
31+
pixi run -e dev setup
32+
pixi run -e dev create -u admin -p admin -e a@a.com
3633
3734
- name: Flake8 - Verify Coding Conventions ⚙️
38-
run: flake8
35+
run: pixi run -e dev flake8
3936

4037
- name: Load Fixtures Test Data ⚙️
41-
run: python3 GeoHealthCheck/models.py load tests/data/fixtures.json y
38+
run: pixi run -e dev load-data tests/data/fixtures.json
39+
40+
# - name: Test DB downgrade to previous schema (via Alembic) ⚙️
41+
# run: pixi run -e dev db-action downgrade
42+
#
43+
# - name: Test DB upgrade back to current schema (via Alembic) ⚙️
44+
# run: pixi run -e dev db-action upgrade
4245

4346
- name: Run Probes ⚙️
44-
run: python3 GeoHealthCheck/healthcheck.py
47+
run: pixi run -e dev run-healthchecks
4548

4649
- name: Run Unit Tests ⚙️
47-
run: python3 tests/run_tests.py
50+
run: pixi run -e dev test
51+
52+
- name: Publish Test Report 📄
53+
if: always()
54+
run: cat report.md >> "$GITHUB_STEP_SUMMARY" || true
4855

4956
- name: Build Docs 📖
50-
run: cd docs && make html
57+
run: pixi run -e dev docs
5158

5259
- name: Cleanup 💯
53-
run: python3 GeoHealthCheck/models.py drop
60+
run: pixi run -e dev drop-data

.gitignore

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,16 @@ instance
5858
tmp/
5959
GeoHealthCheck/static/docs
6060
GeoHealthCheck/static/lib
61+
!GeoHealthCheck/static/lib/jqueryui
62+
!GeoHealthCheck/static/lib/jspark
6163
GeoHealthCheck.wsgi
6264
GeoHealthCheck.conf
6365

6466
# Data
65-
GeoHealthCheck/data.db
67+
data.db
68+
# test report
69+
report.md
70+
71+
# pixi environments
72+
.pixi/*
73+
!.pixi/config.toml

Dockerfile

Lines changed: 78 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,72 @@
1-
FROM ubuntu:noble
1+
FROM ghcr.io/prefix-dev/pixi:0.72.2-noble AS build
2+
# Inspired by https://tech.quantco.com/blog/pixi-production/
3+
4+
# ARGS
5+
ARG LANGUAGE="en_US"
6+
ARG ENCODING="UTF-8"
7+
8+
ENV LOCALE_STR="${LANGUAGE}.${ENCODING} ${ENCODING}" \
9+
DEBIAN_FRONTEND=noninteractive
10+
11+
# Install and Configure default locale
12+
# NB GHC has its own language handling via Babel.
13+
RUN apt update -y \
14+
&& apt install -y locales \
15+
&& echo "${LOCALE_STR}" > /etc/locale.gen \
16+
&& locale-gen \
17+
&& rm -rf /var/lib/apt/lists/*
18+
19+
# Copy entire GHC repo content to /app
20+
WORKDIR /app
21+
COPY . .
22+
23+
# Install deps (in /app/.pixi/).
24+
RUN pixi install -e prod --locked
25+
26+
# pixi-env.sh: to be sourced, sets all paths and more.
27+
RUN echo '#!/bin/bash' > /app/pixi-env.sh
28+
RUN pixi shell-hook -e prod -s bash --as-is >> /app/pixi-env.sh
29+
RUN echo 'exec "$@"' >> /app/pixi-env.sh
30+
RUN echo chmod +x /app/pixi-env.sh
31+
32+
# Prepare the GHC app, mainly web-related.
33+
RUN pixi run -e prod setup
34+
RUN cp docker/config_site.py instance/
35+
RUN if [ -d docker/plugins ]; then cp -ar docker/plugins/* /app/GeoHealthCheck/plugins/; fi
36+
37+
# Slim down, removing unused files generated within build
38+
RUN rm -rf /app/GeoHealthCheck/docs
39+
RUN find /app/GeoHealthCheck/static/lib -type d -name docs | xargs rm -rf
40+
RUN find /app/GeoHealthCheck/static/lib -type d -name src | xargs rm -rf
41+
42+
FROM ubuntu:24.04 AS production
243

344
# Credits to yjacolin for providing first versions
445
LABEL original_developer="yjacolin <yves.jacolin@camptocamp.com>" \
546
maintainer="Just van den Broecke <justb4@gmail.com>"
647

48+
# Copy the compiled locale files from the builder
49+
COPY --from=build /usr/lib/locale/locale-archive /usr/lib/locale/locale-archive
50+
COPY --from=build /etc/locale.gen /etc/locale.gen
51+
COPY --from=build /etc/default/locale /etc/default/locale
52+
753
# These are default values,
854
# Override when running container via docker(-compose)
9-
10-
# ARGS
11-
ARG TZ="Etc/UTC"
12-
ARG LANG="en_US.UTF-8"
13-
ARG ADD_DEB_PACKAGES=""
14-
15-
# General ENV settings
16-
ENV LC_ALL="en_US.UTF-8" \
17-
LANG="en_US.UTF-8" \
18-
LANGUAGE="en_US.UTF-8" \
19-
\
20-
\
21-
DEB_PACKAGES="locales gunicorn python3.12-venv postgresql-client python3-gunicorn python3-gevent python3-lxml python3-pyproj" \
22-
DEB_BUILD_DEPS="make python3-pip" \
55+
ENV LANG='en_US.UTF-8' \
56+
LANGUAGE='en_US:en' \
57+
LC_ALL='en_US.UTF-8' \
58+
TZ='Etc/UTC' \
59+
DEB_PACKAGES="ca-certificates postgresql-client" \
60+
DEB_BUILD_DEPS="adduser" \
2361
ADMIN_NAME=admin \
2462
ADMIN_PWD=admin \
2563
ADMIN_EMAIL=admin.istrator@mydomain.com \
26-
SQLALCHEMY_DATABASE_URI='sqlite:////GeoHealthCheck/DB/data.db' \
64+
SQLALCHEMY_DATABASE_URI='sqlite:////app/instance/DB/data.db' \
2765
SQLALCHEMY_ENGINE_OPTION_PRE_PING=False \
2866
SECRET_KEY='d544ccc37dc3ad214c09b1b7faaa64c60351d5c8bb48b342' \
67+
GHC_HOME=/app \
68+
GHC_USER=ghc \
69+
GHC_USER_HOME=/home/ghc \
2970
GHC_PROBE_HTTP_TIMEOUT_SECS=30 \
3071
GHC_MINIMAL_RUN_FREQUENCY_MINS=10 \
3172
GHC_RETENTION_DAYS=30 \
@@ -54,52 +95,40 @@ ENV LC_ALL="en_US.UTF-8" \
5495
GHC_GEOIP_LATFIELD='lat' \
5596
GHC_GEOIP_LONFIELD='lon' \
5697
GHC_METADATA_CACHE_SECS=900 \
57-
\
58-
# WSGI server settings, assumed is gunicorn \
59-
HOST=0.0.0.0 \
60-
PORT=80 \
61-
WSGI_WORKERS=4 \
62-
WSGI_WORKER_TIMEOUT=6000 \
63-
WSGI_WORKER_CLASS='gevent' \
64-
\
98+
HOST=0.0.0.0 \
99+
PORT=80 \
100+
WSGI_WORKERS=4 \
101+
WSGI_WORKER_TIMEOUT=6000 \
102+
WSGI_WORKER_CLASS='gevent' \
103+
GHC_USER_PLUGINS=''
104+
65105
# GHC Core Plugins modules and/or classes, seldom needed to set: \
66106
# if not specified here or in Container environment \
67107
# all GHC built-in Plugins will be active. \
68108
#ENV GHC_PLUGINS 'GeoHealthCheck.plugins.probe.owsgetcaps,\
69109
# GeoHealthCheck.plugins.probe.wms, ...., ...\
70110
# GeoHealthCheck.plugins.check.checks' \
71-
\
72-
# GHC User Plugins, best be overridden via Container environment \
73-
GHC_USER_PLUGINS=''
74-
75-
# Install operating system dependencies
76-
RUN \
77-
apt-get update \
78-
&& apt-get --no-install-recommends install -y ${DEB_PACKAGES} ${DEB_BUILD_DEPS} ${ADD_DEB_PACKAGES} \
79-
&& localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 \
80-
&& echo "For ${TZ} date=$(date)" && echo "Locale=$(locale)"
81111

82-
# Add standard files and Add/override Plugins
83-
# Alternative Entrypoints to run GHC jobs
84-
# Override default Entrypoint with these on Containers
85-
COPY docker/scripts/*.sh docker/config_site.py docker/plugins /
86-
87-
# Add Source Code
88-
COPY . /GeoHealthCheck
112+
# GHC User Plugins, best be overridden via Container environment \
89113

90-
# Install
114+
# Install remaining dependencies and create user.
91115
RUN \
92-
chmod a+x /*.sh && ./install.sh \
93-
# Cleanup TODO: remove unused Locales and TZs \
94-
&& apt-get remove --purge -y ${DEB_BUILD_DEPS} \
95-
&& apt-get clean \
116+
apt update \
117+
&& apt --no-install-recommends install -y ${DEB_PACKAGES} ${DEB_BUILD_DEPS} \
118+
&& echo "For ${TZ} date=$(date)" && echo "Locale=$(locale)" \
119+
&& adduser --disabled-password --shell /bin/bash --gecos "User" ${GHC_USER} \
120+
&& apt remove --purge -y ${DEB_BUILD_DEPS} \
121+
&& apt clean \
96122
&& apt autoremove -y \
97123
&& rm -rf /var/lib/apt/lists/*
98124

125+
# Copy entire bundle: GHC plus all dependencies, including non-Python binaries in /app.
126+
WORKDIR /app
127+
COPY --from=build --chown=${GHC_USER}:${GHC_USER} /app /app
99128

100-
# For SQLite
101-
VOLUME ["/GeoHealthCheck/DB/"]
129+
# Run Containers as user.
130+
USER ${GHC_USER}
102131

103132
EXPOSE ${PORT}
104133

105-
ENTRYPOINT /run-web.sh
134+
ENTRYPOINT [ "/app/docker/scripts/run-web.sh" ]

GeoHealthCheck/manage.py

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,16 @@
77
# Usage:
88
#
99
# $ python3 manage.py --help
10-
# usage: manage.py [-h] {shell,db,runserver} ...
11-
#
12-
# positional arguments:
13-
# {shell,db,runserver}
14-
# shell Runs a Python shell inside Flask application context.
15-
# db Perform database migrations
16-
# runserver Runs the Flask development server i.e. app.run()
10+
# usage: manage.py action
1711
#
1812
# optional arguments:
1913
# -h, --help show this help message and exit
2014
#
2115
# For DB management:
22-
# $ python3 manage.py db --help
16+
# $ python manage.py upgrade
2317
# usage: Perform database migrations
2418
#
25-
# positional arguments:
19+
# action arguments:
2620
# {upgrade,migrate,current,stamp,init,downgrade,history,revision}
2721
# upgrade Upgrade to a later version
2822
# migrate Alias for 'revision --autogenerate'
@@ -37,17 +31,44 @@
3731
# optional arguments:
3832
# -h, --help show this help message and exit
3933

40-
from flask_script import Manager
41-
from flask_migrate import Migrate, MigrateCommand
34+
import sys
35+
import os
36+
from flask_migrate import (Migrate, upgrade, downgrade, current,
37+
migrate, history, revision)
4238
from init import App
4339

4440
DB = App.get_db()
4541
APP = App.get_app()
42+
workdir_path = os.path.dirname(__file__)
43+
migrations_path = os.path.join(workdir_path, 'migrations')
44+
Migrate(APP, DB, directory=migrations_path)
45+
ACTIONS = ['current', 'upgrade', 'downgrade', 'migrate', 'history', 'revision']
46+
47+
if __name__ == '__main__':
48+
if len(sys.argv) < 1 or sys.argv[1] not in ACTIONS:
49+
print(f'Invalid action, valid values: {ACTIONS}')
50+
sys.exit(1)
4651

47-
migrate = Migrate(APP, DB)
52+
# Valid action name
53+
action = sys.argv[1]
4854

49-
manager = Manager(APP)
50-
manager.add_command('db', MigrateCommand)
55+
os.chdir(workdir_path)
5156

52-
if __name__ == '__main__':
53-
manager.run()
57+
with APP.app_context():
58+
if action == 'current':
59+
current()
60+
elif action == 'upgrade':
61+
# Upgrade to latest version
62+
upgrade()
63+
elif action == 'downgrade':
64+
# Downgrade one version back
65+
downgrade()
66+
elif action == 'migrate':
67+
# Generate revision
68+
migrate()
69+
elif action == 'history':
70+
# Show revisions
71+
history()
72+
elif action == 'revision':
73+
# Create new revision
74+
revision()

0 commit comments

Comments
 (0)