feat(tenant,harbor,bucket): wire per-tenant apps through the tenant Gateway

Tenants can now opt their own apps into Gateway API without touching
the publishing tenant. The mechanism mirrors the existing
_namespace.ingress contract exactly.

packages/apps/tenant/templates/namespace.yaml:

- Compute a new $gateway value: if the tenant sets .Values.gateway
  it becomes the tenant's own namespace, otherwise it inherits from
  the parent namespace (empty means no Gateway anywhere in the chain).
- Ship $gateway out through the cozystack-values Secret under
  _namespace.gateway and through a namespace label
  namespace.cozystack.io/gateway.

packages/apps/harbor and packages/system/bucket:

- Existing Ingress wrapped in '{{ if not $gateway }}' so it only
  renders when no Gateway is attached anywhere up the tenant chain.
- New templates/httproute.yaml render when $gateway is non-empty:
  apiVersion gateway.networking.k8s.io/v1 HTTPRoute, parentRef to
  the 'cozystack' Gateway in whichever namespace $gateway points
  at, hostname <release>.<host> (harbor) or <bucketName>.<host>
  (bucket), backendRef to the existing Service on its current port.

Per-tenant resolution: a tenant with .Values.gateway=true gets its
own Gateway in its own namespace, and harbor/bucket deployed inside
that tenant attach to that namespace's Gateway — no cross-namespace
references, no coupling to the publishing tenant.

Default remains ingress-nginx: tenants that do not opt in keep
rendering Ingress verbatim.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
This commit is contained in:
Aleksei Sviridkin 2026-04-23 19:55:59 +03:00
parent 2c5bf50f43
commit b719cf67e3
No known key found for this signature in database
GPG key ID: 7988329FDF395282
5 changed files with 54 additions and 0 deletions

View file

@ -0,0 +1,21 @@
{{- $gateway := .Values._namespace.gateway | default "" }}
{{- $host := .Values._namespace.host }}
{{- $harborHost := .Values.host | default (printf "%s.%s" .Release.Name $host) }}
{{- if $gateway }}
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: {{ .Release.Name }}
spec:
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: cozystack
namespace: {{ $gateway }}
hostnames:
- {{ $harborHost | quote }}
rules:
- backendRefs:
- name: {{ .Release.Name }}
port: 80
{{- end }}

View file

@ -1,8 +1,10 @@
{{- $ingress := .Values._namespace.ingress }}
{{- $gateway := .Values._namespace.gateway | default "" }}
{{- $host := .Values._namespace.host }}
{{- $harborHost := .Values.host | default (printf "%s.%s" .Release.Name $host) }}
{{- $solver := (index .Values._cluster "solver") | default "http01" }}
{{- $clusterIssuer := (index .Values._cluster "issuer-name") | default "letsencrypt-prod" }}
{{- if not $gateway }}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
@ -35,3 +37,4 @@ spec:
name: {{ .Release.Name }}
port:
number: 80
{{- end }}

View file

@ -29,6 +29,11 @@
{{- $ingress = $tenantName }}
{{- end }}
{{- $gateway := $parentNamespace.gateway | default "" }}
{{- if .Values.gateway }}
{{- $gateway = $tenantName }}
{{- end }}
{{- $monitoring := $parentNamespace.monitoring | default "" }}
{{- if .Values.monitoring }}
{{- $monitoring = $tenantName }}
@ -61,6 +66,7 @@ metadata:
{{/* Labels for network policies */}}
namespace.cozystack.io/etcd: {{ $etcd | quote }}
namespace.cozystack.io/ingress: {{ $ingress | quote }}
namespace.cozystack.io/gateway: {{ $gateway | quote }}
namespace.cozystack.io/monitoring: {{ $monitoring | quote }}
namespace.cozystack.io/seaweedfs: {{ $seaweedfs | quote }}
namespace.cozystack.io/host: {{ $computedHost | quote }}
@ -92,6 +98,7 @@ stringData:
_namespace:
etcd: {{ $etcd | quote }}
ingress: {{ $ingress | quote }}
gateway: {{ $gateway | quote }}
monitoring: {{ $monitoring | quote }}
seaweedfs: {{ $seaweedfs | quote }}
host: {{ $computedHost | quote }}

View file

@ -0,0 +1,20 @@
{{- $host := .Values._namespace.host }}
{{- $gateway := .Values._namespace.gateway | default "" }}
{{- if $gateway }}
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: {{ .Values.bucketName }}-ui
spec:
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: cozystack
namespace: {{ $gateway }}
hostnames:
- {{ printf "%s.%s" .Values.bucketName $host | quote }}
rules:
- backendRefs:
- name: {{ .Values.bucketName }}-ui
port: 8080
{{- end }}

View file

@ -1,7 +1,9 @@
{{- $host := .Values._namespace.host }}
{{- $ingress := .Values._namespace.ingress }}
{{- $gateway := .Values._namespace.gateway | default "" }}
{{- $solver := (index .Values._cluster "solver") | default "http01" }}
{{- $clusterIssuer := (index .Values._cluster "issuer-name") | default "letsencrypt-prod" }}
{{- if not $gateway }}
apiVersion: networking.k8s.io/v1
kind: Ingress
@ -32,3 +34,4 @@ spec:
name: {{ .Values.bucketName }}-ui
port:
number: 8080
{{- end }}