sandcastle-ng

Scripts for the deployment of Sandcastle (GNU Taler)
Log | Files | Refs | README

commit 9063241ee279a5e08369edb06d283a578e0a1d44
parent ed7f70629285f64901ab6d2080f5193ac6d10307
Author: Florian Dold <dold@taler.net>
Date:   Sat, 22 Aug 2026 18:04:58 +0200

sandcastle: expose deployment status endpoint

Diffstat:
MDockerfile | 6++++++
MREADME.md | 9+++++++++
Msandcastle-build | 6++++++
Mscripts/demo/setup-sandcastle.sh | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mtests/test_sandcastle_build_packages.py | 8++++++++
5 files changed, 104 insertions(+), 2 deletions(-)

diff --git a/Dockerfile b/Dockerfile @@ -131,3 +131,9 @@ RUN sed -i /etc/postgresql/17/main/postgresql.conf -e 's/^port[ ]*=.*$/port = 54 # and thus fail, clobbering the systemd status. RUN systemctl mask systemd-modules-load.service RUN systemctl disable proc-sys-fs-binfmt_misc.automount + +# Keep the Sandcastle revision in its own final layer so revision-only changes +# do not invalidate package installation and image configuration. +ARG SANDCASTLE_VERSION=unknown +RUN install -d /usr/share/sandcastle && \ + printf '%s\n' "$SANDCASTLE_VERSION" >/usr/share/sandcastle/version diff --git a/README.md b/README.md @@ -142,6 +142,8 @@ Wait until everything has been set up: While provisioning is running, this follows the output of ``setup-sandcastle.service`` inside the container. It exits successfully once provisioning has completed, or with a non-zero status if provisioning fails. +After a successful deployment, the landing host serves the Sandcastle version, +component versions, and deployment time at ``/metrics/sandcastle-status``. Note that ``./sandcastle-run`` is just a wrapper around ``podman run``. If required, you can pass addtional arguments to ``./sandcastle-run``. @@ -172,6 +174,13 @@ the services will be available as This gives a nice environment for integration testing. +# Running the Unit Tests + +Run the Python unit-test suite from the repository root: + + python3 -m unittest discover -s tests -v + + # Stopping the deployment For stopping the deployment simply run diff --git a/sandcastle-build b/sandcastle-build @@ -38,6 +38,11 @@ if [[ ! -r $containerfile ]]; then exit 1 fi +sandcastle_version=unknown +if git_version=$(git describe --tags --always --dirty 2>/dev/null); then + sandcastle_version=$git_version +fi + builder_image=localhost/taler-sandcastle-package-builder:latest cache_dir=$SCRIPT_DIR/cache mkdir -p \ @@ -67,6 +72,7 @@ python3 buildscripts/sandcastle_build_packages.py \ echo "Building final sandcastle image" exec "${build_args[@]}" "${no_cache_args[@]}" \ + --build-arg "SANDCASTLE_VERSION=$sandcastle_version" \ --target taler-final \ --tag taler-base-all \ . diff --git a/scripts/demo/setup-sandcastle.sh b/scripts/demo/setup-sandcastle.sh @@ -40,7 +40,57 @@ update_config_block() { fi } +SANDCASTLE_STATUS_DIR=/var/www/sandcastle/metrics +SANDCASTLE_STATUS_FILE=$SANDCASTLE_STATUS_DIR/sandcastle-status + +# Atomically publish the version information for the status endpoint after a +# successful deployment. +write_sandcastle_status() { + local temporary_file + local sandcastle_version=unknown + local architecture + local component_dir + local component + local package + local versions + local turnstile_version + + if [[ -s /usr/share/sandcastle/version ]]; then + sandcastle_version=$(</usr/share/sandcastle/version) + fi + architecture=$(dpkg --print-architecture) + mkdir -p "$SANDCASTLE_STATUS_DIR" + temporary_file=$(mktemp "$SANDCASTLE_STATUS_FILE.XXXXXX") + + { + printf 'sandcastle: %s\n' "$sandcastle_version" + printf 'deployed-at: %s\n' "$(date --utc +%Y-%m-%dT%H:%M:%SZ)" + printf 'components:\n' + for component_dir in "/packages/$architecture"/*; do + [[ -d $component_dir ]] || continue + component=${component_dir##*/} + versions=$( + for package in "$component_dir"/*.deb; do + [[ -f $package ]] || continue + dpkg-deb --field "$package" Version + done | sort --unique | paste --serial --delimiters=, + ) + [[ -n $versions ]] || continue + printf ' %s: %s\n' "$component" "$versions" + done + if turnstile_version=$( + git -C /opt/turnstile describe --tags --always --dirty 2>/dev/null + ); then + printf ' turnstile: %s\n' "$turnstile_version" + fi + } >"$temporary_file" + + chmod 644 "$temporary_file" + mv "$temporary_file" "$SANDCASTLE_STATUS_FILE" +} + echo "Provisioning sandcastle" +rm -f "$SANDCASTLE_STATUS_FILE" "$SANDCASTLE_STATUS_FILE".* # General configuration. # Might eventually be moved to an external file. @@ -132,6 +182,7 @@ PORT_INTERNAL_EXCHANGE=8201 PORT_INTERNAL_MERCHANT=8301 PORT_INTERNAL_LIBEUFIN_BANK=8080 PORT_INTERNAL_LANDING=8501 +PORT_INTERNAL_LANDING_SERVICE=8511 PORT_INTERNAL_BLOG=8502 PORT_INTERNAL_DONATIONS=8503 PORT_INTERNAL_BANK_SPA=8505 @@ -481,7 +532,7 @@ CURRENCY = $CURRENCY [frontend-demo-landing] SERVE = http -HTTP_PORT = $PORT_INTERNAL_LANDING +HTTP_PORT = $PORT_INTERNAL_LANDING_SERVICE [frontend-demo-blog] SERVE = http @@ -658,6 +709,17 @@ cat <<EOF >/etc/caddy/Caddyfile } } +(sandcastle_landing) { + handle /metrics/sandcastle-status { + root * /var/www/sandcastle + header Content-Type "text/plain; charset=utf-8" + file_server + } + handle { + reverse_proxy :$PORT_INTERNAL_LANDING_SERVICE + } +} + # Services that only listen on unix domain sockets # are reverse-proxied to serve on a TCP port. @@ -678,6 +740,10 @@ cat <<EOF >/etc/caddy/Caddyfile } } +:$PORT_INTERNAL_LANDING { + import sandcastle_landing +} + :$PORT_INTERNAL_BANK_SPA { root * /usr/share/libeufin/spa root /settings.json /etc/libeufin/ @@ -734,6 +800,11 @@ https://$BANK_DOMAIN { } } +https://$LANDING_DOMAIN { + tls internal + import sandcastle_landing +} + https://$EXCHANGE_DOMAIN { tls internal reverse_proxy unix//run/taler-exchange/httpd/exchange-http.sock @@ -805,7 +876,7 @@ http://$CHALLENGER_DOMAIN$PORT_SUFFIX { } http://$LANDING_DOMAIN$PORT_SUFFIX { - reverse_proxy :$PORT_INTERNAL_LANDING + import sandcastle_landing } http://$BLOG_DOMAIN$PORT_SUFFIX { @@ -1443,5 +1514,7 @@ fi cd / +write_sandcastle_status + # FIXME: Maybe do some taler-wallet-cli test? # FIXME: How do we report errors occurring during the setup script? diff --git a/tests/test_sandcastle_build_packages.py b/tests/test_sandcastle_build_packages.py @@ -254,6 +254,11 @@ class BuildOrchestrationTests(unittest.TestCase): executable = root / "bin" / command executable.write_text(fake_command, encoding="utf-8") executable.chmod(0o755) + git = root / "bin" / "git" + git.write_text( + "#!/bin/sh\nprintf 'sandcastle-version\\n'\n", encoding="utf-8" + ) + git.chmod(0o755) environment = os.environ.copy() environment["PATH"] = f"{root / 'bin'}:{environment['PATH']}" @@ -274,6 +279,9 @@ class BuildOrchestrationTests(unittest.TestCase): self.assertIn("<buildscripts/sandcastle_build_packages.py>", calls[1]) self.assertIn("<--force>", calls[1]) self.assertIn("<--target> <taler-final>", calls[2]) + self.assertIn( + "<--build-arg> <SANDCASTLE_VERSION=sandcastle-version>", calls[2] + ) self.assertIn("<--no-cache>", calls[2]) log.write_text("", encoding="utf-8")