From cc6084ca585bcea34ed09ddca89e738886ac4c6a Mon Sep 17 00:00:00 2001 From: jklein Date: Fri, 21 Aug 2026 23:12:38 +0200 Subject: [PATCH 1/3] Add install-test stage to the PR pipeline (k3d) New install-test job in validate-charts.yml: for exactly the charts a PR changes, install them into an ephemeral k3d cluster (pinned k3d v5.9.0, k3s v1.35.5-k3s1) and require every Deployment/StatefulSet to become Ready. Runs non-blocking (continue-on-error) during the stabilization phase. Fixtures live at the repo root under ci/ (not inside chart directories, so they neither trigger publishes on merge nor get packaged): a stub OnePasswordItem CRD, per-chart test values and fixtures (dummy secrets with in-cluster DSNs, throwaway postgres/redis/dex, synapse-generate seed job, static hostPath PVs where charts pin storage classes or need RWX), plus a shared ci/run-install-test.sh harness that also runs locally. Excluded with documented reasons (ci/excluded-charts.txt): satisfactory (multi-GB image), teamspeak-server (TS6 beta license expired, image crashes on start), template-chart (placeholder image). All 14 non-excluded charts verified locally in k3d using only the committed fixtures. --- .github/workflows/validate-charts.yml | 34 +++ README.md | 16 +- ci/apache-tika/test-values.yaml | 3 + ci/calibre-web-automated/fixtures.yaml | 82 +++++++ ci/calibre-web-automated/settings.env | 2 + ci/calibre-web-automated/test-values.yaml | 12 + ci/common/fixtures.yaml | 24 ++ ci/cyberchef/test-values.yaml | 5 + ci/excluded-charts.txt | 5 + ci/gotenberg/test-values.yaml | 3 + ci/homeassistant/fixtures.yaml | 19 ++ ci/homeassistant/settings.env | 2 + ci/homeassistant/test-values.yaml | 9 + ci/matrix-synapse/fixtures.yaml | 95 ++++++++ ci/matrix-synapse/settings.env | 2 + ci/matrix-synapse/test-values.yaml | 14 ++ ci/outline/fixtures.yaml | 184 +++++++++++++++ ci/outline/test-values.yaml | 7 + ci/paperless-ngx/fixtures.yaml | 193 ++++++++++++++++ ci/paperless-ngx/settings.env | 2 + ci/paperless-ngx/test-values.yaml | 10 + ci/patchmon/fixtures.yaml | 269 ++++++++++++++++++++++ ci/patchmon/test-values.yaml | 19 ++ ci/rallly/fixtures.yaml | 143 ++++++++++++ ci/rallly/test-values.yaml | 12 + ci/run-install-test.sh | 112 +++++++++ ci/samba/fixtures.yaml | 10 + ci/samba/test-values.yaml | 10 + ci/urlaubsverwaltung/fixtures.yaml | 141 ++++++++++++ ci/urlaubsverwaltung/settings.env | 2 + ci/urlaubsverwaltung/test-values.yaml | 6 + ci/voucher-vault/fixtures.yaml | 188 +++++++++++++++ ci/voucher-vault/test-values.yaml | 8 + ci/zipline/fixtures.yaml | 65 ++++++ ci/zipline/test-values.yaml | 9 + 35 files changed, 1716 insertions(+), 1 deletion(-) create mode 100644 ci/apache-tika/test-values.yaml create mode 100644 ci/calibre-web-automated/fixtures.yaml create mode 100644 ci/calibre-web-automated/settings.env create mode 100644 ci/calibre-web-automated/test-values.yaml create mode 100644 ci/common/fixtures.yaml create mode 100644 ci/cyberchef/test-values.yaml create mode 100644 ci/excluded-charts.txt create mode 100644 ci/gotenberg/test-values.yaml create mode 100644 ci/homeassistant/fixtures.yaml create mode 100644 ci/homeassistant/settings.env create mode 100644 ci/homeassistant/test-values.yaml create mode 100644 ci/matrix-synapse/fixtures.yaml create mode 100644 ci/matrix-synapse/settings.env create mode 100644 ci/matrix-synapse/test-values.yaml create mode 100644 ci/outline/fixtures.yaml create mode 100644 ci/outline/test-values.yaml create mode 100644 ci/paperless-ngx/fixtures.yaml create mode 100644 ci/paperless-ngx/settings.env create mode 100644 ci/paperless-ngx/test-values.yaml create mode 100644 ci/patchmon/fixtures.yaml create mode 100644 ci/patchmon/test-values.yaml create mode 100644 ci/rallly/fixtures.yaml create mode 100644 ci/rallly/test-values.yaml create mode 100755 ci/run-install-test.sh create mode 100644 ci/samba/fixtures.yaml create mode 100644 ci/samba/test-values.yaml create mode 100644 ci/urlaubsverwaltung/fixtures.yaml create mode 100644 ci/urlaubsverwaltung/settings.env create mode 100644 ci/urlaubsverwaltung/test-values.yaml create mode 100644 ci/voucher-vault/fixtures.yaml create mode 100644 ci/voucher-vault/test-values.yaml create mode 100644 ci/zipline/fixtures.yaml create mode 100644 ci/zipline/test-values.yaml diff --git a/.github/workflows/validate-charts.yml b/.github/workflows/validate-charts.yml index d087452..1a86d43 100644 --- a/.github/workflows/validate-charts.yml +++ b/.github/workflows/validate-charts.yml @@ -67,3 +67,37 @@ jobs: else echo "Version bump OK: $BASE_VERSION -> $NEW_VERSION" fi + + install-test: + name: "Install-test ${{ matrix.chart }}" + needs: [detect-changed-charts, build-chart] + if: needs.detect-changed-charts.outputs.any-chart-changed == 'true' + runs-on: ubuntu-latest + # Stabilization phase (issue #22): the job reports failures but does not + # block the PR. Promote to a required check (drop continue-on-error) once + # it has proven stable across several chart PRs. + continue-on-error: true + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + chart: ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} + env: + K3D_VERSION: v5.9.0 + K3S_IMAGE: docker.io/rancher/k3s:v1.35.5-k3s1 + steps: + - name: "Checkout Repository" + uses: actions/checkout@v4 + - name: "Install Helm" + uses: azure/setup-helm@v4 + - name: "Install k3d" + run: curl -sSfL https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | TAG="$K3D_VERSION" bash + - name: "Create k3d cluster" + run: k3d cluster create install-test --image "$K3S_IMAGE" --wait --timeout 180s + # Excluded charts (see ci/excluded-charts.txt) are skipped inside the + # script with their documented reason. + - name: "Install chart and wait for readiness" + run: ci/run-install-test.sh "${{ matrix.chart }}" + - name: "Delete k3d cluster" + if: always() + run: k3d cluster delete install-test || true diff --git a/README.md b/README.md index fb9f5d4..1e44cc4 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,21 @@ Collection of some self-made helm-charts. Each top-level directory is a standalo - `publish-.yml` — one thin workflow per chart; triggers on pushes to `main` touching `/**` (or manually) and calls the reusable workflow below. **A push to a chart directory on `main` publishes that chart**, so the chart version must be bumped in the same change. - `publish-helm-chart.yml` — reusable workflow: `helm package`, then `helm push` to the private JK-Registry. Registry credentials come from 1Password Connect (`OP_CONNECT_TOKEN` secret, items under `op://GitHub-Actions/`). - `change-app-version.yml` — manually dispatched app-version bump; the chart must be listed in its `chart-name` choice list. -- `validate-charts.yml` — PR pipeline: detects which charts a PR touches and, for exactly those, runs `helm lint --strict`, `helm package`, and a chart-version bump check (`template-chart` is exempt from the version check). PRs without chart changes skip the build matrix. +- `validate-charts.yml` — PR pipeline: detects which charts a PR touches and, for exactly those, runs `helm lint --strict`, `helm package`, and a chart-version bump check (`template-chart` is exempt from the version check). PRs without chart changes skip the build matrix. After the build stage, an **install-test** job (same changed-charts matrix) installs each changed chart into an ephemeral [k3d](https://k3d.io) cluster on the runner and requires every workload to become Ready — see below. + +### Install-test (PR pipeline stage 2) + +For every chart a PR changes, `ci/run-install-test.sh ` runs against a throwaway k3d cluster: it applies the stub `OnePasswordItem` CRD plus the chart's fixtures, `helm install`s the chart with its CI values, and waits for all Deployments/StatefulSets to roll out (per-chart timeout). Since probes and security hardening are active on all workloads, "Ready" means the app actually answers. On failure the job dumps pod descriptions, events and container logs. + +Fixtures live at the **repo root** under `ci/` (deliberately *not* inside the chart directories, so they are neither packaged into the chart tgz nor trigger the publish workflows on merge): + +- `ci/common/` — fixtures applied for every chart (schema-less stub CRD for `OnePasswordItem`) +- `ci//test-values.yaml` — values for the CI install (required for every installable chart) +- `ci//fixtures.yaml` — dummy secrets with in-cluster connection data, throwaway infrastructure (postgres/redis/dex), static hostPath PVs/StorageClasses where the chart pins a storage class or needs RWX, and seed jobs (e.g. `synapse generate`) +- `ci//settings.env` — optional overrides (e.g. `INSTALL_TIMEOUT` for slow starters) +- `ci/excluded-charts.txt` — charts that cannot run in CI, each with a justification. Currently: `satisfactory` (multi-GB image + 10GB+ Steam download), `teamspeak-server` (the TS6 v6.0.0-beta8 image crashes with "The default license has expired" until the appVersion is bumped), `template-chart` (scaffold with placeholder image) + +Fixture images avoid Docker Hub where possible (`public.ecr.aws`/`ghcr.io`) to dodge runner rate limits. The job currently runs with `continue-on-error: true` (stabilization phase); it will be promoted to a required check once it has proven stable across several chart PRs. When adding a new chart, add its `ci//` fixtures (or an entry in `ci/excluded-charts.txt`) — the install-test fails if both are missing. The script also runs locally against any existing cluster: `k3d cluster create t && ci/run-install-test.sh && k3d cluster delete t`. ## Creating a new chart diff --git a/ci/apache-tika/test-values.yaml b/ci/apache-tika/test-values.yaml new file mode 100644 index 0000000..8c234cf --- /dev/null +++ b/ci/apache-tika/test-values.yaml @@ -0,0 +1,3 @@ +# apache-tika needs no extra values for a CI install: no secrets, no ingress, +# no persistence. +{} diff --git a/ci/calibre-web-automated/fixtures.yaml b/ci/calibre-web-automated/fixtures.yaml new file mode 100644 index 0000000..ffe8ebd --- /dev/null +++ b/ci/calibre-web-automated/fixtures.yaml @@ -0,0 +1,82 @@ +# Fixtures for the calibre-web-automated install-test: the cwa secret the +# 1Password operator would otherwise materialize and static hostPath PVs +# (the consume PVC is ReadWriteMany, which local-path cannot provision). +# This chart names resources --..., release name is ci. +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: ci-static +provisioner: kubernetes.io/no-provisioner +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: node-setup +spec: + backoffLimit: 2 + template: + spec: + restartPolicy: Never + containers: + - name: setup + image: public.ecr.aws/docker/library/busybox:1.36 + command: + - sh + - -c + - > + mkdir -p /pv/cwa/data /pv/cwa/consume && + chmod -R 0777 /pv/cwa + volumeMounts: + - name: pv + mountPath: /pv + volumes: + - name: pv + hostPath: + path: /var/lib/ci-pv + type: DirectoryOrCreate +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: ci-cwa-data +spec: + capacity: + storage: 2Gi + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + storageClassName: ci-static + claimRef: + namespace: default + name: calibre-web-automated-ci-cwa-pvc + hostPath: + path: /var/lib/ci-pv/cwa/data + type: DirectoryOrCreate +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: ci-cwa-consume +spec: + capacity: + storage: 1Gi + accessModes: + - ReadWriteMany + persistentVolumeReclaimPolicy: Retain + storageClassName: ci-static + claimRef: + namespace: default + name: calibre-web-automated-ci-cwa-consume-pvc + hostPath: + path: /var/lib/ci-pv/cwa/consume + type: DirectoryOrCreate +--- +apiVersion: v1 +kind: Secret +metadata: + name: calibre-web-automated-ci-cwa-secret +stringData: + hardcover-api-token: ci-dummy-token + # Mounted as /config/.config/calibre/customize.py.json (calibre plugin + # registry); an empty JSON object is a valid file. + calibre-plugins: "{}" diff --git a/ci/calibre-web-automated/settings.env b/ci/calibre-web-automated/settings.env new file mode 100644 index 0000000..beb1ef7 --- /dev/null +++ b/ci/calibre-web-automated/settings.env @@ -0,0 +1,2 @@ +# First start unpacks the calibre library scaffold onto the volume. +INSTALL_TIMEOUT=600 diff --git a/ci/calibre-web-automated/test-values.yaml b/ci/calibre-web-automated/test-values.yaml new file mode 100644 index 0000000..2be27b3 --- /dev/null +++ b/ci/calibre-web-automated/test-values.yaml @@ -0,0 +1,12 @@ +secrets: + cwaSecretRef: op://ci/cwa + +cwa: + # Both PVCs share this class and the consume PVC is RWX, which k3d's + # local-path provisioner cannot serve - the fixtures pre-bind static + # hostPath PVs of class ci-static instead. + storageClass: ci-static + storage: 2Gi + +ingress: + domain: cwa.ci.local diff --git a/ci/common/fixtures.yaml b/ci/common/fixtures.yaml new file mode 100644 index 0000000..c967d3d --- /dev/null +++ b/ci/common/fixtures.yaml @@ -0,0 +1,24 @@ +# Schema-less stub CRD for the 1Password operator's OnePasswordItem CR. +# The charts render OnePasswordItem resources; in CI there is no operator, so +# this stub lets them apply cleanly while the actual Kubernetes Secrets are +# hand-created per chart in ci//fixtures.yaml. +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: onepassworditems.onepassword.com +spec: + group: onepassword.com + names: + kind: OnePasswordItem + listKind: OnePasswordItemList + plural: onepassworditems + singular: onepassworditem + scope: Namespaced + versions: + - name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + x-kubernetes-preserve-unknown-fields: true diff --git a/ci/cyberchef/test-values.yaml b/ci/cyberchef/test-values.yaml new file mode 100644 index 0000000..64aa16e --- /dev/null +++ b/ci/cyberchef/test-values.yaml @@ -0,0 +1,5 @@ +# cyberchef: stateless, no secrets. Only the ingress host is required +# (clusterIssuer already has a default). The Ingress just has to apply - +# there is no ingress-nginx/cert-manager in the CI cluster. +ingress: + domain: cyberchef.ci.local diff --git a/ci/excluded-charts.txt b/ci/excluded-charts.txt new file mode 100644 index 0000000..e5b62da --- /dev/null +++ b/ci/excluded-charts.txt @@ -0,0 +1,5 @@ +# Charts excluded from the PR install-test, one per line: . +# ci/run-install-test.sh skips these with the given reason. +satisfactory the game-server image is multiple GB and Steam downloads 10GB+ game files on first start - impractical on a CI runner +teamspeak-server the TS6 v6.0.0-beta8 image crashes on start ("The default license has expired. Please use the latest server version.", re-verified 2026-08-21); nothing to test until the appVersion is bumped +template-chart scaffold with placeholder image "registry/image:xxx", never published diff --git a/ci/gotenberg/test-values.yaml b/ci/gotenberg/test-values.yaml new file mode 100644 index 0000000..221ca9e --- /dev/null +++ b/ci/gotenberg/test-values.yaml @@ -0,0 +1,3 @@ +# gotenberg needs no extra values for a CI install: no secrets, no ingress, +# no persistence. +{} diff --git a/ci/homeassistant/fixtures.yaml b/ci/homeassistant/fixtures.yaml new file mode 100644 index 0000000..56c88d4 --- /dev/null +++ b/ci/homeassistant/fixtures.yaml @@ -0,0 +1,19 @@ +# Fixtures for the homeassistant install-test: the config secret the +# 1Password operator would otherwise materialize (release name: ci). The +# chart mounts the three keys as configuration.yaml, http-config.yaml and +# recorder-config.yaml under /config. +apiVersion: v1 +kind: Secret +metadata: + name: ci-homeassistant-config-secret +stringData: + config: | + default_config: + http: !include http-config.yaml + recorder: !include recorder-config.yaml + http-config: | + use_x_forwarded_for: true + trusted_proxies: + - 10.42.0.0/16 + recorder-config: | + db_url: sqlite:////config/home-assistant_v2.db diff --git a/ci/homeassistant/settings.env b/ci/homeassistant/settings.env new file mode 100644 index 0000000..82e2c53 --- /dev/null +++ b/ci/homeassistant/settings.env @@ -0,0 +1,2 @@ +# Home Assistant's first boot installs/initializes integrations. +INSTALL_TIMEOUT=600 diff --git a/ci/homeassistant/test-values.yaml b/ci/homeassistant/test-values.yaml new file mode 100644 index 0000000..5ba0aeb --- /dev/null +++ b/ci/homeassistant/test-values.yaml @@ -0,0 +1,9 @@ +secrets: + configSecretRef: op://ci/homeassistant-config + +core: + storage: + # k3d's default dynamic provisioner (the values default "default" does + # not exist there). Home Assistant starts as root, so writability of the + # dynamically provisioned volume is not an issue. + storageClass: local-path diff --git a/ci/matrix-synapse/fixtures.yaml b/ci/matrix-synapse/fixtures.yaml new file mode 100644 index 0000000..fb5d192 --- /dev/null +++ b/ci/matrix-synapse/fixtures.yaml @@ -0,0 +1,95 @@ +# Fixtures for the matrix-synapse install-test. Synapse needs a signing key +# and a log config on its PVC before it starts, so the PVC is backed by a +# pre-bound hostPath PV and a seed Job runs "synapse generate" into that +# directory first (same pattern as the k3d verification for PRs #21/#26). +# The chart mounts homeserver.yaml from the config secret; the static config +# below points at the generated key/log-config paths. Release name: ci. +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: ci-static +provisioner: kubernetes.io/no-provisioner +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: ci-matrix-synapse-data +spec: + capacity: + storage: 2Gi + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + storageClassName: ci-static + claimRef: + namespace: default + name: ci-matrix-synapse-pvc + hostPath: + path: /var/lib/ci-pv/matrix-synapse + type: DirectoryOrCreate +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: synapse-seed +spec: + backoffLimit: 2 + template: + spec: + restartPolicy: Never + containers: + - name: generate + # Same image the chart deploys (appVersion of matrix-synapse). + image: ghcr.io/element-hq/synapse:v1.149.1 + args: ["generate"] + env: + - name: SYNAPSE_SERVER_NAME + value: synapse.ci.local + - name: SYNAPSE_REPORT_STATS + value: "no" + # Match the UID/GID the chart runs synapse with, so the generated + # signing key (mode 0600) is readable by the server container. + - name: UID + value: "1000" + - name: GID + value: "1000" + volumeMounts: + - name: pv + mountPath: /data + subPath: matrix-synapse-data + volumes: + - name: pv + hostPath: + path: /var/lib/ci-pv/matrix-synapse + type: DirectoryOrCreate +--- +apiVersion: v1 +kind: Secret +metadata: + name: ci-matrix-synapse-config-secret +stringData: + homeserver.yaml: | + server_name: "synapse.ci.local" + pid_file: /data/homeserver.pid + listeners: + - port: 8008 + tls: false + type: http + x_forwarded: true + resources: + - names: [client, federation] + compress: false + database: + name: sqlite3 + args: + database: /data/homeserver.db + log_config: "/data/synapse.ci.local.log.config" + media_store_path: /media_store + signing_key_path: "/data/synapse.ci.local.signing.key" + registration_shared_secret: "ci-registration-shared-secret" + macaroon_secret_key: "ci-macaroon-secret-key" + form_secret: "ci-form-secret" + report_stats: false + trusted_key_servers: + - server_name: "matrix.org" + suppress_key_server_warning: true diff --git a/ci/matrix-synapse/settings.env b/ci/matrix-synapse/settings.env new file mode 100644 index 0000000..162d60b --- /dev/null +++ b/ci/matrix-synapse/settings.env @@ -0,0 +1,2 @@ +# Synapse first start (SQLite schema setup) plus the config seed job. +INSTALL_TIMEOUT=600 diff --git a/ci/matrix-synapse/test-values.yaml b/ci/matrix-synapse/test-values.yaml new file mode 100644 index 0000000..622c13b --- /dev/null +++ b/ci/matrix-synapse/test-values.yaml @@ -0,0 +1,14 @@ +matrix: + serverName: synapse.ci.local + configSecretRef: op://ci/matrix-synapse-config + delegation: + enabled: true + serverUrl: ci.local + +storage: + amount: 2Gi + storageClass: ci-static + +ingress: + clusterIssuer: ci + domain: matrix.ci.local diff --git a/ci/outline/fixtures.yaml b/ci/outline/fixtures.yaml new file mode 100644 index 0000000..f9b6f66 --- /dev/null +++ b/ci/outline/fixtures.yaml @@ -0,0 +1,184 @@ +# Fixtures for the outline install-test: throwaway postgres + redis, a dex +# OIDC provider (outline fetches the issuer's discovery document at boot), +# and the secrets the 1Password operator would otherwise materialize +# (release name: ci). Outline's /_health probe checks Postgres AND Redis. +apiVersion: v1 +kind: Secret +metadata: + name: ci-outline-secret +stringData: + # Outline requires 64-char hex secrets. + outline-secret-key: 5f4dcc3b5aa765d61d8327deb882cf995f4dcc3b5aa765d61d8327deb882cf99 + outline-utils-secret: 9c1185a5c5e9fc54612808977ee8f5489c1185a5c5e9fc54612808977ee8f548 + discovery-url: http://dex.default.svc.cluster.local:5556/dex + client-id: ci-client + client-secret: ci-client-secret +--- +apiVersion: v1 +kind: Secret +metadata: + name: ci-outline-db-secret +stringData: + connection-url: postgres://ci:ci@postgres:5432/outline +--- +apiVersion: v1 +kind: Secret +metadata: + name: ci-outline-redis-secret +stringData: + connection-url: redis://:cipass@redis:6379 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres + labels: + app: postgres +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: public.ecr.aws/docker/library/postgres:16-alpine + env: + - name: POSTGRES_USER + value: ci + - name: POSTGRES_PASSWORD + value: ci + - name: POSTGRES_DB + value: outline + ports: + - containerPort: 5432 + readinessProbe: + exec: + command: ["pg_isready", "-U", "ci"] + periodSeconds: 5 + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + volumes: + - name: data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: postgres +spec: + selector: + app: postgres + ports: + - port: 5432 + targetPort: 5432 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + labels: + app: redis +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + containers: + - name: redis + image: public.ecr.aws/docker/library/redis:7-alpine + args: ["redis-server", "--requirepass", "cipass"] + ports: + - containerPort: 6379 + readinessProbe: + tcpSocket: + port: 6379 + periodSeconds: 5 +--- +apiVersion: v1 +kind: Service +metadata: + name: redis +spec: + selector: + app: redis + ports: + - port: 6379 + targetPort: 6379 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: dex-config +data: + config.yaml: | + issuer: http://dex.default.svc.cluster.local:5556/dex + storage: + type: memory + web: + http: 0.0.0.0:5556 + oauth2: + skipApprovalScreen: true + staticClients: + - id: ci-client + secret: ci-client-secret + name: CI Client + redirectURIs: + - https://outline.ci.local/auth/oidc.callback + enablePasswordDB: true +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dex + labels: + app: dex +spec: + replicas: 1 + selector: + matchLabels: + app: dex + template: + metadata: + labels: + app: dex + spec: + containers: + - name: dex + image: ghcr.io/dexidp/dex:v2.41.1 + command: ["dex", "serve", "/etc/dex/cfg/config.yaml"] + ports: + - containerPort: 5556 + readinessProbe: + httpGet: + path: /dex/healthz + port: 5556 + periodSeconds: 5 + volumeMounts: + - name: config + mountPath: /etc/dex/cfg + volumes: + - name: config + configMap: + name: dex-config +--- +apiVersion: v1 +kind: Service +metadata: + name: dex +spec: + selector: + app: dex + ports: + - port: 5556 + targetPort: 5556 diff --git a/ci/outline/test-values.yaml b/ci/outline/test-values.yaml new file mode 100644 index 0000000..e75b9f2 --- /dev/null +++ b/ci/outline/test-values.yaml @@ -0,0 +1,7 @@ +secrets: + configSecretRef: op://ci/outline-config + redisSecretRef: op://ci/outline-redis + dbSecretRef: op://ci/outline-db + +ingress: + domain: outline.ci.local diff --git a/ci/paperless-ngx/fixtures.yaml b/ci/paperless-ngx/fixtures.yaml new file mode 100644 index 0000000..770e64c --- /dev/null +++ b/ci/paperless-ngx/fixtures.yaml @@ -0,0 +1,193 @@ +# Fixtures for the paperless-ngx install-test: throwaway postgres + redis, +# the secrets the 1Password operator would otherwise materialize (release +# name: ci), and storage plumbing. The chart hardcodes storageClassName +# "longhorn" (RWX for the consume PVC), which does not exist in k3d: a +# no-provisioner StorageClass named "longhorn" plus pre-bound hostPath PVs +# stand in for it. Paperless runs as uid 1000 with a read-only rootfs and +# hostPath volumes do not support fsGroup, so a node-setup Job pre-creates +# the exact subPath directories world-writable. +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: longhorn +provisioner: kubernetes.io/no-provisioner +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: node-setup +spec: + backoffLimit: 2 + template: + spec: + restartPolicy: Never + containers: + - name: setup + image: public.ecr.aws/docker/library/busybox:1.36 + command: + - sh + - -c + - > + mkdir -p /pv/paperless-ngx/data/data /pv/paperless-ngx/data/media + /pv/paperless-ngx/data/export /pv/paperless-ngx/consume && + chmod -R 0777 /pv/paperless-ngx + volumeMounts: + - name: pv + mountPath: /pv + volumes: + - name: pv + hostPath: + path: /var/lib/ci-pv + type: DirectoryOrCreate +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: ci-paperless-data +spec: + capacity: + storage: 20Gi + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + storageClassName: longhorn + claimRef: + namespace: default + name: ci-paperless-ngx-volume-claim + hostPath: + path: /var/lib/ci-pv/paperless-ngx/data + type: DirectoryOrCreate +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: ci-paperless-consume +spec: + capacity: + storage: 1Gi + accessModes: + - ReadWriteMany + persistentVolumeReclaimPolicy: Retain + storageClassName: longhorn + claimRef: + namespace: default + name: ci-paperless-ngx-consume-volume-claim + hostPath: + path: /var/lib/ci-pv/paperless-ngx/consume + type: DirectoryOrCreate +--- +apiVersion: v1 +kind: Secret +metadata: + name: ci-paperless-ngx-redis-secret +stringData: + redis-connection-url: redis://:cipass@redis:6379 + key-prefix: ci +--- +apiVersion: v1 +kind: Secret +metadata: + name: ci-paperless-ngx-db-secret +stringData: + server: postgres + port: "5432" + database: paperless + username: ci + password: ci +--- +apiVersion: v1 +kind: Secret +metadata: + name: ci-paperless-ngx-secret +stringData: + username: admin + password: ci-admin-password + paperless-secret-key: paperless-ci-secret-key-0123456789abcdef0123456789abcdef +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres + labels: + app: postgres +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: public.ecr.aws/docker/library/postgres:16-alpine + env: + - name: POSTGRES_USER + value: ci + - name: POSTGRES_PASSWORD + value: ci + - name: POSTGRES_DB + value: paperless + ports: + - containerPort: 5432 + readinessProbe: + exec: + command: ["pg_isready", "-U", "ci"] + periodSeconds: 5 + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + volumes: + - name: data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: postgres +spec: + selector: + app: postgres + ports: + - port: 5432 + targetPort: 5432 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + labels: + app: redis +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + containers: + - name: redis + image: public.ecr.aws/docker/library/redis:7-alpine + args: ["redis-server", "--requirepass", "cipass"] + ports: + - containerPort: 6379 + readinessProbe: + tcpSocket: + port: 6379 + periodSeconds: 5 +--- +apiVersion: v1 +kind: Service +metadata: + name: redis +spec: + selector: + app: redis + ports: + - port: 6379 + targetPort: 6379 diff --git a/ci/paperless-ngx/settings.env b/ci/paperless-ngx/settings.env new file mode 100644 index 0000000..d12791f --- /dev/null +++ b/ci/paperless-ngx/settings.env @@ -0,0 +1,2 @@ +# Paperless runs database migrations and an index check on first start. +INSTALL_TIMEOUT=600 diff --git a/ci/paperless-ngx/test-values.yaml b/ci/paperless-ngx/test-values.yaml new file mode 100644 index 0000000..35dbb33 --- /dev/null +++ b/ci/paperless-ngx/test-values.yaml @@ -0,0 +1,10 @@ +secrets: + redisRef: op://ci/paperless-redis + databaseRef: op://ci/paperless-db + paperlessRef: op://ci/paperless + +ingress: + domain: paperless.ci.local + +# smbConnection and oidc stay disabled (defaults): the SMB CSI driver and a +# real OIDC login flow are out of scope for the CI install-test. diff --git a/ci/patchmon/fixtures.yaml b/ci/patchmon/fixtures.yaml new file mode 100644 index 0000000..c548ce1 --- /dev/null +++ b/ci/patchmon/fixtures.yaml @@ -0,0 +1,269 @@ +# Fixtures for the patchmon install-test: throwaway postgres + redis, a dex +# OIDC provider, the secrets the 1Password operator would otherwise +# materialize (release name: ci), a pre-bound world-writable hostPath PV for +# the assets volume (the non-root backend writes agent assets at boot; +# hostPath does not support fsGroup), and a Service alias "backend": the +# frontend image's nginx template resolves an upstream host literally named +# "backend" at startup (found during the probes verification of PR #21). +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: ci-static +provisioner: kubernetes.io/no-provisioner +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: node-setup +spec: + backoffLimit: 2 + template: + spec: + restartPolicy: Never + containers: + - name: setup + image: public.ecr.aws/docker/library/busybox:1.36 + command: + - sh + - -c + - > + mkdir -p /pv/patchmon/patchmon-assets /pv/patchmon/patchmon-agents && + chmod -R 0777 /pv/patchmon + volumeMounts: + - name: pv + mountPath: /pv + volumes: + - name: pv + hostPath: + path: /var/lib/ci-pv + type: DirectoryOrCreate +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: ci-patchmon-data +spec: + capacity: + storage: 10Gi + accessModes: + # The assets PVC is shared by backend and frontend as ReadWriteMany. + - ReadWriteMany + persistentVolumeReclaimPolicy: Retain + storageClassName: ci-static + claimRef: + namespace: default + name: ci-patchmon-pvc + hostPath: + path: /var/lib/ci-pv/patchmon + type: DirectoryOrCreate +--- +apiVersion: v1 +kind: Service +metadata: + name: backend +spec: + selector: + app: ci-patchmon-backend + ports: + - name: http + port: 80 + targetPort: 3001 + - name: direct + port: 3001 + targetPort: 3001 +--- +apiVersion: v1 +kind: Secret +metadata: + name: ci-patchmon-postgres-secret +stringData: + database-url: postgresql://ci:ci@postgres:5432/patchmon +--- +apiVersion: v1 +kind: Secret +metadata: + name: ci-patchmon-redis-secret +stringData: + host: redis + port: "6379" + username: default + password: cipass + db: "0" +--- +apiVersion: v1 +kind: Secret +metadata: + name: ci-patchmon-patchmon-secret +stringData: + jwt-secret: patchmon-ci-jwt-secret-0123456789abcdef0123456789abcdef + encryption-key: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef + session-secret: patchmon-ci-session-secret-0123456789abcdef0123456789abcdef +--- +apiVersion: v1 +kind: Secret +metadata: + name: ci-patchmon-oidc-secret +stringData: + issuer-url: http://dex.default.svc.cluster.local:5556/dex + client-id: ci-client + client-secret: ci-client-secret + redirect-uri: https://patchmon.ci.local/auth/callback + requested-scopes: openid profile email + button-text: CI SSO + user-group-name: users + admin-group-name: admins +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres + labels: + app: postgres +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: public.ecr.aws/docker/library/postgres:16-alpine + env: + - name: POSTGRES_USER + value: ci + - name: POSTGRES_PASSWORD + value: ci + - name: POSTGRES_DB + value: patchmon + ports: + - containerPort: 5432 + readinessProbe: + exec: + command: ["pg_isready", "-U", "ci"] + periodSeconds: 5 + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + volumes: + - name: data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: postgres +spec: + selector: + app: postgres + ports: + - port: 5432 + targetPort: 5432 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + labels: + app: redis +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + containers: + - name: redis + image: public.ecr.aws/docker/library/redis:7-alpine + args: ["redis-server", "--requirepass", "cipass"] + ports: + - containerPort: 6379 + readinessProbe: + tcpSocket: + port: 6379 + periodSeconds: 5 +--- +apiVersion: v1 +kind: Service +metadata: + name: redis +spec: + selector: + app: redis + ports: + - port: 6379 + targetPort: 6379 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: dex-config +data: + config.yaml: | + issuer: http://dex.default.svc.cluster.local:5556/dex + storage: + type: memory + web: + http: 0.0.0.0:5556 + oauth2: + skipApprovalScreen: true + staticClients: + - id: ci-client + secret: ci-client-secret + name: CI Client + redirectURIs: + - https://patchmon.ci.local/auth/callback + enablePasswordDB: true +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dex + labels: + app: dex +spec: + replicas: 1 + selector: + matchLabels: + app: dex + template: + metadata: + labels: + app: dex + spec: + containers: + - name: dex + image: ghcr.io/dexidp/dex:v2.41.1 + command: ["dex", "serve", "/etc/dex/cfg/config.yaml"] + ports: + - containerPort: 5556 + readinessProbe: + httpGet: + path: /dex/healthz + port: 5556 + periodSeconds: 5 + volumeMounts: + - name: config + mountPath: /etc/dex/cfg + volumes: + - name: config + configMap: + name: dex-config +--- +apiVersion: v1 +kind: Service +metadata: + name: dex +spec: + selector: + app: dex + ports: + - port: 5556 + targetPort: 5556 diff --git a/ci/patchmon/test-values.yaml b/ci/patchmon/test-values.yaml new file mode 100644 index 0000000..ed811c1 --- /dev/null +++ b/ci/patchmon/test-values.yaml @@ -0,0 +1,19 @@ +secrets: + redisSecretRef: op://ci/patchmon-redis + postgresSecretRef: op://ci/patchmon-postgres + oidcSecretRef: op://ci/patchmon-oidc + patchmonSecretRef: op://ci/patchmon + +ingress: + clusterIssuer: ci + domain: patchmon.ci.local + +patchmon: + storage: + # Point the RWX assets PVC at the fixture StorageClass so the pre-created + # world-writable hostPath PV is used (local-path cannot provision RWX, and + # the non-root backend must write its assets dir at boot). Note: the PVC + # template guards on .storage.storageClass but renders .storage.class, + # so both keys must be set. + storageClass: ci-static + class: ci-static diff --git a/ci/rallly/fixtures.yaml b/ci/rallly/fixtures.yaml new file mode 100644 index 0000000..c546735 --- /dev/null +++ b/ci/rallly/fixtures.yaml @@ -0,0 +1,143 @@ +# Fixtures for the rallly install-test: throwaway postgres, a dex OIDC +# provider (the OIDC discovery URL must answer) and the secrets the +# 1Password operator would otherwise materialize (release name: ci). +apiVersion: v1 +kind: Secret +metadata: + name: ci-rallly-secret +stringData: + secret-key: rallly-ci-secret-key-0123456789abcdef0123456789abcdef + initial-admin-email: ci@example.org +--- +apiVersion: v1 +kind: Secret +metadata: + name: ci-rallly-database-secret +stringData: + database-url: postgres://ci:ci@postgres:5432/rallly +--- +apiVersion: v1 +kind: Secret +metadata: + name: ci-rallly-oidc-secret +stringData: + discovery-url: http://dex.default.svc.cluster.local:5556/dex/.well-known/openid-configuration + client-id: ci-client + client-secret: ci-client-secret +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres + labels: + app: postgres +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: public.ecr.aws/docker/library/postgres:16-alpine + env: + - name: POSTGRES_USER + value: ci + - name: POSTGRES_PASSWORD + value: ci + - name: POSTGRES_DB + value: rallly + ports: + - containerPort: 5432 + readinessProbe: + exec: + command: ["pg_isready", "-U", "ci"] + periodSeconds: 5 + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + volumes: + - name: data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: postgres +spec: + selector: + app: postgres + ports: + - port: 5432 + targetPort: 5432 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: dex-config +data: + config.yaml: | + issuer: http://dex.default.svc.cluster.local:5556/dex + storage: + type: memory + web: + http: 0.0.0.0:5556 + oauth2: + skipApprovalScreen: true + staticClients: + - id: ci-client + secret: ci-client-secret + name: CI Client + redirectURIs: + - https://rallly.ci.local/api/auth/callback/oidc + enablePasswordDB: true +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dex + labels: + app: dex +spec: + replicas: 1 + selector: + matchLabels: + app: dex + template: + metadata: + labels: + app: dex + spec: + containers: + - name: dex + image: ghcr.io/dexidp/dex:v2.41.1 + command: ["dex", "serve", "/etc/dex/cfg/config.yaml"] + ports: + - containerPort: 5556 + readinessProbe: + httpGet: + path: /dex/healthz + port: 5556 + periodSeconds: 5 + volumeMounts: + - name: config + mountPath: /etc/dex/cfg + volumes: + - name: config + configMap: + name: dex-config +--- +apiVersion: v1 +kind: Service +metadata: + name: dex +spec: + selector: + app: dex + ports: + - port: 5556 + targetPort: 5556 diff --git a/ci/rallly/test-values.yaml b/ci/rallly/test-values.yaml new file mode 100644 index 0000000..f7badd1 --- /dev/null +++ b/ci/rallly/test-values.yaml @@ -0,0 +1,12 @@ +secrets: + ralllySecretRef: op://ci/rallly + oidcSecretRef: op://ci/rallly-oidc + databaseSecretRef: op://ci/rallly-db + # smtp stays disabled (default), so no smtpSecretRef is needed. + +rallly: + supportEmail: ci@example.org + +ingress: + clusterIssuer: ci + domain: rallly.ci.local diff --git a/ci/run-install-test.sh b/ci/run-install-test.sh new file mode 100755 index 0000000..c00737c --- /dev/null +++ b/ci/run-install-test.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +# Install-test harness: installs one chart into the cluster of the current +# kubectl context (an ephemeral k3d cluster in CI) using only the committed +# fixtures under ci// and requires every workload to become Ready. +# +# Usage: ci/run-install-test.sh +# +# Layout (repo root): +# ci/common/ fixtures applied for every chart (OnePasswordItem stub CRD) +# ci//test-values.yaml values for the CI install (required) +# ci//fixtures.yaml dummy secrets / throwaway infra / static PVs (optional) +# ci//settings.env optional overrides, e.g. INSTALL_TIMEOUT=600 +# ci/excluded-charts.txt charts that cannot run in CI, with reasons +set -euo pipefail + +CHART="${1:?usage: $0 }" +NAMESPACE="default" +RELEASE="ci" +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +CI_DIR="$ROOT/ci" + +# Per-chart override via ci//settings.env (INSTALL_TIMEOUT in seconds). +INSTALL_TIMEOUT=300 +FIXTURE_JOB_TIMEOUT=600 +if [ -f "$CI_DIR/$CHART/settings.env" ]; then + # shellcheck disable=SC1090 + source "$CI_DIR/$CHART/settings.env" +fi + +# --- Exclusion list --------------------------------------------------------- +if [ -f "$CI_DIR/excluded-charts.txt" ]; then + REASON="$(grep -E "^$CHART([[:space:]]|$)" "$CI_DIR/excluded-charts.txt" | sed -E "s/^$CHART[[:space:]]*//" || true)" + if [ -n "$REASON" ]; then + echo "SKIP: chart '$CHART' is excluded from the install-test: $REASON" + exit 0 + fi +fi + +if [ ! -d "$ROOT/$CHART" ] || [ ! -f "$ROOT/$CHART/Chart.yaml" ]; then + echo "ERROR: '$CHART' is not a chart directory" >&2 + exit 1 +fi +if [ ! -f "$CI_DIR/$CHART/test-values.yaml" ]; then + echo "ERROR: missing ci/$CHART/test-values.yaml — every installable chart needs CI fixtures." >&2 + echo "Add test values (and fixtures.yaml if the app needs secrets/infra), or add the chart" >&2 + echo "to ci/excluded-charts.txt with a justification." >&2 + exit 1 +fi + +# --- Failure diagnostics ---------------------------------------------------- +diagnose() { + echo "=================== INSTALL-TEST FAILED: DIAGNOSTICS ===================" + echo "--- pods ---" + kubectl -n "$NAMESPACE" get pods -o wide || true + echo "--- events (most recent last) ---" + kubectl -n "$NAMESPACE" get events --sort-by=.metadata.creationTimestamp | tail -60 || true + echo "--- describe pods ---" + kubectl -n "$NAMESPACE" describe pods || true + echo "--- container logs ---" + for POD in $(kubectl -n "$NAMESPACE" get pods -o name); do + echo "--- logs $POD ---" + kubectl -n "$NAMESPACE" logs "$POD" --all-containers --tail=150 --prefix || true + kubectl -n "$NAMESPACE" logs "$POD" --all-containers --tail=50 --previous --prefix 2>/dev/null || true + done + echo "========================================================================" +} +trap 'RC=$?; if [ $RC -ne 0 ]; then diagnose; fi; exit $RC' EXIT + +# --- 1. Apply fixtures ------------------------------------------------------ +echo "::group::Apply fixtures" +kubectl apply -f "$CI_DIR/common/" +if [ -f "$CI_DIR/$CHART/fixtures.yaml" ]; then + kubectl apply -f "$CI_DIR/$CHART/fixtures.yaml" +fi +echo "::endgroup::" + +# --- 2. Wait for fixture jobs (node setup, data seeding) and infra ---------- +echo "::group::Wait for fixture jobs and infrastructure" +JOBS="$(kubectl -n "$NAMESPACE" get jobs -o name 2>/dev/null || true)" +for JOB in $JOBS; do + echo "Waiting for $JOB to complete..." + kubectl -n "$NAMESPACE" wait --for=condition=complete "$JOB" --timeout="${FIXTURE_JOB_TIMEOUT}s" +done +for RES in $(kubectl -n "$NAMESPACE" get deploy,sts -o name 2>/dev/null || true); do + echo "Waiting for fixture $RES..." + kubectl -n "$NAMESPACE" rollout status "$RES" --timeout="${FIXTURE_JOB_TIMEOUT}s" +done +echo "::endgroup::" + +# --- 3. Install the chart --------------------------------------------------- +echo "::group::helm install" +helm install "$RELEASE" "$ROOT/$CHART" \ + --namespace "$NAMESPACE" \ + -f "$CI_DIR/$CHART/test-values.yaml" +echo "::endgroup::" + +# --- 4. Require every workload to become Ready ------------------------------ +echo "::group::Wait for workloads (timeout ${INSTALL_TIMEOUT}s each)" +WORKLOADS="$(kubectl -n "$NAMESPACE" get deploy,sts -o name)" +if [ -z "$WORKLOADS" ]; then + echo "ERROR: the release created no Deployments/StatefulSets" >&2 + exit 1 +fi +for RES in $WORKLOADS; do + echo "Waiting for $RES..." + kubectl -n "$NAMESPACE" rollout status "$RES" --timeout="${INSTALL_TIMEOUT}s" +done +echo "::endgroup::" + +echo "--- final state ---" +kubectl -n "$NAMESPACE" get pods -o wide +echo "OK: chart '$CHART' installed and all workloads are Ready." diff --git a/ci/samba/fixtures.yaml b/ci/samba/fixtures.yaml new file mode 100644 index 0000000..8469479 --- /dev/null +++ b/ci/samba/fixtures.yaml @@ -0,0 +1,10 @@ +# Fixtures for the samba install-test: the users secret the 1Password +# operator would otherwise materialize (release name: ci). users.conf uses +# the dockur/samba "user:password" format. +apiVersion: v1 +kind: Secret +metadata: + name: ci-samba-users-secret +stringData: + users.conf: | + ciuser:ci-password diff --git a/ci/samba/test-values.yaml b/ci/samba/test-values.yaml new file mode 100644 index 0000000..73ba1e7 --- /dev/null +++ b/ci/samba/test-values.yaml @@ -0,0 +1,10 @@ +secrets: + sambaUsersRef: op://ci/samba-users + +samba: + storageClass: local-path + shares: + - name: ci-share + validUsers: ciuser + description: CI test share + size: 1Gi diff --git a/ci/urlaubsverwaltung/fixtures.yaml b/ci/urlaubsverwaltung/fixtures.yaml new file mode 100644 index 0000000..ef58745 --- /dev/null +++ b/ci/urlaubsverwaltung/fixtures.yaml @@ -0,0 +1,141 @@ +# Fixtures for the urlaubsverwaltung install-test: throwaway postgres, a dex +# OIDC provider (Spring performs OIDC discovery against the issuer at boot) +# and the secrets the 1Password operator would otherwise materialize. +# Note: this chart names secrets --..., release name is ci. +apiVersion: v1 +kind: Secret +metadata: + name: urlaubsverwaltung-ci-uv-db-secret +stringData: + jdbc-url: jdbc:postgresql://postgres:5432/urlaubsverwaltung + username: ci + password: ci +--- +apiVersion: v1 +kind: Secret +metadata: + name: urlaubsverwaltung-ci-uv-oidc-secret +stringData: + client-id: ci-client + client-secret: ci-client-secret + issuer-name: CI + redirect-uri: https://uv.ci.local/login/oauth2/code/default + issuer-uri: http://dex.default.svc.cluster.local:5556/dex + jwt-issuer-uri: http://dex.default.svc.cluster.local:5556/dex +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres + labels: + app: postgres +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: public.ecr.aws/docker/library/postgres:16-alpine + env: + - name: POSTGRES_USER + value: ci + - name: POSTGRES_PASSWORD + value: ci + - name: POSTGRES_DB + value: urlaubsverwaltung + ports: + - containerPort: 5432 + readinessProbe: + exec: + command: ["pg_isready", "-U", "ci"] + periodSeconds: 5 + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + volumes: + - name: data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: postgres +spec: + selector: + app: postgres + ports: + - port: 5432 + targetPort: 5432 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: dex-config +data: + config.yaml: | + issuer: http://dex.default.svc.cluster.local:5556/dex + storage: + type: memory + web: + http: 0.0.0.0:5556 + oauth2: + skipApprovalScreen: true + staticClients: + - id: ci-client + secret: ci-client-secret + name: CI Client + redirectURIs: + - https://uv.ci.local/login/oauth2/code/default + enablePasswordDB: true +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dex + labels: + app: dex +spec: + replicas: 1 + selector: + matchLabels: + app: dex + template: + metadata: + labels: + app: dex + spec: + containers: + - name: dex + image: ghcr.io/dexidp/dex:v2.41.1 + command: ["dex", "serve", "/etc/dex/cfg/config.yaml"] + ports: + - containerPort: 5556 + readinessProbe: + httpGet: + path: /dex/healthz + port: 5556 + periodSeconds: 5 + volumeMounts: + - name: config + mountPath: /etc/dex/cfg + volumes: + - name: config + configMap: + name: dex-config +--- +apiVersion: v1 +kind: Service +metadata: + name: dex +spec: + selector: + app: dex + ports: + - port: 5556 + targetPort: 5556 diff --git a/ci/urlaubsverwaltung/settings.env b/ci/urlaubsverwaltung/settings.env new file mode 100644 index 0000000..c8e1558 --- /dev/null +++ b/ci/urlaubsverwaltung/settings.env @@ -0,0 +1,2 @@ +# Spring Boot startup (JVM + Liquibase migrations) can take a while. +INSTALL_TIMEOUT=600 diff --git a/ci/urlaubsverwaltung/test-values.yaml b/ci/urlaubsverwaltung/test-values.yaml new file mode 100644 index 0000000..25a6351 --- /dev/null +++ b/ci/urlaubsverwaltung/test-values.yaml @@ -0,0 +1,6 @@ +secrets: + databaseSecretRef: op://ci/uv-db + oidcSecretRef: op://ci/uv-oidc + +ingress: + domain: uv.ci.local diff --git a/ci/voucher-vault/fixtures.yaml b/ci/voucher-vault/fixtures.yaml new file mode 100644 index 0000000..f8d9bc2 --- /dev/null +++ b/ci/voucher-vault/fixtures.yaml @@ -0,0 +1,188 @@ +# Fixtures for the voucher-vault install-test: throwaway postgres + redis +# (Celery broker), a dex OIDC provider and the secrets the 1Password +# operator would otherwise materialize (release name: ci). +apiVersion: v1 +kind: Secret +metadata: + name: ci-voucher-vault-secret +stringData: + secretKey: voucher-vault-ci-django-secret-0123456789abcdef0123456789abcdef + redis-url: redis://:cipass@redis:6379/0 +--- +apiVersion: v1 +kind: Secret +metadata: + name: ci-voucher-vault-db-secret +stringData: + host: postgres + port: "5432" + user: ci + password: ci + database: vouchervault +--- +apiVersion: v1 +kind: Secret +metadata: + name: ci-voucher-vault-oidc-secret +stringData: + oidc-jwks-endpoint: http://dex.default.svc.cluster.local:5556/dex/keys + client-id: ci-client + client-secret: ci-client-secret + authorization-endpoint: http://dex.default.svc.cluster.local:5556/dex/auth + token-endpoint: http://dex.default.svc.cluster.local:5556/dex/token + user-endpoint: http://dex.default.svc.cluster.local:5556/dex/userinfo +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres + labels: + app: postgres +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: public.ecr.aws/docker/library/postgres:16-alpine + env: + - name: POSTGRES_USER + value: ci + - name: POSTGRES_PASSWORD + value: ci + - name: POSTGRES_DB + value: vouchervault + ports: + - containerPort: 5432 + readinessProbe: + exec: + command: ["pg_isready", "-U", "ci"] + periodSeconds: 5 + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + volumes: + - name: data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: postgres +spec: + selector: + app: postgres + ports: + - port: 5432 + targetPort: 5432 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + labels: + app: redis +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + containers: + - name: redis + image: public.ecr.aws/docker/library/redis:7-alpine + args: ["redis-server", "--requirepass", "cipass"] + ports: + - containerPort: 6379 + readinessProbe: + tcpSocket: + port: 6379 + periodSeconds: 5 +--- +apiVersion: v1 +kind: Service +metadata: + name: redis +spec: + selector: + app: redis + ports: + - port: 6379 + targetPort: 6379 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: dex-config +data: + config.yaml: | + issuer: http://dex.default.svc.cluster.local:5556/dex + storage: + type: memory + web: + http: 0.0.0.0:5556 + oauth2: + skipApprovalScreen: true + staticClients: + - id: ci-client + secret: ci-client-secret + name: CI Client + redirectURIs: + - https://vv.ci.local/oidc/callback + enablePasswordDB: true +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dex + labels: + app: dex +spec: + replicas: 1 + selector: + matchLabels: + app: dex + template: + metadata: + labels: + app: dex + spec: + containers: + - name: dex + image: ghcr.io/dexidp/dex:v2.41.1 + command: ["dex", "serve", "/etc/dex/cfg/config.yaml"] + ports: + - containerPort: 5556 + readinessProbe: + httpGet: + path: /dex/healthz + port: 5556 + periodSeconds: 5 + volumeMounts: + - name: config + mountPath: /etc/dex/cfg + volumes: + - name: config + configMap: + name: dex-config +--- +apiVersion: v1 +kind: Service +metadata: + name: dex +spec: + selector: + app: dex + ports: + - port: 5556 + targetPort: 5556 diff --git a/ci/voucher-vault/test-values.yaml b/ci/voucher-vault/test-values.yaml new file mode 100644 index 0000000..f15e77e --- /dev/null +++ b/ci/voucher-vault/test-values.yaml @@ -0,0 +1,8 @@ +secrets: + voucherVaultSecretRef: op://ci/voucher-vault + oidcSecretRef: op://ci/voucher-vault-oidc + databaseSecretRef: op://ci/voucher-vault-db + +ingress: + clusterIssuer: ci + domain: vv.ci.local diff --git a/ci/zipline/fixtures.yaml b/ci/zipline/fixtures.yaml new file mode 100644 index 0000000..1ec5ddb --- /dev/null +++ b/ci/zipline/fixtures.yaml @@ -0,0 +1,65 @@ +# Fixtures for the zipline install-test: throwaway postgres and the secrets +# the 1Password operator would otherwise materialize (release name: ci). +apiVersion: v1 +kind: Secret +metadata: + name: ci-zipline-db-secret +stringData: + connection-url: postgresql://ci:ci@postgres:5432/zipline +--- +apiVersion: v1 +kind: Secret +metadata: + name: ci-zipline-core-secret +stringData: + server-secret: zipline-ci-core-secret-0123456789abcdef0123456789abcdef0123456789 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres + labels: + app: postgres +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: public.ecr.aws/docker/library/postgres:16-alpine + env: + - name: POSTGRES_USER + value: ci + - name: POSTGRES_PASSWORD + value: ci + - name: POSTGRES_DB + value: zipline + ports: + - containerPort: 5432 + readinessProbe: + exec: + command: ["pg_isready", "-U", "ci"] + periodSeconds: 5 + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + volumes: + - name: data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: postgres +spec: + selector: + app: postgres + ports: + - port: 5432 + targetPort: 5432 diff --git a/ci/zipline/test-values.yaml b/ci/zipline/test-values.yaml new file mode 100644 index 0000000..08f06d7 --- /dev/null +++ b/ci/zipline/test-values.yaml @@ -0,0 +1,9 @@ +config: + storage: 1Gi + storageClass: local-path + domain: zipline.ci.local + # The OnePasswordItem CRs only need non-null item paths; the actual + # Kubernetes Secrets come from ci/zipline/fixtures.yaml. + secretRef: + db: op://ci/zipline-db + zipline: op://ci/zipline-core From ab5e5c02d190c38610f91dccfbfc69f451d9a6a8 Mon Sep 17 00:00:00 2001 From: jklein Date: Fri, 21 Aug 2026 23:13:23 +0200 Subject: [PATCH 2/3] TEMP: bump cyberchef to demonstrate the install-test job (will be reverted) --- cyberchef/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cyberchef/Chart.yaml b/cyberchef/Chart.yaml index bc4a3b7..55487ac 100644 --- a/cyberchef/Chart.yaml +++ b/cyberchef/Chart.yaml @@ -4,7 +4,7 @@ description: A Helm chart to deploy Cyberchef on Kubernetes type: application -version: 3.0.0+up10.22.1 +version: 3.0.1+up10.22.1 appVersion: "10.22.1" maintainers: From dfa8d3386c1112bf185f250a36147fe95bc162bc Mon Sep 17 00:00:00 2001 From: jklein Date: Fri, 21 Aug 2026 23:14:37 +0200 Subject: [PATCH 3/3] Revert "TEMP: bump cyberchef to demonstrate the install-test job (will be reverted)" This reverts commit ab5e5c02d190c38610f91dccfbfc69f451d9a6a8. --- cyberchef/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cyberchef/Chart.yaml b/cyberchef/Chart.yaml index 55487ac..bc4a3b7 100644 --- a/cyberchef/Chart.yaml +++ b/cyberchef/Chart.yaml @@ -4,7 +4,7 @@ description: A Helm chart to deploy Cyberchef on Kubernetes type: application -version: 3.0.1+up10.22.1 +version: 3.0.0+up10.22.1 appVersion: "10.22.1" maintainers: