diff --git a/changelog.d/tenant-extra-hostnames.md b/changelog.d/tenant-extra-hostnames.md new file mode 100644 index 0000000..4c91013 --- /dev/null +++ b/changelog.d/tenant-extra-hostnames.md @@ -0,0 +1,50 @@ +### Toegevoegd — 2026-10-13 (tenant.extraHostnames) + +Een tenant kan nu op meer dan één hostnaam antwoorden. Nieuw optioneel veld +`tenant.extraHostnames` (lijst). De ApplicationSet `nextcloud-tenants`: + +- zet elke extra host achter de primaire host in `nextcloud.trustedDomains` + (en daarmee in `NEXTCLOUD_TRUSTED_DOMAINS`); +- rendert via `extraManifests` een tweede Ingress `nextcloud-extra-hosts`, + omdat chart 8.9.0 in zijn eigen Ingress alleen `nextcloud.host` als rule zet + (`ingress.hosts` uit de ApplicationSet leest die chart niet); +- geeft elke extra host een eigen TLS-blok en dus een eigen Certificate via + HTTP-01, zodat één host met nog niet kloppende DNS de andere niet tegenhoudt; +- neemt `ingress.enabled`, annotaties, `ingressClassName`, `path`, `pathType` + en poort via de `tpl` van de chart over uit dezelfde values als de + hoofd-Ingress: geen tweede kopie van de nginx-instellingen. + +**Bestaande tenants renderen byte-identiek.** Het blok wordt alleen geëmit als +de lijst gezet is. Gemeten: alle 89 tenantbestanden op main en op deze branch +gerenderd (Argo-goTemplate met sprig en `missingkey=default`, daarna +`helm template` met chart 8.9.0 en dezelfde valueFiles-volgorde), values en +manifests per tenant byte voor byte gelijk. + +`validate-values.sh` controleert het nieuwe veld (lijst, geldige DNS-namen en +geen IP-adres, geen dubbele, niet de primaire host, onder `commonground.nu` +alleen de eigen namen, unieke secretnaam ≤ 253, niet samen met +`ingress.enabled: false`). Ook `tenant.hostname` mag geen IP-adres meer zijn. +Over alle tenantbestanden heen weigert hij een host die twee tenants claimen +(primaire host, `extraHostnames`, `frontend.host`, `frontend.extraHosts`) en +een `tenant.name` die in twee bestanden staat, met fixtures in +`nextcloud-platform/tests/cases/tenant-extra-hostnames-*`. + +Bekende beperkingen: `trusted_domains` wordt alleen bij de eerste installatie +geschreven, en een verwijderde host blijft erin staan tot een +`occ config:system:delete trusted_domains `; `charts/tenant-httproute` neemt +nog één host. Zie +`docs/ADDING-TENANT.md`. + +**Apex-hosts (review f3).** Een extra host met twee labels (`gemeente.nl`) +wordt geweigerd zonder `tenant.extraHostnamesAllowApex: true`: HSTS met +includeSubDomains op een apex dwingt HTTPS af op elk subdomein van de klant. +Apex-hosts krijgen een eigen Ingress `nextcloud-extra-hosts-apex` met +`hsts-include-subdomains: "false"`; de hoofd-Ingress en de andere extra hosts +houden hun waarde. ingress-nginx leest die annotatie niet (HSTS komt uit de +controller-ConfigMap), dus live blijft includeSubDomains staan tot die +ConfigMap of de route verandert; zie `docs/ADDING-TENANT.md`. Nieuwe +render-check `nextcloud-platform/tests/render-extra-hosts.sh` in `verify.sh`. + +**Certificaten (review f4).** Extra hosts krijgen alleen Let's Encrypt: hun +Ingresses erven `cert-manager.io/cluster-issuer`. Een eigen of betaald cert per +extra host kan nog niet; `tenant.frontend.tls` is het bestaande patroon. diff --git a/docs/ADDING-TENANT.md b/docs/ADDING-TENANT.md index 779b16d..c73bff5 100644 --- a/docs/ADDING-TENANT.md +++ b/docs/ADDING-TENANT.md @@ -1,5 +1,5 @@ --- -last_reviewed: 2026-10-13 +last_reviewed: 2026-10-14 owner: info@conduction.nl --- @@ -249,6 +249,102 @@ soap-enabled build from `ConductionNL/nextcloud-images`. `validate-values.sh` has no top-level key allowlist, so an `image:` block passes validation as-is — the rules above are not machine-checked. +#### Extra hostnames on one tenant + +One Nextcloud can answer on more than one hostname, for example a workplace on +`cloud.` and a public site served by an app on `www.`. Set the +primary host as usual and list the others: + +```yaml +tenant: + hostname: cloud.example.nl + hostnameOverride: true + extraHostnames: + - www.example.nl +``` + +A bare apex such as `example.nl` is refused unless you also set +`extraHostnamesAllowApex: true`; read the HSTS point below first. + +What the ApplicationSet does with it (`argo/applicationsets/nextcloud-tenants.yaml`): + +| Effect | Detail | +|---|---| +| `trusted_domains` | every extra host is added to `nextcloud.trustedDomains`, after the primary host | +| routing | a second Ingress `nextcloud-extra-hosts`, because chart 8.9.0 puts only `nextcloud.host` in its own Ingress rules. Apex hosts (two labels) go into a third Ingress `nextcloud-extra-hosts-apex` | +| certificates | one TLS entry per host, secret `nextcloud---tls`, so one cert-manager Certificate per host via HTTP-01 (`letsencrypt-prod`). A host whose DNS is not ready does not block the others | +| annotations | taken from `ingress.annotations`, `ingress.className`, `ingress.path`, `ingress.pathType` and `service.port` through the chart's `tpl`, so they always match the main Ingress; with `ingress.enabled: false` no Ingress is rendered. The apex Ingress has one difference: `nginx.ingress.kubernetes.io/hsts-include-subdomains: "false"` | + +A tenant without `extraHostnames` renders byte-identical to before; the block is +only emitted when the list is set. + +Seven things to know: + +- **An apex host gets HSTS with includeSubDomains, so it needs an opt-in.** + Every host on our ingress-nginx answers with + `Strict-Transport-Security: max-age=31536000; includeSubDomains` (measured on + `dinkelland.commonground.nu`). On an apex like `example.nl` that means a + browser that visits it once forces HTTPS on every `*.example.nl` for a year, + including internal or http-only subdomains of the customer. Neither we nor the + customer can undo that in a browser that already has it. `validate-values.sh` + therefore refuses an extra host with two labels unless the tenant sets + `tenant.extraHostnamesAllowApex: true`. Set it only when the owner of the zone + has accepted that every subdomain must work over HTTPS. +- **The apex Ingress asks for no includeSubDomains, but ingress-nginx does not + read that today.** `nextcloud-extra-hosts-apex` carries + `hsts-include-subdomains: "false"`; the main Ingress and the other extra hosts + keep their value. ingress-nginx has no per-Ingress HSTS annotations: `hsts`, + `hsts-max-age` and `hsts-include-subdomains` are settings of the controller + ConfigMap. That is why the live max-age is 31536000 (the ConfigMap default) + and not the 15552000 in `values/common.yaml` (see `docs/GATEWAY-API.md`). The + ConfigMap `nginx-ingress-nginx-controller` is in no repo (Helm release `nginx`, + see `cluster-infra/scripts/tls-sigalgs-apply.sh`). So with the opt-in, the + apex host does get includeSubDomains until one of these changes: the + controller ConfigMap sets `hsts-include-subdomains: "false"` (for every host + on the cluster), the apex Ingress gets a `configuration-snippet` that sets the + header itself (only if the controller allows snippet annotations, not + verified), or the apex host moves to a Gateway API route whose `route.hsts` + leaves includeSubDomains out (`charts/tenant-httproute` does that per tenant, + but takes one host). The nginx sidecar in the pod also sends its own header, + `max-age=15768000; includeSubDomains; preload` (`values/common.yaml`, + `nginx.config.default`). ingress-nginx replaces it today, but a route that + does not set HSTS itself lets it through. +- **Extra hosts only get Let's Encrypt.** Both extra Ingresses inherit + `cert-manager.io/cluster-issuer: letsencrypt-prod` from `ingress.annotations`, + so cert-manager creates and owns the certificate of every extra host. A paid + or own certificate per extra host is not possible yet: a certificate you put + in `nextcloud---tls` yourself is overwritten by + cert-manager, and a CAA record that allows only the customer's own CA leaves + the Certificate on `Ready=False` while nginx serves its fake certificate. The + existing pattern for a customer certificate is `tenant.frontend.tls` + (`secretName` plus `issuer: none`, an Ingress without the cert-manager + annotation; see `tenant-tubbergen-prod.yaml`). `extraHostnames` has no such + option; a host that needs its own certificate cannot be an extra host for now. + +- **DNS is yours when the zone is not ours.** external-dns only manages + `commonground.nu`, `openwoo.app` and `opencatalogi.nl` + (`cluster-infra/external-dns/values.yaml`). For any other zone the zone owner + creates the records by hand, pointing at the ingress-nginx load balancer, and + keeps them **DNS only** (not proxied): Cloudflare caps request bodies at 100 MB + against our 16G, and HTTP-01 has to reach the Ingress. +- **A CAA record on the zone must allow `letsencrypt.org`**, or issuance fails + (see the Let's Encrypt point above). +- **`trusted_domains` is written at first install only.** The Nextcloud image + reads `NEXTCLOUD_TRUSTED_DOMAINS` when it installs; adding a host to a running + tenant later also needs `occ config:system:set trusted_domains --value=` + in the pod (same pattern as `cutover-tenant.sh`). Put the full list in the file + before the tenant is first created. Removing a host from the list works the + same way: Argo prunes the Ingress, but the host stays in `config.php` until + you run `occ config:system:delete trusted_domains `. In both cases run + `occ config:system:get trusted_domains` first to find the right ``: `set` + on an index that is already in use overwrites that entry. +- **Gateway API routes do not use this list.** `charts/tenant-httproute` takes one + host; a tenant with `gateway.nextcloud: true` gets a route for the primary host + only. The extra hosts stay on the nginx Ingress, and that Ingress follows + `ingress.enabled` like the chart's own: `ingress.enabled: false` would leave + them without any route, so `validate-values.sh` refuses `extraHostnames` + together with `ingress.enabled: false`. + Validate before pushing: ```bash diff --git a/docs/CHECKS-AND-BALANCES.md b/docs/CHECKS-AND-BALANCES.md index f4f43c2..6ec9ace 100644 --- a/docs/CHECKS-AND-BALANCES.md +++ b/docs/CHECKS-AND-BALANCES.md @@ -1,5 +1,5 @@ --- -last_reviewed: 2026-08-10 +last_reviewed: 2026-10-14 owner: info@conduction.nl --- @@ -35,6 +35,23 @@ Before merge, CI runs `scripts/verify.sh`, which wraps: and patterns across all tenant files - `nextcloud-platform/scripts/smoke-checks.sh` — Helm chart renders +`validate-values.sh` also checks the optional `tenant.extraHostnames` list (a +list, valid DNS names and no IP address, no duplicates, not the primary host, +under `commonground.nu` only this tenant's own names, a unique derived TLS +secret name Kubernetes accepts, not together with `ingress.enabled: false`, and +no apex host such as `gemeente.nl` without `tenant.extraHostnamesAllowApex: true`). +`tenant.hostname` is refused as an IP address too. It also checks across all +tenant files that no host (primary, `extraHostnames`, `frontend.host`, +`frontend.extraHosts`) is claimed by two tenants, also when it is run on a +single file, and that no two tenant files share a `tenant.name`. A valid list still gives a warning, because DNS and +HTTP-01 for those hosts are outside what CI can see. Fixtures for every branch +are in `nextcloud-platform/tests/cases/tenant-extra-hostnames-*`. +`nextcloud-platform/tests/render-extra-hosts.sh` (also in `verify.sh`) renders +two of those fixtures through the ApplicationSet and chart 8.9.0 and checks the +result: an apex host lands in its own Ingress with +`hsts-include-subdomains: "false"`, the main Ingress keeps its value, and both +extra Ingresses keep the cert-manager issuer. + This catches invalid values and Helm/rendering failures early. Both are dry-run: CI needs no cluster access and no secrets. The same gate runs locally via `./scripts/verify.sh` before pushing. diff --git a/docs/GATEWAY-API.md b/docs/GATEWAY-API.md index df57279..6c1b456 100644 --- a/docs/GATEWAY-API.md +++ b/docs/GATEWAY-API.md @@ -1,5 +1,5 @@ --- -last_reviewed: 2026-08-17 +last_reviewed: 2026-10-13 owner: info@conduction.nl --- @@ -47,6 +47,14 @@ een tenant kan zijn WOO-frontend eerder migreren dan zijn Nextcloud. Zonder `gateway:`-blok rendert er niets extra's, ook geen `enabled: false`. Een blok dat altijd meegaat zou alle 84 Applications tegelijk laten hersyncen. +`tenant.extraHostnames` (zie `docs/ADDING-TENANT.md`) gaat hier niet mee: de +route krijgt alleen de primaire host. Een tenant met extra hostnamen blijft voor +die hosts op de nginx-Ingress tot deze chart een lijst hostnames aanneemt, met +per host een listener op de Gateway. Die Ingress (`nextcloud-extra-hosts`) volgt +`ingress.enabled`, net als de hoofd-Ingress van de chart. `validate-values.sh` +weigert daarom `extraHostnames` samen met `ingress.enabled: false`: de extra +hosts zouden dan geen enkele route meer hebben. + ## Waarom dit nog niet schaalt De frontends hebben het makkelijk: `*.openwoo.app` en `*.accept.openwoo.app` @@ -89,8 +97,10 @@ De HTTPRoute komt náást de Ingress. Het verkeer verschuift **niet**: het DNS-record blijft naar ingress-nginx wijzen, want external-dns laat een bestaand record met rust zolang de Ingress bestaat. Gemeten 2026-08-17. -De cutover is dus het weghalen van de Ingress. Doe dat niet lichtvaardig: het -certificaat van deze host wordt via HTTP-01 over díé Ingress vernieuwd. Verdwijnt +De cutover is dus het weghalen van de Ingress. Heeft de tenant +`tenant.extraHostnames`, dan kan dat nog niet: `ingress.enabled: false` haalt ook +de Ingress van de extra hosts weg, en de validator weigert die combinatie. Doe +het ook verder niet lichtvaardig: het certificaat van deze host wordt via HTTP-01 over díé Ingress vernieuwd. Verdwijnt hij voordat cert-manager's `--enable-gateway-api` aanstaat (die vlag staat nu uit), dan breekt de vernieuwing stil en pas bij de eerstvolgende renewal. diff --git a/docs/index.md b/docs/index.md index ac873ec..9127dff 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,5 +1,5 @@ --- -last_reviewed: 2026-10-13 +last_reviewed: 2026-10-14 owner: info@conduction.nl --- @@ -82,6 +82,14 @@ Verwijderen doe je niet met een script uit deze repo, maar met een `.yaml` met precies één afwijking en een `.expect` met `PASS` of de vereiste deelstrings). `scripts/verify.sh` draait de suite mee. +`nextcloud-platform/tests/render-extra-hosts.sh` rendert twee +`extraHostnames`-fixtures zoals Argo het doet (values-blok van de +ApplicationSet, daarna `helm template` met chart 8.9.0) en controleert de +Ingresses: een apex-host krijgt een eigen Ingress met +`hsts-include-subdomains: "false"`. Ook die draait mee in `scripts/verify.sh`; +hij haalt de chart op, of gebruikt `NEXTCLOUD_CHART` als die naar een +uitgepakte chart wijst. + Dat de validator over de echte vloot groen is, zegt alleen dat de huidige bestanden schoon zijn — niet dat een check nog aanslaat op een fout die niemand meer maakt. Een nieuwe check hoort dus met minstens twee cases te komen. diff --git a/nextcloud-platform/argo/applicationsets/nextcloud-tenants.yaml b/nextcloud-platform/argo/applicationsets/nextcloud-tenants.yaml index 1ef952c..6660909 100644 --- a/nextcloud-platform/argo/applicationsets/nextcloud-tenants.yaml +++ b/nextcloud-platform/argo/applicationsets/nextcloud-tenants.yaml @@ -104,6 +104,16 @@ spec: {{- end -}} {{- $appapiEnabled := default false .tenant.features.appapi -}} {{- $phpMemoryLimit := default "2048M" .tenant.phpMemoryLimit -}} + {{- $extraHosts := default (list) .tenant.extraHostnames -}} + {{- $subHosts := list -}} + {{- $apexHosts := list -}} + {{- range $extraHosts -}} + {{- if eq (len (splitList "." .)) 2 -}} + {{- $apexHosts = append $apexHosts . -}} + {{- else -}} + {{- $subHosts = append $subHosts . -}} + {{- end -}} + {{- end -}} fullnameOverride: "nextcloud" commonLabels: nextcloud.platform/tenant: "{{ .tenant.name }}" @@ -113,6 +123,9 @@ spec: host: "{{ $host }}" trustedDomains: - "{{ $host }}" + {{- range $extraHosts }} + - "{{ . }}" + {{- end }} extraEnv: - name: PHP_MEMORY_LIMIT value: "{{ $phpMemoryLimit }}" @@ -197,7 +210,74 @@ spec: s3: bucket: "nextcloud" prefix: "{{ $name }}/" - + {{- if $extraHosts }} + # tenant.extraHostnames: extra hostnamen op DEZELFDE Nextcloud. + # De chart (8.9.0) zet in zijn eigen Ingress alleen nextcloud.host + # als rule, dus extra hosts krijgen hier een tweede Ingress. Eén + # TLS-blok per host, dus één Certificate per host: een host waarvan + # DNS nog niet klopt houdt de andere niet tegen. ingress.enabled, + # annotaties, ingressClassName, path, pathType en poort komen via + # de tpl van de chart uit dezelfde values als de hoofd-Ingress, + # op dezelfde manier als templates/ingress.yaml van de chart, dus + # geen tweede kopie van de nginx-instellingen. Alleen geëmit als + # de lijst gezet is: bestaande tenants renderen byte-identiek. + # + # Apex-hosts (twee labels, zoals gemeente.nl) krijgen een eigen + # Ingress nextcloud-extra-hosts-apex met hsts-include-subdomains + # "false" over de annotaties heen: includeSubDomains op een apex + # dwingt HTTPS af op elk subdomein van de klant. De validator + # eist daarvoor tenant.extraHostnamesAllowApex: true. Let op: de + # annotatie is een intentie, ingress-nginx leest hsts-* alleen uit + # zijn ConfigMap (zie docs/ADDING-TENANT.md). + extraManifests: + {{- range $group := list (dict "naam" "nextcloud-extra-hosts" "hosts" $subHosts "apex" false) (dict "naam" "nextcloud-extra-hosts-apex" "hosts" $apexHosts "apex" true) }} + {{- if $group.hosts }} + - | + {{`{{- if .Values.ingress.enabled }}`}} + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + name: {{ $group.naam }} + labels: + app.kubernetes.io/name: nextcloud + app.kubernetes.io/part-of: nextcloud-platform + nextcloud.platform/tenant: "{{ $name }}" + {{- if $group.apex }} + {{`{{- with merge (dict "nginx.ingress.kubernetes.io/hsts-include-subdomains" "false") (.Values.ingress.annotations | default dict) }}`}} + {{- else }} + {{`{{- with .Values.ingress.annotations }}`}} + {{- end }} + annotations: + {{`{{- toYaml . | nindent 4 }}`}} + {{`{{- end }}`}} + spec: + {{`{{- with .Values.ingress.className }}`}} + ingressClassName: {{`{{ . }}`}} + {{`{{- end }}`}} + tls: + {{- range $group.hosts }} + - secretName: "nextcloud-{{ $name }}-{{ replace "." "-" . }}-tls" + hosts: + - "{{ . }}" + {{- end }} + rules: + {{- range $group.hosts }} + - host: "{{ . }}" + http: + paths: + - path: {{`{{ .Values.ingress.path }}`}} + pathType: {{`{{ .Values.ingress.pathType }}`}} + backend: + service: + name: nextcloud + port: + number: {{`{{ .Values.service.port }}`}} + {{- end }} + {{`{{- end }}`}} + {{- end }} + {{- end }} + {{- end }} + # Our GitOps repo for values files # # Drie refs, drie stappen: diff --git a/nextcloud-platform/scripts/validate-values.sh b/nextcloud-platform/scripts/validate-values.sh index 8ac5216..857a076 100644 --- a/nextcloud-platform/scripts/validate-values.sh +++ b/nextcloud-platform/scripts/validate-values.sh @@ -11,12 +11,14 @@ # Usage: # ./scripts/validate-values.sh [tenant-file.yaml ...] # ./scripts/validate-values.sh # validates all tenants +# ./scripts/validate-values.sh --dump-host-claims # host-register (zie build_host_claims) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" -TENANT_DIR="${REPO_ROOT}/values/tenants" +# VALIDATE_TENANT_DIR: alleen voor tests/run-tests.sh (fixtures met een eigen vloot) +TENANT_DIR="${VALIDATE_TENANT_DIR:-${REPO_ROOT}/values/tenants}" # Colors for output RED='\033[0;31m' @@ -364,6 +366,25 @@ validate_chart_version_format() { return 0 } +# Hostnaam zoals Kubernetes hem in een Ingress-rule accepteert (RFC 1123): +# labels van kleine letters, cijfers en koppeltekens, minstens één punt, elk +# label max 63 tekens en het geheel max 253, en het laatste label niet alleen +# cijfers (dan is het een IP-adres, dat Kubernetes niet als Ingress-host neemt). +# Anders weigert de API-server de Ingress en faalt de sync van de Application. +# +# Usage: is_valid_hostname +is_valid_hostname() { + local host="$1" label + [[ "$host" =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)+$ ]] || return 1 + [ ${#host} -le 253 ] || return 1 + [[ "${host##*.}" =~ ^[0-9]+$ ]] && return 1 + local IFS=. + for label in $host; do + [ ${#label} -le 63 ] || return 1 + done + return 0 +} + # Validate hostname format validate_hostname() { local file="$1" @@ -376,8 +397,8 @@ validate_hostname() { fi # Basic hostname validation (RFC 1123) - if ! [[ "$hostname" =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ ]]; then - log_error "$file: Invalid hostname '$hostname'. Must be a valid DNS name." + if ! is_valid_hostname "$hostname"; then + log_error "$file: Invalid hostname '$hostname'. Must be a valid DNS name (at least one dot, labels max 63, total max 253, not an IP address)." return 1 fi @@ -438,6 +459,315 @@ validate_hostname_convention() { return 0 } +# Primaire host zoals de ApplicationSet (argo/applicationsets/nextcloud-tenants.yaml) +# hem bepaalt: tenant.hostname, en anders afgeleid uit tenant.name. Houd dit +# gelijk met dat template. Let op: het template kent 'demo' niet als suffix, +# zo'n tenant valt terug op tenant.environment (bv. x-demo.accept.commonground.nu). +# +# Usage: primary_host +primary_host() { + local name="$1" env="$2" hostname="$3" suffix="" org="$1" omgeving + + if [ -n "$hostname" ] && [ "$hostname" != "null" ]; then + echo "$hostname" + return 0 + fi + + if [[ "$name" =~ (accept|test|prod)$ ]]; then + suffix="${BASH_REMATCH[1]}" + fi + if [[ "$name" =~ ^(.*)-(accept|test|prod)$ ]]; then + org="${BASH_REMATCH[1]}" + fi + omgeving="${suffix:-$env}" + + if [ "$omgeving" = "prod" ]; then + echo "${org}.commonground.nu" + else + echo "${org}.${omgeving}.commonground.nu" + fi +} + +# ingress.enabled zoals de render hem ziet: dezelfde valueFiles-volgorde als de +# ApplicationSet (common, env, db, canary-overrides, tenantbestand), de laatste +# die hem zet wint. De inline values van de ApplicationSet zetten hem niet. +# +# Usage: effective_ingress_enabled +effective_ingress_enabled() { + local file="$1" values="${REPO_ROOT}/values" env db canary value f result="false" + env=$(yq eval '.tenant.environment // ""' "$file" 2>/dev/null) + db=$(yq eval '.tenant.dbType // "postgres"' "$file" 2>/dev/null) + canary=$(yq eval '.tenant.canary // ""' "$file" 2>/dev/null) + local layers=("${values}/common.yaml" "${values}/env/${env}.yaml" "${values}/db/${db}.yaml") + [ "$canary" = "true" ] && layers+=("${values}/canary-overrides.yaml") + layers+=("$file") + for f in "${layers[@]}"; do + [ -f "$f" ] || continue + value=$(yq eval '.ingress.enabled' "$f" 2>/dev/null) + [ "$value" != "null" ] && [ -n "$value" ] && result="$value" + done + echo "$result" +} + +# Validate tenant.extraHostnames (optioneel) +# +# Extra hostnamen op dezelfde Nextcloud. De ApplicationSet zet ze in +# trustedDomains en rendert er een tweede Ingress voor, met één TLS-secret +# "nextcloud---tls" per host. Gecontroleerd: +# lijstvorm, geldige DNS-naam (geen IP-adres), geen dubbele, niet gelijk aan de +# primaire host, onder commonground.nu alleen hosts van de eigen tenant, een +# unieke secretnaam die Kubernetes accepteert (DNS-subdomein, max 253), +# ingress.enabled: true, en een apex-host (twee labels, zoals gemeente.nl) +# alleen met tenant.extraHostnamesAllowApex: true. Botsingen met hosts van andere tenants vangt +# validate_host_claims; een dubbele tenant.name vangt dump_host_claims. +validate_extra_hostnames() { + local file="$1" + local kind name hostname primary primary_bron + kind=$(yq eval '.tenant.extraHostnames | type' "$file" 2>/dev/null) + + if [ "$kind" = "!!null" ] || [ -z "$kind" ]; then + return 0 + fi + + if [ "$kind" != "!!seq" ]; then + log_error "$file: tenant.extraHostnames moet een lijst zijn (got $kind)" + return 1 + fi + + name=$(yq eval '.tenant.name' "$file" 2>/dev/null) + hostname=$(yq eval '.tenant.hostname // ""' "$file" 2>/dev/null) + primary=$(primary_host "$name" "$(yq eval '.tenant.environment' "$file" 2>/dev/null)" "$hostname") + if [ -n "$hostname" ]; then + primary_bron="die staat al in tenant.hostname" + else + primary_bron="afgeleid uit tenant.name, zoals de ApplicationSet doet" + fi + + # Opt-in voor apex-hosts. HSTS met includeSubDomains op een apex dwingt + # HTTPS af op elk subdomein van de klant, ook interne en http-only, en dat + # is niet terug te draaien. Alleen met een expliciete true, en alleen als de + # zone-eigenaar dat accepteert (docs/ADDING-TENANT.md). + local allow_apex allow_apex_kind + allow_apex_kind=$(yq eval '.tenant.extraHostnamesAllowApex | type' "$file" 2>/dev/null) + allow_apex=$(yq eval '.tenant.extraHostnamesAllowApex' "$file" 2>/dev/null) + local has_error=0 + if [ "$allow_apex_kind" != "!!null" ] && [ "$allow_apex_kind" != "!!bool" ]; then + log_error "$file: tenant.extraHostnamesAllowApex moet true of false zijn (got $allow_apex_kind)" + has_error=1 + fi + + local host seen=" " secret seen_secrets=" " + while IFS= read -r host; do + if ! is_valid_hostname "$host"; then + log_error "$file: tenant.extraHostnames bevat ongeldige hostnaam '$host' (kleine letters, minstens één punt, labels max 63, totaal max 253, geen IP-adres)" + has_error=1 + continue + fi + if [[ "$host" != *.*.* ]] && [ "$allow_apex" != "true" ]; then + log_error "$file: tenant.extraHostnames bevat apex-host '$host'. Die krijgt HSTS met includeSubDomains, dus HTTPS-dwang op elk subdomein van de zone. Zet tenant.extraHostnamesAllowApex: true alleen als de zone-eigenaar dat accepteert (zie docs/ADDING-TENANT.md)" + has_error=1 + fi + if [ "$host" = "$primary" ]; then + log_error "$file: tenant.extraHostnames bevat de primaire host '$host' ($primary_bron)" + has_error=1 + fi + # commonground.nu is van het platform: een extra host daaronder moet + # volgens de naamconventie bij deze tenant horen. + if [ "$host" != "$primary" ] && [[ "$host" == commonground.nu || "$host" == *.commonground.nu ]] \ + && ! convention_hosts "$name" | grep -qxF -- "$host"; then + log_error "$file: tenant.extraHostnames bevat '$host' onder commonground.nu, maar die hoort niet bij tenant '$name' (toegestaan: $(convention_hosts "$name" | paste -sd ' '))" + has_error=1 + fi + if [[ "$seen" == *" $host "* ]]; then + log_error "$file: tenant.extraHostnames bevat '$host' dubbel" + has_error=1 + # De eerste keer zijn de overige checks voor deze host al gedaan + continue + fi + seen="${seen}${host} " + secret="nextcloud-${name}-${host//./-}-tls" + if [ ${#secret} -gt 253 ]; then + log_error "$file: TLS-secretnaam '$secret' voor '$host' is langer dan 253 tekens" + has_error=1 + fi + # Punten worden streepjes, dus a-b.nl en a.b.nl krijgen hetzelfde + # secret: één Certificate dat maar één van beide hosts dekt. + if [[ "$seen_secrets" == *" $secret "* ]]; then + log_error "$file: TLS-secretnaam '$secret' voor '$host' is gelijk aan die van een andere extra host" + has_error=1 + fi + seen_secrets="${seen_secrets}${secret} " + done < <(yq eval '.tenant.extraHostnames[]' "$file" 2>/dev/null) + + if [ $has_error -eq 0 ]; then + log_warning "$file: tenant.extraHostnames gezet; DNS en HTTP-01 per host moeten kloppen (zie docs/ADDING-TENANT.md)" + fi + + # De extra Ingress volgt ingress.enabled, net als die van de chart. Zonder + # Ingress hebben de extra hosts geen enkele route: charts/tenant-httproute + # neemt alleen de primaire host. + if [ "$(effective_ingress_enabled "$file")" != "true" ]; then + log_error "$file: tenant.extraHostnames vereist ingress.enabled: true. De extra hosts lopen via de nginx-Ingress zolang charts/tenant-httproute maar één host neemt (zie docs/GATEWAY-API.md)" + has_error=1 + fi + + return $has_error +} + +# Hosts onder commonground.nu die volgens de conventie bij deze tenant horen. +# Zelfde regel als validate_hostname_convention: prod krijgt .commonground.nu +# en de migratiehost .migrate.commonground.nu, de rest +# ..commonground.nu. +# +# Usage: convention_hosts +convention_hosts() { + local name="$1" org suffix + [[ "$name" =~ ^(.+)-(accept|test|demo|prod)$ ]] || return 0 + org="${BASH_REMATCH[1]}" + suffix="${BASH_REMATCH[2]}" + if [ "$suffix" = "prod" ]; then + echo "${org}.commonground.nu" + echo "${org}.migrate.commonground.nu" + else + echo "${org}.${suffix}.commonground.nu" + fi +} + +# Alle hosts die één tenantbestand claimt, als regels "": +# de primaire host (zoals de ApplicationSet hem bepaalt), tenant.extraHostnames +# en de WOO-frontend (tenant.frontend.host en tenant.frontend.extraHosts, alleen +# als ze expliciet gezet zijn; de afgeleide frontend-host staat in React-base). +# +# Usage: tenant_host_claims +tenant_host_claims() { + local file="$1" name env hostname + name=$(yq eval '.tenant.name // ""' "$file" 2>/dev/null) || return 0 + [ -z "$name" ] && return 0 + env=$(yq eval '.tenant.environment // ""' "$file" 2>/dev/null) + hostname=$(yq eval '.tenant.hostname // ""' "$file" 2>/dev/null) + printf '%s\tprimaire host\n' "$(primary_host "$name" "$env" "$hostname")" + local bron host + while read -r bron host; do + [ -n "$host" ] && printf '%s\t%s\n' "$host" "$bron" + done < <(yq eval '.tenant as $t | ( + [$t.extraHostnames // [] | select(tag == "!!seq") | .[] | to_string | "tenant.extraHostnames " + .] + + [$t.frontend.host // null | select(. != null) | to_string | "tenant.frontend.host " + .] + + [$t.frontend.extraHosts // [] | select(tag == "!!seq") | .[] | to_string | "tenant.frontend.extraHosts " + .] + ) | .[]' "$file" 2>/dev/null || true) +} + +# Absoluut pad met symlinks opgelost, zonder GNU realpath +abs_path() { + local dir + dir=$(cd "$(dirname "$1")" 2>/dev/null && pwd -P) || { echo "$1"; return 0; } + echo "${dir}/$(basename "$1")" +} + +# Host-register van values/tenants/, als regels "". +# Twee bestanden met dezelfde tenant.name zijn een fout: het register (en +# daarmee de botsingscheck) zou er stil één van overslaan. +dump_host_claims() { + local -A by_name=() + local file name has_error=0 host bron + while IFS= read -r -d '' file; do + name=$(yq eval '.tenant.name // ""' "$file" 2>/dev/null) || continue + [ -z "$name" ] && continue + if [ -n "${by_name[$name]:-}" ]; then + log_error "$file: tenant.name '$name' staat ook in ${by_name[$name]}" + has_error=$((has_error + 1)) + continue + fi + by_name["$name"]="$file" + done < <(find "$TENANT_DIR" -name "tenant-*.yaml" -print0 2>/dev/null | sort -z) + + for name in "${!by_name[@]}"; do + while IFS=$'\t' read -r host bron; do + [ -z "$host" ] && continue + printf '%s\t%s\t%s\n' "$host" "$name" "$bron" + done < <(tenant_host_claims "${by_name[$name]}") + done + return $has_error +} + +# Host-register over ALLE tenants: dat van values/tenants/, plus de bestanden op +# de commandoregel. Een bestand BUITEN values/tenants/ (een fixture, een +# gewijzigde kopie) vervangt het tenantbestand met dezelfde tenant.name, zodat +# het niet met zichzelf botst. Zo vergelijkt ook `validate-values.sh <één +# bestand>` met de rest van de vloot. Een bestand BINNEN values/tenants/ staat +# al in het register; een tweede daar met een bestaande naam is een fout. +# +# VALIDATE_HOST_CLAIMS_FILE: een register dat `validate-values.sh +# --dump-host-claims` eerder schreef (run-tests.sh bouwt het één keer). Ontbreekt +# het bestand, dan wordt het register gewoon opnieuw opgebouwd. +HOST_CLAIMS="" +build_host_claims() { + local cache="${VALIDATE_HOST_CLAIMS_FILE:-}" + if [ -n "$cache" ] && [ -f "$cache" ]; then + HOST_CLAIMS=$(cat "$cache") + else + # Draait in een subshell: tel de fouten hier bij (de meldingen zelf + # staan al op stderr) + local dups=0 + HOST_CLAIMS=$(dump_host_claims) || dups=$? + ERRORS=$((ERRORS + dups)) + fi + [ -n "$HOST_CLAIMS" ] && HOST_CLAIMS+=$'\n' + + local tenant_dir file path name host bron + tenant_dir=$(cd "$TENANT_DIR" 2>/dev/null && pwd -P) || tenant_dir="$TENANT_DIR" + local -A overlay=() + for file in "$@"; do + path=$(abs_path "$file") + [[ "$path" == "${tenant_dir}/"* ]] && continue + name=$(yq eval '.tenant.name // ""' "$file" 2>/dev/null) || continue + [ -z "$name" ] && continue + if [ -n "${overlay[$name]:-}" ] && [ "${overlay[$name]}" != "$path" ]; then + log_error "$file: tenant.name '$name' staat ook in ${overlay[$name]}" + continue + fi + overlay["$name"]="$path" + done + + for name in "${!overlay[@]}"; do + HOST_CLAIMS=$(printf '%s' "$HOST_CLAIMS" | awk -F'\t' -v n="$name" '$2 != n') + [ -n "$HOST_CLAIMS" ] && HOST_CLAIMS+=$'\n' + while IFS=$'\t' read -r host bron; do + [ -z "$host" ] && continue + HOST_CLAIMS+="${host}"$'\t'"${name}"$'\t'"${bron}"$'\n' + done < <(tenant_host_claims "${overlay[$name]}") + done +} + +# Geen host mag door twee tenants geclaimd worden. Een tweede Ingress voor +# dezelfde host (in een andere namespace) wordt door de admission-webhook van +# ingress-nginx geweigerd, of zonder webhook samengevoegd met die van de ander: +# dan gaat verkeer naar de verkeerde Nextcloud. Binnen één tenant mag een extra +# host ook niet de host van de eigen WOO-frontend zijn (een andere Ingress). +validate_host_claims() { + local file="$1" name + name=$(yq eval '.tenant.name // ""' "$file" 2>/dev/null) || return 0 + [ -z "$name" ] && return 0 + + local own has_error=0 host bron other + own=$(tenant_host_claims "$file") + while IFS=$'\t' read -r host bron; do + [ -z "$host" ] && continue + while IFS=$'\t' read -r other; do + [ -z "$other" ] && continue + log_error "$file: host '$host' ($bron) wordt al geclaimd door tenant ${other}" + has_error=1 + done < <(printf '%s' "$HOST_CLAIMS" | awk -F'\t' -v h="$host" -v n="$name" \ + '$1 == h && $2 != n { print "\x27" $2 "\x27 (" $3 ")" }') + if [ "$bron" = "tenant.extraHostnames" ] \ + && printf '%s\n' "$own" | grep -qxF -e "${host}"$'\t'"tenant.frontend.host" -e "${host}"$'\t'"tenant.frontend.extraHosts"; then + log_error "$file: tenant.extraHostnames bevat '$host', de host van de eigen WOO-frontend" + has_error=1 + fi + done <<<"$own" + + return $has_error +} + # Validate wave number validate_wave() { local file="$1" @@ -674,6 +1004,8 @@ validate_tenant_file() { validate_chart_version_format "$file" || ((file_errors++)) validate_hostname "$file" || ((file_errors++)) validate_hostname_convention "$file" || ((file_errors++)) + validate_extra_hostnames "$file" || ((file_errors++)) + validate_host_claims "$file" || ((file_errors++)) validate_wave "$file" || ((file_errors++)) validate_bucket "$file" || ((file_errors++)) validate_frontend_image "$file" || ((file_errors++)) @@ -691,6 +1023,13 @@ validate_tenant_file() { # Main main() { check_dependencies + + # --dump-host-claims: schrijf het host-register van values/tenants/ naar + # stdout, voor VALIDATE_HOST_CLAIMS_FILE (zie build_host_claims) + if [ "${1:-}" = "--dump-host-claims" ]; then + dump_host_claims + exit $? + fi local files=() @@ -708,6 +1047,9 @@ main() { exit 0 fi + # Hosts van alle tenants, voor validate_host_claims + build_host_claims "$@" + echo "==========================================" echo "Validating ${#files[@]} tenant file(s)" echo "==========================================" diff --git a/nextcloud-platform/tests/cases/tenant-dubbele-naam.expect b/nextcloud-platform/tests/cases/tenant-dubbele-naam.expect new file mode 100644 index 0000000..d3b4a95 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-dubbele-naam.expect @@ -0,0 +1 @@ +tenant.name 'zelfde-prod' staat ook in diff --git a/nextcloud-platform/tests/cases/tenant-dubbele-naam.fleet/tenant-zelfde-prod-a.yaml b/nextcloud-platform/tests/cases/tenant-dubbele-naam.fleet/tenant-zelfde-prod-a.yaml new file mode 100644 index 0000000..b5de65e --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-dubbele-naam.fleet/tenant-zelfde-prod-a.yaml @@ -0,0 +1,9 @@ +--- +tenant: + name: zelfde-prod + environment: prod + wave: "1" + dbType: postgres + apps: + enabled: + - openregister diff --git a/nextcloud-platform/tests/cases/tenant-dubbele-naam.fleet/tenant-zelfde-prod-b.yaml b/nextcloud-platform/tests/cases/tenant-dubbele-naam.fleet/tenant-zelfde-prod-b.yaml new file mode 100644 index 0000000..b5de65e --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-dubbele-naam.fleet/tenant-zelfde-prod-b.yaml @@ -0,0 +1,9 @@ +--- +tenant: + name: zelfde-prod + environment: prod + wave: "1" + dbType: postgres + apps: + enabled: + - openregister diff --git a/nextcloud-platform/tests/cases/tenant-dubbele-naam.yaml b/nextcloud-platform/tests/cases/tenant-dubbele-naam.yaml new file mode 100644 index 0000000..f0fa171 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-dubbele-naam.yaml @@ -0,0 +1,14 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - voorbeeld.nl + - www.voorbeeld.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-frontend.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-frontend.expect new file mode 100644 index 0000000..34f86a2 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-frontend.expect @@ -0,0 +1 @@ +host 'open.dinkelland.nl' (tenant.extraHostnames) wordt al geclaimd door tenant 'dinkelland-prod' (tenant.frontend.host) diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-frontend.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-frontend.yaml new file mode 100644 index 0000000..335da83 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-frontend.yaml @@ -0,0 +1,14 @@ +--- +# Leunt op values/tenants/tenant-dinkelland-prod.yaml: breekt als de hosts van dinkelland veranderen. +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - open.dinkelland.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-tenant.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-tenant.expect new file mode 100644 index 0000000..dad078f --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-tenant.expect @@ -0,0 +1 @@ +host 'dinkelland.commonground.nu' (tenant.extraHostnames) wordt al geclaimd door tenant 'dinkelland-prod' (primaire host) diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-tenant.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-tenant.yaml new file mode 100644 index 0000000..a41911d --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-tenant.yaml @@ -0,0 +1,14 @@ +--- +# Leunt op values/tenants/tenant-dinkelland-prod.yaml: breekt als de hosts van dinkelland veranderen. +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - dinkelland.commonground.nu diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-met-optin.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-met-optin.expect new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-met-optin.expect @@ -0,0 +1 @@ +PASS diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-met-optin.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-met-optin.yaml new file mode 100644 index 0000000..ebd1a3d --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-met-optin.yaml @@ -0,0 +1,16 @@ +--- +# Apex-host met de opt-in: geldig. De render-check (render-extra-hosts.sh) gebruikt dit bestand ook. +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnamesAllowApex: true + extraHostnames: + - voorbeeld.nl + - www.voorbeeld.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-false.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-false.expect new file mode 100644 index 0000000..5bc170f --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-false.expect @@ -0,0 +1 @@ +bevat apex-host 'voorbeeld.nl' diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-false.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-false.yaml new file mode 100644 index 0000000..1a83045 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-false.yaml @@ -0,0 +1,15 @@ +--- +# Opt-in expliciet uit: geweigerd zoals zonder opt-in. +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnamesAllowApex: false + extraHostnames: + - voorbeeld.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-tekst.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-tekst.expect new file mode 100644 index 0000000..6328ade --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-tekst.expect @@ -0,0 +1,2 @@ +tenant.extraHostnamesAllowApex moet true of false zijn +bevat apex-host 'voorbeeld.nl' diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-tekst.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-tekst.yaml new file mode 100644 index 0000000..ae22be6 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-tekst.yaml @@ -0,0 +1,15 @@ +--- +# De opt-in is een boolean, geen tekst. +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnamesAllowApex: "ja" + extraHostnames: + - voorbeeld.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-zonder-optin.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-zonder-optin.expect new file mode 100644 index 0000000..5bc170f --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-zonder-optin.expect @@ -0,0 +1 @@ +bevat apex-host 'voorbeeld.nl' diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-zonder-optin.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-zonder-optin.yaml new file mode 100644 index 0000000..793cf24 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-zonder-optin.yaml @@ -0,0 +1,15 @@ +--- +# Apex-host (twee labels) zonder tenant.extraHostnamesAllowApex: geweigerd. +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - voorbeeld.nl + - www.voorbeeld.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-berekende-primaire-host.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-berekende-primaire-host.expect new file mode 100644 index 0000000..9aa6da7 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-berekende-primaire-host.expect @@ -0,0 +1 @@ +bevat de primaire host 'voorbeeld.commonground.nu' diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-berekende-primaire-host.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-berekende-primaire-host.yaml new file mode 100644 index 0000000..b864e8a --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-berekende-primaire-host.yaml @@ -0,0 +1,11 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + apps: + enabled: + - openregister + extraHostnames: + - voorbeeld.commonground.nu diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-eigen.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-eigen.expect new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-eigen.expect @@ -0,0 +1 @@ +PASS diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-eigen.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-eigen.yaml new file mode 100644 index 0000000..cf7d17b --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-eigen.yaml @@ -0,0 +1,13 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - voorbeeld.commonground.nu diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-vreemd.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-vreemd.expect new file mode 100644 index 0000000..f906cd9 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-vreemd.expect @@ -0,0 +1 @@ +bevat 'nergens.commonground.nu' onder commonground.nu, maar die hoort niet bij tenant 'voorbeeld-prod' diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-vreemd.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-vreemd.yaml new file mode 100644 index 0000000..93ef9f7 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-vreemd.yaml @@ -0,0 +1,13 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - nergens.commonground.nu diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-dubbel.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-dubbel.expect new file mode 100644 index 0000000..170c639 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-dubbel.expect @@ -0,0 +1 @@ +bevat 'www.voorbeeld.nl' dubbel diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-dubbel.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-dubbel.yaml new file mode 100644 index 0000000..15150ec --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-dubbel.yaml @@ -0,0 +1,14 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - www.voorbeeld.nl + - www.voorbeeld.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-eigen-frontend.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-eigen-frontend.expect new file mode 100644 index 0000000..8a80912 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-eigen-frontend.expect @@ -0,0 +1 @@ +tenant.extraHostnames bevat 'open.voorbeeld.nl', de host van de eigen WOO-frontend diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-eigen-frontend.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-eigen-frontend.yaml new file mode 100644 index 0000000..b99f661 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-eigen-frontend.yaml @@ -0,0 +1,15 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + frontend: + host: open.voorbeeld.nl + extraHostnames: + - open.voorbeeld.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-geen-lijst.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-geen-lijst.expect new file mode 100644 index 0000000..e6660db --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-geen-lijst.expect @@ -0,0 +1 @@ +tenant.extraHostnames moet een lijst zijn diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-geen-lijst.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-geen-lijst.yaml new file mode 100644 index 0000000..6151b85 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-geen-lijst.yaml @@ -0,0 +1,12 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: www.voorbeeld.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-geldig.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-geldig.expect new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-geldig.expect @@ -0,0 +1 @@ +PASS diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-geldig.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-geldig.yaml new file mode 100644 index 0000000..f0ca77e --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-geldig.yaml @@ -0,0 +1,14 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - portaal.voorbeeld.nl + - www.voorbeeld.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-getal.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-getal.expect new file mode 100644 index 0000000..aceaeda --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-getal.expect @@ -0,0 +1 @@ +tenant.extraHostnames bevat ongeldige hostnaam '123' diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-getal.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-getal.yaml new file mode 100644 index 0000000..13163e7 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-getal.yaml @@ -0,0 +1,13 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - 123 diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-hoofdletter.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-hoofdletter.expect new file mode 100644 index 0000000..8d8debd --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-hoofdletter.expect @@ -0,0 +1 @@ +tenant.extraHostnames bevat ongeldige hostnaam 'Www.Voorbeeld.nl' diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-hoofdletter.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-hoofdletter.yaml new file mode 100644 index 0000000..2b4133e --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-hoofdletter.yaml @@ -0,0 +1,13 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - Www.Voorbeeld.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-ip-adres.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-ip-adres.expect new file mode 100644 index 0000000..02f0ce5 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-ip-adres.expect @@ -0,0 +1 @@ +tenant.extraHostnames bevat ongeldige hostnaam '81.24.6.82' diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-ip-adres.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-ip-adres.yaml new file mode 100644 index 0000000..e731742 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-ip-adres.yaml @@ -0,0 +1,13 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - 81.24.6.82 diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-label-te-lang.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-label-te-lang.expect new file mode 100644 index 0000000..abcb929 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-label-te-lang.expect @@ -0,0 +1 @@ +tenant.extraHostnames bevat ongeldige hostnaam 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.voorbeeld.nl' diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-label-te-lang.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-label-te-lang.yaml new file mode 100644 index 0000000..0f2cbc0 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-label-te-lang.yaml @@ -0,0 +1,13 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.voorbeeld.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-localhost.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-localhost.expect new file mode 100644 index 0000000..7ad51c5 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-localhost.expect @@ -0,0 +1 @@ +tenant.extraHostnames bevat ongeldige hostnaam 'localhost' diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-localhost.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-localhost.yaml new file mode 100644 index 0000000..45670d4 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-localhost.yaml @@ -0,0 +1,13 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - localhost diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-primaire-host.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-primaire-host.expect new file mode 100644 index 0000000..47f9a5f --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-primaire-host.expect @@ -0,0 +1 @@ +bevat de primaire host 'cloud.voorbeeld.nl' diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-primaire-host.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-primaire-host.yaml new file mode 100644 index 0000000..32be033 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-primaire-host.yaml @@ -0,0 +1,13 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - cloud.voorbeeld.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-zelfde-secret.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-zelfde-secret.expect new file mode 100644 index 0000000..656bd5e --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-zelfde-secret.expect @@ -0,0 +1 @@ +TLS-secretnaam 'nextcloud-voorbeeld-prod-a-b-voorbeeld-nl-tls' voor 'a.b.voorbeeld.nl' is gelijk aan die van een andere extra host diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-zelfde-secret.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-zelfde-secret.yaml new file mode 100644 index 0000000..f7dd437 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-zelfde-secret.yaml @@ -0,0 +1,14 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - a-b.voorbeeld.nl + - a.b.voorbeeld.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-zonder-ingress.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-zonder-ingress.expect new file mode 100644 index 0000000..09497b3 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-zonder-ingress.expect @@ -0,0 +1 @@ +tenant.extraHostnames vereist ingress.enabled: true diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-zonder-ingress.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-zonder-ingress.yaml new file mode 100644 index 0000000..c83cde7 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-zonder-ingress.yaml @@ -0,0 +1,15 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - www.voorbeeld.nl +ingress: + enabled: false diff --git a/nextcloud-platform/tests/cases/tenant-hostname-zonder-punt.expect b/nextcloud-platform/tests/cases/tenant-hostname-zonder-punt.expect new file mode 100644 index 0000000..fd7d8a8 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-hostname-zonder-punt.expect @@ -0,0 +1 @@ +Invalid hostname 'localhost' diff --git a/nextcloud-platform/tests/cases/tenant-hostname-zonder-punt.yaml b/nextcloud-platform/tests/cases/tenant-hostname-zonder-punt.yaml new file mode 100644 index 0000000..b4f76d2 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-hostname-zonder-punt.yaml @@ -0,0 +1,11 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: localhost + hostnameOverride: true + apps: + enabled: + - openregister diff --git a/nextcloud-platform/tests/render-extra-hosts.sh b/nextcloud-platform/tests/render-extra-hosts.sh new file mode 100755 index 0000000..06e6051 --- /dev/null +++ b/nextcloud-platform/tests/render-extra-hosts.sh @@ -0,0 +1,162 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: EUPL-1.2 +# role: tool +# +# nextcloud-platform/tests/render-extra-hosts.sh — render-check voor +# tenant.extraHostnames. +# +# De fixture-suite (run-tests.sh) toetst de validator; deze check toetst wat de +# ApplicationSet er daarna van maakt. Hij rendert twee fixtures zoals Argo het +# doet: eerst het inline values-blok van de ApplicationSet met het tenantbestand +# als parameters, daarna `helm template` met chart 8.9.0 en dezelfde +# valueFiles-volgorde. Daarna moet gelden: +# +# tenant-extra-hostnames-apex-met-optin (voorbeeld.nl + www.voorbeeld.nl) +# - Ingress nextcloud-extra-hosts-apex bestaat, met alleen voorbeeld.nl en +# hsts-include-subdomains "false"; +# - Ingress nextcloud-extra-hosts heeft alleen www.voorbeeld.nl en dezelfde +# hsts-include-subdomains als de hoofd-Ingress; +# - de hoofd-Ingress houdt de waarde uit values/common.yaml (de apex-merge +# mag de gedeelde values niet muteren); +# - beide extra Ingresses houden de cert-manager-issuer van de hoofd-Ingress. +# tenant-extra-hostnames-geldig (geen apex) +# - geen Ingress nextcloud-extra-hosts-apex. +# +# Argo rendert het values-blok met Go text/template en sprig. Helm doet dat +# ook, dus deze check gebruikt helm als template-engine: het blok gaat als +# template in een wegwerp-chart, met het tenantbestand als values. Twee +# verschillen met Argo, en hoe die gedekt zijn: +# - Argo draait met missingkey=default, helm met missingkey=zero. Daardoor +# breekt `.tenant.features.appapi` in helm als `features` ontbreekt, en in +# Argo niet. De check zet daarom elke tussenliggende map die het blok leest +# (afgeleid uit de `.tenant.a.b`-paden in het blok zelf) op `{}` als hij +# ontbreekt; een ontbrekende sleutel in een lege map geeft in beide engines +# dezelfde lege waarde. +# - Helm heeft een paar functies meer (include, tpl, required); het blok +# gebruikt die niet. +# Gemeten 2026-10-14: voor alle 89 tenantbestanden en de extraHostnames-fixtures +# gaf deze engine hetzelfde values-blok als een Go-emulatie van Argo, byte voor +# byte op de afsluitende lege regel na (text/template + sprig, missingkey=default). +# +# Writes: read-only (schrijft alleen in een eigen mktemp-map) +# Idempotent: ja +# Requires: bash, helm, yq; netwerk naar https://nextcloud.github.io/helm/, +# tenzij NEXTCLOUD_CHART naar een uitgepakte chart wijst +# +# Usage: +# nextcloud-platform/tests/render-extra-hosts.sh +# NEXTCLOUD_CHART=/pad/naar/nextcloud nextcloud-platform/tests/render-extra-hosts.sh + +set -euo pipefail +shopt -s inherit_errexit + +HERE="$(cd "$(dirname "$0")" && pwd)" +PLATFORM="$(cd "${HERE}/.." && pwd)" +VALUES="${PLATFORM}/values" +APPSET="${PLATFORM}/argo/applicationsets/nextcloud-tenants.yaml" +CASES="${HERE}/cases" +CHART_VERSION="8.9.0" +HSTS_SUB="nginx.ingress.kubernetes.io/hsts-include-subdomains" +ISSUER="cert-manager.io/cluster-issuer" + +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "$TMP_DIR"' EXIT + +fails=0 +fail() { echo "FAIL: $*" >&2; fails=$((fails + 1)); } + +chart="${NEXTCLOUD_CHART:-}" +if [[ -z "$chart" ]]; then + helm pull nextcloud --repo https://nextcloud.github.io/helm/ \ + --version "$CHART_VERSION" --untar --untardir "$TMP_DIR/chart" >/dev/null + chart="$TMP_DIR/chart/nextcloud" +fi + +# Wegwerp-chart met het values-blok van de ApplicationSet als enige template. +engine="$TMP_DIR/engine" +mkdir -p "$engine/templates" +printf 'apiVersion: v2\nname: appset-values\nversion: 0.0.0\n' >"$engine/Chart.yaml" +{ + echo '{{- with .Values }}' + yq -r '.spec.template.spec.sources[0].helm.values' "$APPSET" + echo '{{- end }}' +} >"$engine/templates/values.yaml" + +# Tussenliggende maps die het blok leest, zoals `.tenant.apps.versions` uit +# `.tenant.apps.versions.opencatalogi`. Elk wordt `{}` als hij ontbreekt. +fill_expr="$(grep -oE '\.tenant(\.[A-Za-z0-9_]+){2,}' "$engine/templates/values.yaml" \ + | sed -E 's/\.[A-Za-z0-9_]+$//' \ + | awk -F. '{p=".tenant"; for (i=3; i<=NF; i++) {p=p"."$i; print p}}' \ + | sort -u | awk '{printf "%s%s |= (. // {})", (NR>1 ? " | " : ""), $0}')" + +# Usage: appset_values +appset_values() { + yq "${fill_expr:-.}" "$1" >"$2.params.yaml" + helm template x "$engine" -f "$2.params.yaml" --show-only templates/values.yaml \ + | sed '1,2d' >"$2" +} + +# Usage: render → pad naar de gerenderde manifests +render() { + local name="$1" tenant="${CASES}/$1.yaml" out="$TMP_DIR/$1" + local env db + appset_values "$tenant" "$out.values.yaml" + env="$(yq -r '.tenant.environment' "$tenant")" + db="$(yq -r '.tenant.dbType // "postgres"' "$tenant")" + helm template nextcloud "$chart" \ + -f "${VALUES}/common.yaml" -f "${VALUES}/env/${env}.yaml" -f "${VALUES}/db/${db}.yaml" \ + -f "$tenant" -f "$out.values.yaml" \ + --namespace "$(yq -r '.tenant.name' "$tenant")" >"$out.manifests.yaml" + echo "$out.manifests.yaml" +} + +# Usage: ingress +ingress() { + # yq geeft per niet-passend document een lege regel; die weg. + yq -r "select(.kind == \"Ingress\" and .metadata.name == \"$2\") | $3" "$1" | sed '/^$/d' +} + +expected_main="$(yq -r ".ingress.annotations.\"${HSTS_SUB}\"" "${VALUES}/common.yaml")" +expected_issuer="$(yq -r ".ingress.annotations.\"${ISSUER}\"" "${VALUES}/common.yaml")" + +# Alleen voor het meten van de engine tegen Argo: print het values-blok van +# één tenantbestand en stop. +if [[ "${1:-}" == "--values" ]]; then + appset_values "$2" "$TMP_DIR/one" + cat "$TMP_DIR/one" + exit 0 +fi + +m="$(render tenant-extra-hostnames-apex-met-optin)" + +got="$(ingress "$m" nextcloud-extra-hosts-apex '[.spec.rules[].host] | join(",")')" +[[ "$got" == "voorbeeld.nl" ]] || fail "apex-Ingress hosts: verwacht 'voorbeeld.nl', kreeg '$got'" +got="$(ingress "$m" nextcloud-extra-hosts-apex "[.spec.tls[].hosts[]] | join(\",\")")" +[[ "$got" == "voorbeeld.nl" ]] || fail "apex-Ingress tls-hosts: verwacht 'voorbeeld.nl', kreeg '$got'" +got="$(ingress "$m" nextcloud-extra-hosts-apex ".metadata.annotations.\"${HSTS_SUB}\"")" +[[ "$got" == "false" ]] || fail "apex-Ingress ${HSTS_SUB}: verwacht 'false', kreeg '$got'" +got="$(ingress "$m" nextcloud-extra-hosts-apex ".metadata.annotations.\"${ISSUER}\"")" +[[ "$got" == "$expected_issuer" ]] || fail "apex-Ingress ${ISSUER}: verwacht '$expected_issuer', kreeg '$got'" + +got="$(ingress "$m" nextcloud-extra-hosts '[.spec.rules[].host] | join(",")')" +[[ "$got" == "www.voorbeeld.nl" ]] || fail "extra Ingress hosts: verwacht 'www.voorbeeld.nl', kreeg '$got'" +got="$(ingress "$m" nextcloud-extra-hosts ".metadata.annotations.\"${HSTS_SUB}\"")" +[[ "$got" == "$expected_main" ]] || fail "extra Ingress ${HSTS_SUB}: verwacht '$expected_main', kreeg '$got'" +got="$(ingress "$m" nextcloud-extra-hosts ".metadata.annotations.\"${ISSUER}\"")" +[[ "$got" == "$expected_issuer" ]] || fail "extra Ingress ${ISSUER}: verwacht '$expected_issuer', kreeg '$got'" + +got="$(ingress "$m" nextcloud ".metadata.annotations.\"${HSTS_SUB}\"")" +[[ "$got" == "$expected_main" ]] || fail "hoofd-Ingress ${HSTS_SUB}: verwacht '$expected_main', kreeg '$got'" + +m="$(render tenant-extra-hostnames-geldig)" +got="$(ingress "$m" nextcloud-extra-hosts-apex '.metadata.name')" +[[ -z "$got" ]] || fail "geen apex-host, toch een Ingress nextcloud-extra-hosts-apex" +got="$(ingress "$m" nextcloud-extra-hosts '[.spec.rules[].host] | join(",")')" +[[ "$got" == "portaal.voorbeeld.nl,www.voorbeeld.nl" ]] \ + || fail "extra Ingress hosts (geldig): verwacht 'portaal.voorbeeld.nl,www.voorbeeld.nl', kreeg '$got'" + +if [[ $fails -gt 0 ]]; then + echo "render-extra-hosts: $fails fout(en)" >&2 + exit 1 +fi +echo "render-extra-hosts OK" diff --git a/nextcloud-platform/tests/run-tests.sh b/nextcloud-platform/tests/run-tests.sh index e2f0177..219fead 100755 --- a/nextcloud-platform/tests/run-tests.sh +++ b/nextcloud-platform/tests/run-tests.sh @@ -21,6 +21,16 @@ # Een fixture die per ongeluk op een ANDERE check faalt dan bedoeld wordt zo # ook gevangen: de verwachte deelstring komt dan niet voor. # +# De validator vergelijkt hosts met alle tenantbestanden in values/tenants/, +# ook bij één bestand. Fixtures die een botsing met een andere tenant toetsen +# (tenant-extra-hostnames-andere-*) leunen daarom op tenant-dinkelland-prod. +# Dat register bouwt deze runner één keer (VALIDATE_HOST_CLAIMS_FILE), niet per +# case. +# +# .fleet/ optioneel: een eigen map met tenantbestanden die voor deze +# case values/tenants/ vervangt (VALIDATE_TENANT_DIR), voor +# checks over de hele vloot, zoals een dubbele tenant.name. +# # Writes: read-only (schrijft alleen in een eigen mktemp-map) # Idempotent: ja # Requires: bash, yq (via validate-values.sh) @@ -62,7 +72,11 @@ run_case() { # validate-values.sh geeft non-zero bij fouten; dat is hier data, geen crash. set +e - output="$(bash "$VALIDATOR" "$yaml" 2>&1)" + if [[ -d "${CASES_DIR}/${name}.fleet" ]]; then + output="$(VALIDATE_TENANT_DIR="${CASES_DIR}/${name}.fleet" bash "$VALIDATOR" "$yaml" 2>&1)" + else + output="$(VALIDATE_HOST_CLAIMS_FILE="$CLAIMS_FILE" bash "$VALIDATOR" "$yaml" 2>&1)" + fi rc=$? set -e # ANSI-kleuren eruit, anders matchen de deelstrings niet @@ -115,6 +129,16 @@ main() { exit 1 fi + # Host-register van values/tenants/ één keer opbouwen in plaats van per + # case. Globaal: de EXIT-trap draait buiten deze functie. + TMP_DIR="$(mktemp -d)" + trap 'rm -rf "$TMP_DIR"' EXIT + CLAIMS_FILE="${TMP_DIR}/host-claims.tsv" + if ! bash "$VALIDATOR" --dump-host-claims >"$CLAIMS_FILE"; then + echo "error: host-register van values/tenants/ niet op te bouwen" >&2 + exit 1 + fi + local yaml for yaml in "${CASES_DIR}"/*.yaml; do [[ -e "$yaml" ]] || continue diff --git a/scripts/verify.sh b/scripts/verify.sh index 77b9d7f..f5c02e7 100755 --- a/scripts/verify.sh +++ b/scripts/verify.sh @@ -12,7 +12,9 @@ # # Writes: read-only # Idempotent: yes -# Requires: bash, yq, yamllint (via validate-values), helm +# Requires: bash, yq, yamllint (via validate-values), helm; netwerk naar +# nextcloud.github.io/helm voor render-extra-hosts.sh, of +# NEXTCLOUD_CHART naar een uitgepakte chart 8.9.0 # # Usage: # ./scripts/verify.sh @@ -137,6 +139,12 @@ echo "doc-assertion OK (${script_count} scripts gedekt in de index)" nextcloud-platform/tests/run-tests.sh >/dev/null echo "validator-tests OK" +# Render-check voor tenant.extraHostnames: wat de ApplicationSet van een geldige +# lijst maakt. Een apex-host krijgt een eigen Ingress met +# hsts-include-subdomains "false"; de hoofd-Ingress houdt zijn waarde. De +# validator-tests hierboven zien de render niet. +nextcloud-platform/tests/render-extra-hosts.sh >/dev/null +echo "render-extra-hosts OK" # Fixture-tests voor de Conduction-apps install-hook in values/common.yaml. Die # hook draait in elke tenant-pod; een fout erin merk je pas bij de herstart van # de hele vloot. De suite draait hem lokaal met stubs en eist pariteit met de