Implement app deployment improvements

This commit is contained in:
2026-05-22 22:25:40 +02:00
parent 60a9e37a86
commit 934770cb68
15 changed files with 552 additions and 25 deletions

View File

@@ -0,0 +1,74 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'USAGE'
Build or patch an application env Secret with a URL-encoded PostgreSQL DATABASE_URL.
Required environment:
APP_NAMESPACE Consumer namespace, for example vergabe-teilnahme
APP_ENV_SECRET Env Secret to create or patch, for example vergabe-teilnahme-env
APP_DB_SECRET Secret containing the raw cnpg role password
APP_DB_USER Database user
APP_DB_HOST Database host
APP_DB_NAME Database name
Optional environment:
APP_DB_PASSWORD_KEY Secret key containing the raw password (default: password)
APP_DB_PORT Database port (default: 5432)
APP_DB_SCHEME URL scheme (default: postgresql)
USAGE
}
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
exit 0
fi
: "${APP_NAMESPACE:?Set APP_NAMESPACE}"
: "${APP_ENV_SECRET:?Set APP_ENV_SECRET}"
: "${APP_DB_SECRET:?Set APP_DB_SECRET}"
: "${APP_DB_USER:?Set APP_DB_USER}"
: "${APP_DB_HOST:?Set APP_DB_HOST}"
: "${APP_DB_NAME:?Set APP_DB_NAME}"
APP_DB_PASSWORD_KEY="${APP_DB_PASSWORD_KEY:-password}"
APP_DB_PORT="${APP_DB_PORT:-5432}"
APP_DB_SCHEME="${APP_DB_SCHEME:-postgresql}"
for cmd in kubectl base64 python3; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "ERROR: missing required command: $cmd" >&2
exit 1
fi
done
raw_password="$(
kubectl get secret "$APP_DB_SECRET" \
-n "$APP_NAMESPACE" \
-o "jsonpath={.data.${APP_DB_PASSWORD_KEY}}" | base64 -d
)"
if [[ -z "$raw_password" ]]; then
echo "ERROR: secret $APP_NAMESPACE/$APP_DB_SECRET did not contain key $APP_DB_PASSWORD_KEY" >&2
exit 1
fi
encoded_password="$(
RAW_PASSWORD="$raw_password" python3 -c 'import os, urllib.parse; print(urllib.parse.quote(os.environ["RAW_PASSWORD"], safe=""))'
)"
database_url="${APP_DB_SCHEME}://${APP_DB_USER}:${encoded_password}@${APP_DB_HOST}:${APP_DB_PORT}/${APP_DB_NAME}"
if kubectl get secret "$APP_ENV_SECRET" -n "$APP_NAMESPACE" >/dev/null 2>&1; then
patch="$(
DATABASE_URL="$database_url" python3 -c 'import json, os; print(json.dumps({"stringData": {"DATABASE_URL": os.environ["DATABASE_URL"]}}))'
)"
kubectl patch secret "$APP_ENV_SECRET" -n "$APP_NAMESPACE" --type=merge -p "$patch"
else
kubectl create secret generic "$APP_ENV_SECRET" \
-n "$APP_NAMESPACE" \
--from-literal=DATABASE_URL="$database_url"
echo "WARN: created $APP_NAMESPACE/$APP_ENV_SECRET with DATABASE_URL only; add other required env keys separately" >&2
fi
echo "Updated DATABASE_URL in secret $APP_NAMESPACE/$APP_ENV_SECRET"

24
tools/check-sops.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env bash
set -euo pipefail
SOPS_SENTINEL="${SOPS_SENTINEL:-helm/gitea-values.sops.yaml}"
SOPS_AGE_KEY_FILE="${SOPS_AGE_KEY_FILE:-$HOME/.config/sops/age/keys.txt}"
if ! command -v sops >/dev/null 2>&1; then
echo "ERROR: sops is not installed" >&2
exit 1
fi
if [[ ! -s "$SOPS_AGE_KEY_FILE" ]]; then
echo "ERROR: SOPS age key file is missing or empty: $SOPS_AGE_KEY_FILE" >&2
echo "Place the operator age identity there, or set SOPS_AGE_KEY_FILE to its path." >&2
exit 1
fi
if [[ ! -f "$SOPS_SENTINEL" ]]; then
echo "ERROR: sentinel file does not exist: $SOPS_SENTINEL" >&2
exit 1
fi
sops -d "$SOPS_SENTINEL" >/dev/null
echo "ok: decrypted $SOPS_SENTINEL with $SOPS_AGE_KEY_FILE"

32
tools/check-tools.sh Executable file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -euo pipefail
missing=0
check_required() {
local cmd="$1"
if command -v "$cmd" >/dev/null 2>&1; then
echo "ok: $cmd"
else
echo "ERROR: missing required tool: $cmd" >&2
missing=1
fi
}
check_required kubectl
check_required helm
check_required sops
check_required python3
if command -v kubectl >/dev/null 2>&1; then
if kubectl cnpg --help >/dev/null 2>&1; then
echo "ok: kubectl cnpg"
else
echo "WARN: kubectl cnpg plugin is missing; install with: kubectl krew install cnpg" >&2
if ! kubectl krew version >/dev/null 2>&1; then
echo "WARN: kubectl krew is missing; install krew before using kubectl krew install cnpg" >&2
fi
fi
fi
exit "$missing"

38
tools/k8s-server-dry-run.sh Executable file
View File

@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"
VERGABE_RELEASE="${VERGABE_RELEASE:-vergabe-teilnahme}"
VERGABE_NAMESPACE="${VERGABE_NAMESPACE:-vergabe-teilnahme}"
VERGABE_CHART="${VERGABE_CHART:-charts/vergabe-teilnahme}"
VERGABE_VALUES="${VERGABE_VALUES:-helm/vergabe-teilnahme-values.yaml}"
DRY_RUN_CREATE_NAMESPACES="${DRY_RUN_CREATE_NAMESPACES:-false}"
for cmd in kubectl helm; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "ERROR: missing required command: $cmd" >&2
exit 1
fi
done
kubectl api-resources >/dev/null
if [[ "$DRY_RUN_CREATE_NAMESPACES" == "true" ]]; then
kubectl create namespace "$VERGABE_NAMESPACE" --dry-run=client -o yaml | kubectl apply -f -
fi
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
helm template "$VERGABE_RELEASE" "$VERGABE_CHART" \
--namespace "$VERGABE_NAMESPACE" \
-f "$VERGABE_VALUES" \
> "$tmpdir/vergabe-teilnahme.yaml"
echo "server dry-run: committed manifests"
kubectl apply --dry-run=server -f manifests
echo "server dry-run: rendered $VERGABE_RELEASE chart"
kubectl apply --dry-run=server -n "$VERGABE_NAMESPACE" -f "$tmpdir/vergabe-teilnahme.yaml"

47
tools/smoke-service.sh Executable file
View File

@@ -0,0 +1,47 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'USAGE'
Run a service smoke test from a persistent pod, then exec curl inside it.
Usage:
NAMESPACE=<namespace> tools/smoke-service.sh http://service.namespace.svc/path
Optional environment:
POD_NAME Reusable smoke pod name (default: service-smoke)
SMOKE_IMAGE Image with curl installed (default: curlimages/curl:8.10.1)
CLEANUP Delete the smoke pod after the test (default: false)
USAGE
}
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" || $# -ne 1 ]]; then
usage
exit 0
fi
target_url="$1"
NAMESPACE="${NAMESPACE:-default}"
POD_NAME="${POD_NAME:-service-smoke}"
SMOKE_IMAGE="${SMOKE_IMAGE:-curlimages/curl:8.10.1}"
CLEANUP="${CLEANUP:-false}"
if ! command -v kubectl >/dev/null 2>&1; then
echo "ERROR: missing required command: kubectl" >&2
exit 1
fi
if ! kubectl get pod "$POD_NAME" -n "$NAMESPACE" >/dev/null 2>&1; then
kubectl run "$POD_NAME" \
-n "$NAMESPACE" \
--image="$SMOKE_IMAGE" \
--restart=Never \
--command -- sleep 3600
fi
kubectl wait -n "$NAMESPACE" --for=condition=Ready "pod/$POD_NAME" --timeout=90s
kubectl exec -n "$NAMESPACE" "$POD_NAME" -- curl -fsS "$target_url"
if [[ "$CLEANUP" == "true" ]]; then
kubectl delete pod "$POD_NAME" -n "$NAMESPACE" --wait=false
fi