Merge pull request #27 from jk-cluster/feat/install-test-pipeline
CI: install-test stage for changed charts in the PR pipeline (k3d)
This commit is contained in:
35 files changed
+1716
-1
No files matched your search
@@ -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
|
||||
@@ -36,7 +36,21 @@ Collection of some self-made helm-charts. Each top-level directory is a standalo
|
||||
- `publish-<chart>.yml` — one thin workflow per chart; triggers on pushes to `main` touching `<chart>/**` (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 <chart>` 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/<chart>/test-values.yaml` — values for the CI install (required for every installable chart)
|
||||
- `ci/<chart>/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/<chart>/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/<chart>/` 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 <chart> && k3d cluster delete t`.
|
||||
|
||||
## Creating a new chart
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# apache-tika needs no extra values for a CI install: no secrets, no ingress,
|
||||
# no persistence.
|
||||
{}
|
||||
@@ -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 <chart>-<release>-..., 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: "{}"
|
||||
@@ -0,0 +1,2 @@
|
||||
# First start unpacks the calibre library scaffold onto the volume.
|
||||
INSTALL_TIMEOUT=600
|
||||
@@ -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
|
||||
@@ -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/<chart>/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
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
# Charts excluded from the PR install-test, one per line: <chart> <reason>.
|
||||
# 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
|
||||
@@ -0,0 +1,3 @@
|
||||
# gotenberg needs no extra values for a CI install: no secrets, no ingress,
|
||||
# no persistence.
|
||||
{}
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
# Home Assistant's first boot installs/initializes integrations.
|
||||
INSTALL_TIMEOUT=600
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
# Synapse first start (SQLite schema setup) plus the config seed job.
|
||||
INSTALL_TIMEOUT=600
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
# Paperless runs database migrations and an index check on first start.
|
||||
INSTALL_TIMEOUT=600
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Executable
+112
@@ -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/<chart>/ and requires every workload to become Ready.
|
||||
#
|
||||
# Usage: ci/run-install-test.sh <chart-name>
|
||||
#
|
||||
# Layout (repo root):
|
||||
# ci/common/ fixtures applied for every chart (OnePasswordItem stub CRD)
|
||||
# ci/<chart>/test-values.yaml values for the CI install (required)
|
||||
# ci/<chart>/fixtures.yaml dummy secrets / throwaway infra / static PVs (optional)
|
||||
# ci/<chart>/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 <chart-name>}"
|
||||
NAMESPACE="default"
|
||||
RELEASE="ci"
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
CI_DIR="$ROOT/ci"
|
||||
|
||||
# Per-chart override via ci/<chart>/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."
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 <chart>-<release>-..., 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
|
||||
@@ -0,0 +1,2 @@
|
||||
# Spring Boot startup (JVM + Liquibase migrations) can take a while.
|
||||
INSTALL_TIMEOUT=600
|
||||
@@ -0,0 +1,6 @@
|
||||
secrets:
|
||||
databaseSecretRef: op://ci/uv-db
|
||||
oidcSecretRef: op://ci/uv-oidc
|
||||
|
||||
ingress:
|
||||
domain: uv.ci.local
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in new issue
Block a user