From 3ce0ed37af72e0b0b684a4efbbcda35818da5658 Mon Sep 17 00:00:00 2001 From: Olivier Vernin Date: Sun, 17 May 2026 09:10:40 +0200 Subject: [PATCH 1/6] feat: Enhance udash helm chart * By default use cnpg operator to install the postgresql database * Update udash front configuration which requires version > 0.17 * Fix udash server configuration template Signed-off-by: Olivier Vernin --- charts/udash/Chart.yaml | 3 +- charts/udash/README.md | 44 ++++++++++++++++--- charts/udash/README.md.gotmpl | 35 +++++++++++++-- charts/udash/templates/_helpers.tpl | 7 +++ charts/udash/templates/cnpg-cluster.yaml | 16 +++++++ charts/udash/templates/configmap-front.yaml | 14 ++---- charts/udash/templates/configmap-server.yaml | 9 +++- charts/udash/templates/deployment-server.yaml | 13 ++++++ charts/udash/templates/secrets-database.yaml | 3 +- charts/udash/values.yaml | 17 ++++++- 10 files changed, 135 insertions(+), 26 deletions(-) create mode 100644 charts/udash/templates/cnpg-cluster.yaml diff --git a/charts/udash/Chart.yaml b/charts/udash/Chart.yaml index e85e174..8c12e5a 100644 --- a/charts/udash/Chart.yaml +++ b/charts/udash/Chart.yaml @@ -4,4 +4,5 @@ description: Udash, the Updatecli DASHboard icon: https://www.updatecli.io/images/updatecli_transparent.png name: udash type: application -version: 0.25.0 +version: 0.26.0 + diff --git a/charts/udash/README.md b/charts/udash/README.md index f5132b2..038c46a 100644 --- a/charts/udash/README.md +++ b/charts/udash/README.md @@ -1,6 +1,6 @@ # udash -![Version: 0.25.0](https://img.shields.io/badge/Version-0.25.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.1.0](https://img.shields.io/badge/AppVersion-0.1.0-informational?style=flat-square) +![Version: 0.26.0](https://img.shields.io/badge/Version-0.26.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.1.0](https://img.shields.io/badge/AppVersion-0.1.0-informational?style=flat-square) Udash, the Updatecli DASHboard @@ -23,7 +23,24 @@ An optional **Ingress** routes: - Kubernetes 1.19+ - Helm 3.x -- A running **PostgreSQL** instance accessible from the cluster +- **[CloudNative-PG operator](https://cloudnative-pg.io/)** (when `cnpg.enabled: true`, which is the default) + +Install the CNPG operator before installing this chart, and use `--wait` to ensure the operator +pod is fully ready (including its admission webhook) before proceeding: + +```console +helm repo add cnpg https://cloudnative-pg.github.io/charts +helm upgrade --install cnpg \ + --namespace cnpg-system \ + --create-namespace \ + cnpg/cloudnative-pg \ + --wait +``` + +Without `--wait`, the operator pod may not be ready when the chart creates the `Cluster` resource, +causing: `failed calling webhook "mcluster.cnpg.io": no endpoints available for service "cnpg-webhook-service"`. + +If you prefer to bring your own PostgreSQL instance, set `cnpg.enabled=false` and supply the connection URI via `secrets.database.stringdata.uri`. ## Get Repository Info @@ -40,12 +57,21 @@ helm install udash updatecli/udash ## Installing the Chart -### Read-only mode (default) +### Default (CloudNative-PG managed PostgreSQL) -In read-only mode the server runs without authentication (dry-run). No OAuth credentials are required. +By default the chart provisions a CNPG `Cluster` and injects credentials automatically. Just install the CNPG operator first (see Prerequisites), then: + +```console +helm install udash updatecli/udash +``` + +### Read-only mode with external PostgreSQL + +Disable CNPG and supply your own connection URI: ```console helm install udash updatecli/udash \ + --set cnpg.enabled=false \ --set secrets.database.stringdata.uri="postgres://user:pass@postgres:5432/udash?sslmode=disable" ``` @@ -56,12 +82,13 @@ Set `readonly: false` and supply your OAuth2 provider details: ```console helm install udash updatecli/udash \ --set readonly=false \ - --set secrets.database.stringdata.uri="postgres://user:pass@postgres:5432/udash?sslmode=disable" \ --set secrets.auth.stringdata.clientid="my-client-id" \ --set secrets.auth.stringdata.audience="https://udash.example/api" \ --set secrets.auth.stringdata.issuer="https://auth.example" ``` +When using an external PostgreSQL in full mode, also set `cnpg.enabled=false` and `secrets.database.stringdata.uri`. + ### With Ingress ```console @@ -88,6 +115,11 @@ helm uninstall udash | autoscaling.maxReplicas | int | `100` | Maximum number of replicas. | | autoscaling.minReplicas | int | `1` | Minimum number of replicas. | | autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage for autoscaling. | +| cnpg.database | string | `"udash"` | PostgreSQL database name created during cluster bootstrap. | +| cnpg.enabled | bool | `true` | Enable CloudNative-PG managed PostgreSQL cluster. When true, the chart provisions a CNPG Cluster and injects credentials automatically. Requires the CNPG operator to be installed separately. | +| cnpg.instances | int | `1` | Number of PostgreSQL instances in the CNPG cluster. | +| cnpg.owner | string | `"udash"` | PostgreSQL role/owner created during cluster bootstrap. | +| cnpg.storage.size | string | `"1Gi"` | Storage size for each PostgreSQL instance. | | configMap.annotations | object | `{}` | Annotations to add to the ConfigMap. | | configMap.name | string | `""` | The name of the ConfigMap used to store server/front configuration. If not set, a name is generated using the fullname template. | | fullnameOverride | string | `""` | Full override for the chart name used in resource names. | @@ -116,7 +148,7 @@ helm uninstall udash | secrets.auth.stringdata.audience | string | `"https://udash.example/api"` | OAuth2 audience. | | secrets.auth.stringdata.clientid | string | `"xxx.example"` | OAuth2 client ID. | | secrets.auth.stringdata.issuer | string | `"https://oauth.example"` | OAuth2 issuer URL. | -| secrets.auth.stringdata.mode | string | `"oauth"` | Authentication mode. Supported values: `oauth`. | +| secrets.auth.stringdata.mode | string | `"none"` | Authentication mode. Supported values: `oauth`, `none`. | | secrets.database.annotations | object | `{}` | Annotations to add to the database Secret. | | secrets.database.stringdata.uri | string | `"postgres://postgres:5432/udash?sslmode=disable"` | PostgreSQL connection URI used by the udash-server. | | secrets.name | string | `""` | The name of the Secret used to store credentials. If not set, a name is generated using the fullname template. | diff --git a/charts/udash/README.md.gotmpl b/charts/udash/README.md.gotmpl index 97726d9..e4559e5 100644 --- a/charts/udash/README.md.gotmpl +++ b/charts/udash/README.md.gotmpl @@ -25,7 +25,24 @@ An optional **Ingress** routes: - Kubernetes 1.19+ - Helm 3.x -- A running **PostgreSQL** instance accessible from the cluster +- **[CloudNative-PG operator](https://cloudnative-pg.io/)** (when `cnpg.enabled: true`, which is the default) + +Install the CNPG operator before installing this chart, and use `--wait` to ensure the operator +pod is fully ready (including its admission webhook) before proceeding: + +```console +helm repo add cnpg https://cloudnative-pg.github.io/charts +helm upgrade --install cnpg \ + --namespace cnpg-system \ + --create-namespace \ + cnpg/cloudnative-pg \ + --wait +``` + +Without `--wait`, the operator pod may not be ready when the chart creates the `Cluster` resource, +causing: `failed calling webhook "mcluster.cnpg.io": no endpoints available for service "cnpg-webhook-service"`. + +If you prefer to bring your own PostgreSQL instance, set `cnpg.enabled=false` and supply the connection URI via `secrets.database.stringdata.uri`. ## Get Repository Info @@ -42,12 +59,21 @@ helm install udash updatecli/udash ## Installing the Chart -### Read-only mode (default) +### Default (CloudNative-PG managed PostgreSQL) -In read-only mode the server runs without authentication (dry-run). No OAuth credentials are required. +By default the chart provisions a CNPG `Cluster` and injects credentials automatically. Just install the CNPG operator first (see Prerequisites), then: + +```console +helm install udash updatecli/udash +``` + +### Read-only mode with external PostgreSQL + +Disable CNPG and supply your own connection URI: ```console helm install udash updatecli/udash \ + --set cnpg.enabled=false \ --set secrets.database.stringdata.uri="postgres://user:pass@postgres:5432/udash?sslmode=disable" ``` @@ -58,12 +84,13 @@ Set `readonly: false` and supply your OAuth2 provider details: ```console helm install udash updatecli/udash \ --set readonly=false \ - --set secrets.database.stringdata.uri="postgres://user:pass@postgres:5432/udash?sslmode=disable" \ --set secrets.auth.stringdata.clientid="my-client-id" \ --set secrets.auth.stringdata.audience="https://udash.example/api" \ --set secrets.auth.stringdata.issuer="https://auth.example" ``` +When using an external PostgreSQL in full mode, also set `cnpg.enabled=false` and `secrets.database.stringdata.uri`. + ### With Ingress ```console diff --git a/charts/udash/templates/_helpers.tpl b/charts/udash/templates/_helpers.tpl index 59d6827..574d9b0 100644 --- a/charts/udash/templates/_helpers.tpl +++ b/charts/udash/templates/_helpers.tpl @@ -82,3 +82,10 @@ Create the name of the secrets use by udash agents {{- define "udash.secretName" -}} {{- default (include "udash.fullname" .) .Values.secrets.name }} {{- end }} + +{{/* +Create the name of the CloudNative-PG cluster +*/}} +{{- define "udash.cnpgClusterName" -}} +{{- printf "%s-db" (include "udash.fullname" .) }} +{{- end }} diff --git a/charts/udash/templates/cnpg-cluster.yaml b/charts/udash/templates/cnpg-cluster.yaml new file mode 100644 index 0000000..1c3d868 --- /dev/null +++ b/charts/udash/templates/cnpg-cluster.yaml @@ -0,0 +1,16 @@ +{{- if .Values.cnpg.enabled }} +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: {{ include "udash.cnpgClusterName" . }} + labels: + {{- include "udash.labels" . | nindent 4 }} +spec: + instances: {{ .Values.cnpg.instances }} + bootstrap: + initdb: + database: {{ .Values.cnpg.database }} + owner: {{ .Values.cnpg.owner }} + storage: + size: {{ .Values.cnpg.storage.size }} +{{- end }} diff --git a/charts/udash/templates/configmap-front.yaml b/charts/udash/templates/configmap-front.yaml index f4b9b3c..1431ea3 100644 --- a/charts/udash/templates/configmap-front.yaml +++ b/charts/udash/templates/configmap-front.yaml @@ -15,16 +15,8 @@ data: { "OAUTH_DOMAIN": "{{ .Values.secrets.auth.stringdata.issuer }}", "OAUTH_CLIENTID": "{{ .Values.secrets.auth.stringdata.clientid }}", - "OAUTH_AUDIENCE": "{{ .Values.secrets.auth.stringdata.audience }}" + "OAUTH_AUDIENCE": "{{ .Values.secrets.auth.stringdata.audience }}", + "API_BASE_URL": "/api", + "APP_BASE_PATH": "/" } - - # Config.js is used by the front application to get login information - config.js: | - const config = (() => { - return { - "OAUTH_DOMAIN": "{{ .Values.secrets.auth.stringdata.issuer }}", - "OAUTH_CLIENTID": "{{ .Values.secrets.auth.stringdata.clientid }}", - "OAUTH_AUDIENCE": "{{ .Values.secrets.auth.stringdata.audience }}" - }; - })(); {{ end }} diff --git a/charts/udash/templates/configmap-server.yaml b/charts/udash/templates/configmap-server.yaml index 9fc4488..8ca5bb4 100644 --- a/charts/udash/templates/configmap-server.yaml +++ b/charts/udash/templates/configmap-server.yaml @@ -14,16 +14,23 @@ data: config.yaml: | server: dryrun: true + {{- if not .Values.cnpg.enabled }} database: uri: "{{ .Values.secrets.database.stringdata.uri }}" + {{- end }} {{- else }} config.yaml: | server: auth: - mode: "oauth" + mode: "{{ .Values.secrets.auth.stringdata.mode }}" + {{- if and ( eq .Values.secrets.auth.stringdata.mode "oauth" ) }} issuer: "{{ .Values.secrets.auth.stringdata.issuer }}" audience: "{{ .Values.secrets.auth.stringdata.audience }}" + clientid: "{{ .Values.secrets.auth.stringdata.clientid }}" + {{- end }} + {{- if not .Values.cnpg.enabled }} database: uri: "{{ .Values.secrets.database.stringdata.uri }}" + {{- end }} {{- end }} diff --git a/charts/udash/templates/deployment-server.yaml b/charts/udash/templates/deployment-server.yaml index 710bd96..8a6afba 100644 --- a/charts/udash/templates/deployment-server.yaml +++ b/charts/udash/templates/deployment-server.yaml @@ -41,6 +41,19 @@ spec: env: - name: GIN_MODE value: release + {{- if .Values.cnpg.enabled }} + - name: UDASH_DB_URI + valueFrom: + secretKeyRef: + name: {{ include "udash.cnpgClusterName" . }}-app + key: uri + {{- else }} + - name: UDASH_DB_URI + valueFrom: + secretKeyRef: + name: {{ include "udash.secretName" . }}-database + key: uri + {{- end }} ports: - name: http containerPort: 8080 diff --git a/charts/udash/templates/secrets-database.yaml b/charts/udash/templates/secrets-database.yaml index 562cc5e..0400595 100644 --- a/charts/udash/templates/secrets-database.yaml +++ b/charts/udash/templates/secrets-database.yaml @@ -1,3 +1,4 @@ +{{- if not .Values.cnpg.enabled }} apiVersion: v1 kind: Secret metadata: @@ -11,4 +12,4 @@ metadata: type: Opaque stringData: uri: {{ .Values.secrets.database.stringdata.uri }} - +{{- end }} diff --git a/charts/udash/values.yaml b/charts/udash/values.yaml index 6843b7a..e99287a 100644 --- a/charts/udash/values.yaml +++ b/charts/udash/values.yaml @@ -63,8 +63,8 @@ secrets: # -- Annotations to add to the auth Secret. annotations: {} stringdata: - # -- Authentication mode. Supported values: `oauth`. - mode: oauth + # -- Authentication mode. Supported values: `oauth`, `none`. + mode: none # -- OAuth2 client ID. clientid: xxx.example # -- OAuth2 audience. @@ -140,3 +140,16 @@ service: # -- Run the udash-server in read-only / dry-run mode (no authentication required). Set to `false` to enable full OAuth2 authentication. readonly: true + +cnpg: + # -- Enable CloudNative-PG managed PostgreSQL cluster. When true, the chart provisions a CNPG Cluster and injects credentials automatically. Requires the CNPG operator to be installed separately. + enabled: true + # -- Number of PostgreSQL instances in the CNPG cluster. + instances: 1 + # -- PostgreSQL database name created during cluster bootstrap. + database: udash + # -- PostgreSQL role/owner created during cluster bootstrap. + owner: udash + storage: + # -- Storage size for each PostgreSQL instance. + size: 1Gi From b6464099daa3bea1195a2494f1ef404ede6ca93c Mon Sep 17 00:00:00 2001 From: Olivier Vernin Date: Sun, 17 May 2026 09:22:58 +0200 Subject: [PATCH 2/6] chore: update udash-front to v0.19.0 Signed-off-by: Olivier Vernin --- charts/udash/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/udash/values.yaml b/charts/udash/values.yaml index e99287a..cad0f50 100644 --- a/charts/udash/values.yaml +++ b/charts/udash/values.yaml @@ -27,7 +27,7 @@ images: # -- Image pull policy for the udash-front image. pullPolicy: IfNotPresent # -- Overrides the image tag whose default is the chart appVersion. - tag: v0.17.0@sha256:9afa7d7b0d8a8bc70154b83fd3ded620714cd79443c9e17a72bc5d7611c22f28 + tag: v0.19.0@sha256:b220b047a7536ab3bd77a5a312da10a051802a674385cd50ac7df00e652c7e5e # -- Secrets for pulling images from private registries. imagePullSecrets: [] From 07c1cf7d1edf881618fbb3faf677555be750a5c6 Mon Sep 17 00:00:00 2001 From: Olivier Vernin Date: Sun, 17 May 2026 12:29:59 +0200 Subject: [PATCH 3/6] feat: allow to customize endpoint url Signed-off-by: Olivier Vernin --- charts/udash/README.md | 86 +++++++++++++++++++-- charts/udash/README.md.gotmpl | 70 ++++++++++++++++- charts/udash/templates/configmap-front.yaml | 4 +- charts/udash/templates/ingress.yaml | 55 ++++++++++++- charts/udash/values.yaml | 30 ++++++- 5 files changed, 230 insertions(+), 15 deletions(-) diff --git a/charts/udash/README.md b/charts/udash/README.md index 038c46a..3b98d51 100644 --- a/charts/udash/README.md +++ b/charts/udash/README.md @@ -89,15 +89,81 @@ helm install udash updatecli/udash \ When using an external PostgreSQL in full mode, also set `cnpg.enabled=false` and `secrets.database.stringdata.uri`. -### With Ingress +### With Ingress (same host, default paths) + +Routes `udash.example.com/` to the front and `udash.example.com/api` to the server: ```console helm install udash updatecli/udash \ --set ingress.enabled=true \ - --set ingress.className=nginx \ + --set ingress.className=traefik \ --set "ingress.hosts[0].host=udash.example.com" ``` +### With Ingress (split domain) + +Routes the front and API to different hostnames. Set `front.apiBaseUrl` to the absolute API URL +so the browser knows where to reach the server: + +```yaml +front: + apiBaseUrl: "https://api.udash.example.com/api" + +ingress: + enabled: true + className: traefik + hosts: + - host: udash.example.com + tls: + - secretName: udash-tls + hosts: [udash.example.com] + server: + host: api.udash.example.com + tls: + - secretName: udash-api-tls + hosts: [api.udash.example.com] +``` + +### With Ingress (split domain and custom sub-paths) + +Serves the front at `domain.example/project` and the API at `api.domain.example/myproject`. +When the external server sub-path differs from `/api`, Traefik requires a `Middleware` resource +to rewrite the path to the server's internal `/api` prefix. Create the middleware first: + +```yaml +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: udash-api-rewrite + namespace: default +spec: + replacePathRegex: + regex: "^/myproject(.*)" + replacement: "/api$1" +``` + +Then reference it via `ingress.server.annotations` (the middleware name format is +`-@kubernetescrd`): + +```yaml +front: + appBasePath: "/project" + apiBaseUrl: "https://api.domain.example/myproject" + +ingress: + enabled: true + className: traefik + hosts: + - host: domain.example + paths: + front: "/project" + server: + host: api.domain.example + path: "/myproject" + annotations: + traefik.ingress.kubernetes.io/router.middlewares: default-udash-api-rewrite@kubernetescrd +``` + ## Uninstalling the Chart ```console @@ -122,21 +188,29 @@ helm uninstall udash | cnpg.storage.size | string | `"1Gi"` | Storage size for each PostgreSQL instance. | | configMap.annotations | object | `{}` | Annotations to add to the ConfigMap. | | configMap.name | string | `""` | The name of the ConfigMap used to store server/front configuration. If not set, a name is generated using the fullname template. | +| front.apiBaseUrl | string | `"/api"` | API base URL used by the browser. Use a relative path (e.g. "/api") for same-host routing. Use an absolute URL (e.g. "https://api.domain.example/api") for split-domain routing. | +| front.appBasePath | string | `"/"` | Base path for the SPA. Change when the front is mounted at a sub-path (e.g. "/project"). | | fullnameOverride | string | `""` | Full override for the chart name used in resource names. | | imagePullSecrets | list | `[]` | Secrets for pulling images from private registries. | | images.front.pullPolicy | string | `"IfNotPresent"` | Image pull policy for the udash-front image. | | images.front.repository | string | `"ghcr.io/updatecli/udash-front"` | Repository for the udash-front image. | -| images.front.tag | string | `"v0.17.0@sha256:9afa7d7b0d8a8bc70154b83fd3ded620714cd79443c9e17a72bc5d7611c22f28"` | Overrides the image tag whose default is the chart appVersion. | +| images.front.tag | string | `"v0.19.0@sha256:b220b047a7536ab3bd77a5a312da10a051802a674385cd50ac7df00e652c7e5e"` | Overrides the image tag whose default is the chart appVersion. | | images.server.args | list | `["server","start"]` | Arguments for the udash-server container. | | images.server.command | list | `["udash"]` | Command override for the udash-server container. | | images.server.pullPolicy | string | `"IfNotPresent"` | Image pull policy for the udash-server image. | | images.server.repository | string | `"ghcr.io/updatecli/udash"` | Repository for the udash-server image. | | images.server.tag | string | `"v0.14.0@sha256:a52edcb9535d8c392a2e592bf7c3b4fc0c14ecd4b1360aa96639145038b5da75"` | Overrides the image tag whose default is the chart appVersion. | -| ingress.annotations | object | `{}` | Annotations to add to the Ingress resource. | +| ingress.annotations | object | `{}` | Annotations to add to the front Ingress resource. | | ingress.className | string | `""` | IngressClass name (Kubernetes >= 1.18). | | ingress.enabled | bool | `false` | Enable Ingress resource creation. | -| ingress.hosts | list | `[{"host":"udash.local"}]` | Ingress host rules. Traffic to `/` is forwarded to udash-front; traffic to `/api` is forwarded to udash-server. | -| ingress.tls | list | `[]` | TLS configuration for the Ingress. | +| ingress.hosts | list | `[{"host":"udash.local"}]` | Ingress host rules for the front. Traffic is forwarded according to ingress.paths. | +| ingress.paths.front | string | `"/"` | Path prefix for udash-front on same-host routing. | +| ingress.paths.server | string | `"/api"` | Path prefix for udash-server on same-host routing (when ingress.server.host is empty). | +| ingress.server.annotations | object | `{}` | Annotations to add to the server Ingress resource (e.g. rewrite-target when path differs from /api). | +| ingress.server.host | string | `""` | Optional separate hostname for the API server. When empty (default): udash-server is routed via ingress.paths.server on each front host. When set: a second Ingress is created for this host routing to udash-server. | +| ingress.server.path | string | `"/api"` | Path prefix for udash-server on the separate server host. | +| ingress.server.tls | list | `[]` | TLS configuration for the server Ingress. | +| ingress.tls | list | `[]` | TLS configuration for the front Ingress. | | nameOverride | string | `""` | Override for the chart name used in resource names. | | nodeSelector | object | `{}` | Node selector for pod scheduling. | | podAnnotations | object | `{}` | Annotations to add to all pods. | diff --git a/charts/udash/README.md.gotmpl b/charts/udash/README.md.gotmpl index e4559e5..f56ba0d 100644 --- a/charts/udash/README.md.gotmpl +++ b/charts/udash/README.md.gotmpl @@ -91,15 +91,81 @@ helm install udash updatecli/udash \ When using an external PostgreSQL in full mode, also set `cnpg.enabled=false` and `secrets.database.stringdata.uri`. -### With Ingress +### With Ingress (same host, default paths) + +Routes `udash.example.com/` to the front and `udash.example.com/api` to the server: ```console helm install udash updatecli/udash \ --set ingress.enabled=true \ - --set ingress.className=nginx \ + --set ingress.className=traefik \ --set "ingress.hosts[0].host=udash.example.com" ``` +### With Ingress (split domain) + +Routes the front and API to different hostnames. Set `front.apiBaseUrl` to the absolute API URL +so the browser knows where to reach the server: + +```yaml +front: + apiBaseUrl: "https://api.udash.example.com/api" + +ingress: + enabled: true + className: traefik + hosts: + - host: udash.example.com + tls: + - secretName: udash-tls + hosts: [udash.example.com] + server: + host: api.udash.example.com + tls: + - secretName: udash-api-tls + hosts: [api.udash.example.com] +``` + +### With Ingress (split domain and custom sub-paths) + +Serves the front at `domain.example/project` and the API at `api.domain.example/myproject`. +When the external server sub-path differs from `/api`, Traefik requires a `Middleware` resource +to rewrite the path to the server's internal `/api` prefix. Create the middleware first: + +```yaml +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: udash-api-rewrite + namespace: default +spec: + replacePathRegex: + regex: "^/myproject(.*)" + replacement: "/api$1" +``` + +Then reference it via `ingress.server.annotations` (the middleware name format is +`-@kubernetescrd`): + +```yaml +front: + appBasePath: "/project" + apiBaseUrl: "https://api.domain.example/myproject" + +ingress: + enabled: true + className: traefik + hosts: + - host: domain.example + paths: + front: "/project" + server: + host: api.domain.example + path: "/myproject" + annotations: + traefik.ingress.kubernetes.io/router.middlewares: default-udash-api-rewrite@kubernetescrd +``` + ## Uninstalling the Chart ```console diff --git a/charts/udash/templates/configmap-front.yaml b/charts/udash/templates/configmap-front.yaml index 1431ea3..97411dc 100644 --- a/charts/udash/templates/configmap-front.yaml +++ b/charts/udash/templates/configmap-front.yaml @@ -16,7 +16,7 @@ data: "OAUTH_DOMAIN": "{{ .Values.secrets.auth.stringdata.issuer }}", "OAUTH_CLIENTID": "{{ .Values.secrets.auth.stringdata.clientid }}", "OAUTH_AUDIENCE": "{{ .Values.secrets.auth.stringdata.audience }}", - "API_BASE_URL": "/api", - "APP_BASE_PATH": "/" + "API_BASE_URL": "{{ .Values.front.apiBaseUrl }}", + "APP_BASE_PATH": "{{ .Values.front.appBasePath }}" } {{ end }} diff --git a/charts/udash/templates/ingress.yaml b/charts/udash/templates/ingress.yaml index a6a114f..7a3527f 100644 --- a/charts/udash/templates/ingress.yaml +++ b/charts/udash/templates/ingress.yaml @@ -41,7 +41,7 @@ spec: - host: {{ .host | quote }} http: paths: - - path: / + - path: {{ $.Values.ingress.paths.front }} pathType: Prefix backend: {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} @@ -53,7 +53,8 @@ spec: serviceName: udash-front servicePort: {{ $svcPort }} {{- end }} - - path: /api + {{- if not $.Values.ingress.server.host }} + - path: {{ $.Values.ingress.paths.server }} pathType: Prefix backend: {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} @@ -65,5 +66,55 @@ spec: serviceName: udash-server servicePort: {{ $svcPort }} {{- end }} + {{- end }} {{- end }} +{{- if .Values.ingress.server.host }} +--- +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }}-server + labels: + {{- include "udash.labels" . | nindent 4 }} + {{- with .Values.ingress.server.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.server.tls }} + tls: + {{- range .Values.ingress.server.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + - host: {{ .Values.ingress.server.host | quote }} + http: + paths: + - path: {{ .Values.ingress.server.path }} + pathType: Prefix + backend: + {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }} + service: + name: udash-server + port: + number: {{ $svcPort }} + {{- else }} + serviceName: udash-server + servicePort: {{ $svcPort }} + {{- end }} +{{- end }} {{- end }} diff --git a/charts/udash/values.yaml b/charts/udash/values.yaml index cad0f50..329cff2 100644 --- a/charts/udash/values.yaml +++ b/charts/udash/values.yaml @@ -92,18 +92,42 @@ ingress: enabled: false # -- IngressClass name (Kubernetes >= 1.18). className: "" - # -- Annotations to add to the Ingress resource. + # -- Annotations to add to the front Ingress resource. annotations: {} # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" - # -- Ingress host rules. Traffic to `/` is forwarded to udash-front; traffic to `/api` is forwarded to udash-server. + # -- Ingress host rules for the front. Traffic is forwarded according to ingress.paths. hosts: - host: udash.local - # -- TLS configuration for the Ingress. + # -- TLS configuration for the front Ingress. tls: [] # - secretName: chart-example-tls # hosts: # - chart-example.local + paths: + # -- Path prefix for udash-front on same-host routing. + front: "/" + # -- Path prefix for udash-server on same-host routing (when ingress.server.host is empty). + server: "/api" + server: + # -- Optional separate hostname for the API server. + # When empty (default): udash-server is routed via ingress.paths.server on each front host. + # When set: a second Ingress is created for this host routing to udash-server. + host: "" + # -- Path prefix for udash-server on the separate server host. + path: "/api" + # -- Annotations to add to the server Ingress resource (e.g. rewrite-target when path differs from /api). + annotations: {} + # -- TLS configuration for the server Ingress. + tls: [] + +front: + # -- API base URL used by the browser. + # Use a relative path (e.g. "/api") for same-host routing. + # Use an absolute URL (e.g. "https://api.domain.example/api") for split-domain routing. + apiBaseUrl: "/api" + # -- Base path for the SPA. Change when the front is mounted at a sub-path (e.g. "/project"). + appBasePath: "/" # -- Resource requests and limits for all containers. Ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ resources: {} From 5a6ab2fb2ef7494a697b37ddbc859c28334155d2 Mon Sep 17 00:00:00 2001 From: Olivier Vernin Date: Mon, 18 May 2026 17:27:59 +0200 Subject: [PATCH 4/6] fix: frontend/backend path Signed-off-by: Olivier Vernin --- charts/udash/Chart.yaml | 1 - charts/udash/README.md | 102 +++++++++++++----- charts/udash/README.md.gotmpl | 97 +++++++++++++---- charts/udash/templates/_helpers.tpl | 21 ++++ charts/udash/templates/configmap-front.yaml | 5 - charts/udash/templates/deployment-front.yaml | 15 +-- charts/udash/templates/ingress.yaml | 15 ++- .../udash/templates/traefik-strip-front.yaml | 12 +++ .../udash/templates/traefik-strip-server.yaml | 23 ++++ charts/udash/values.yaml | 23 +++- 10 files changed, 245 insertions(+), 69 deletions(-) create mode 100644 charts/udash/templates/traefik-strip-front.yaml create mode 100644 charts/udash/templates/traefik-strip-server.yaml diff --git a/charts/udash/Chart.yaml b/charts/udash/Chart.yaml index 8c12e5a..ab67d3a 100644 --- a/charts/udash/Chart.yaml +++ b/charts/udash/Chart.yaml @@ -5,4 +5,3 @@ icon: https://www.updatecli.io/images/updatecli_transparent.png name: udash type: application version: 0.26.0 - diff --git a/charts/udash/README.md b/charts/udash/README.md index 3b98d51..1653868 100644 --- a/charts/udash/README.md +++ b/charts/udash/README.md @@ -100,6 +100,59 @@ helm install udash updatecli/udash \ --set "ingress.hosts[0].host=udash.example.com" ``` +### With Ingress (same host, subpath) + +Serves the front at `app.example.com/updatecli` and the API at `app.example.com/api`. +The front ingress must strip the `/updatecli` prefix before forwarding to nginx so that +the nginx container always receives paths starting with `/`. Configure the SPA base path +to match via `front.appBasePath`. + +**nginx ingress controller** (uses `rewrite-target` + `use-regex`): + +```yaml +ingress: + enabled: true + className: nginx + annotations: + nginx.ingress.kubernetes.io/rewrite-target: /$2 + nginx.ingress.kubernetes.io/use-regex: "true" + hosts: + - host: app.example.com + paths: + front: "/updatecli(/|$)(.*)" # regex captures the suffix to pass as $2 + server: "/api" # no rewrite needed; server handles /api natively + +front: + appBasePath: "/updatecli" # must match the path prefix (without regex) + apiBaseUrl: "/api" +``` + +**Traefik** — set `ingress.traefik.stripPrefix.enabled: true` and the chart creates the +`StripPrefix` Middleware CR and wires its annotation into the Ingress automatically. +Requires Traefik CRDs (`traefik.io/v1alpha1`) to be installed in the cluster: + +```yaml +ingress: + enabled: true + className: traefik + hosts: + - host: app.example.com + paths: + front: "/updatecli" + server: "/api" + traefik: + stripPrefix: + enabled: true # creates the Middleware CR and injects the router annotation + +front: + appBasePath: "/updatecli" + apiBaseUrl: "/api" +``` + +> **Note:** `front.appBasePath` tells the SPA JavaScript router which path prefix to use for +> client-side navigation. It must always match the **un-rewritten** path (e.g. `/updatecli`). +> The API server does not need a strip-prefix because it handles `/api` natively. + ### With Ingress (split domain) Routes the front and API to different hostnames. Set `front.apiBaseUrl` to the absolute API URL @@ -126,29 +179,19 @@ ingress: ### With Ingress (split domain and custom sub-paths) -Serves the front at `domain.example/project` and the API at `api.domain.example/myproject`. -When the external server sub-path differs from `/api`, Traefik requires a `Middleware` resource -to rewrite the path to the server's internal `/api` prefix. Create the middleware first: +Serves the front at `domain.example/project` and the API at `api.domain.example/updatecli`. +Set `ingress.traefik.stripPrefix.enabled: true` and the chart automatically creates all needed +Traefik Middlewares and wires their annotations: -```yaml -apiVersion: traefik.io/v1alpha1 -kind: Middleware -metadata: - name: udash-api-rewrite - namespace: default -spec: - replacePathRegex: - regex: "^/myproject(.*)" - replacement: "/api$1" -``` +- **Front**: `StripPrefix /project` → nginx receives `/` +- **Server**: `StripPrefix /updatecli` → `AddPrefix /api` → backend receives `/api/*` -Then reference it via `ingress.server.annotations` (the middleware name format is -`-@kubernetescrd`): +Requires Traefik CRDs (`traefik.io/v1alpha1`) to be installed: ```yaml front: appBasePath: "/project" - apiBaseUrl: "https://api.domain.example/myproject" + apiBaseUrl: "https://api.domain.example/updatecli" ingress: enabled: true @@ -157,13 +200,23 @@ ingress: - host: domain.example paths: front: "/project" + server: "/api" + traefik: + stripPrefix: + enabled: true # creates Middlewares for both front and server automatically server: host: api.domain.example - path: "/myproject" - annotations: - traefik.ingress.kubernetes.io/router.middlewares: default-udash-api-rewrite@kubernetescrd + path: "/updatecli" ``` +The chart renders: + +| Middleware | Type | Effect | +|---|---|---| +| `-strip-front` | StripPrefix `/project` | strips front sub-path before nginx | +| `-strip-server` | StripPrefix `/updatecli` | strips external prefix on API host | +| `-add-server` | AddPrefix `/api` | restores the `/api` prefix the backend expects | + ## Uninstalling the Chart ```console @@ -189,7 +242,7 @@ helm uninstall udash | configMap.annotations | object | `{}` | Annotations to add to the ConfigMap. | | configMap.name | string | `""` | The name of the ConfigMap used to store server/front configuration. If not set, a name is generated using the fullname template. | | front.apiBaseUrl | string | `"/api"` | API base URL used by the browser. Use a relative path (e.g. "/api") for same-host routing. Use an absolute URL (e.g. "https://api.domain.example/api") for split-domain routing. | -| front.appBasePath | string | `"/"` | Base path for the SPA. Change when the front is mounted at a sub-path (e.g. "/project"). | +| front.appBasePath | string | `"/"` | Base path for the SPA. Must match ingress.paths.front when using subpath routing. Example: set both ingress.paths.front and front.appBasePath to "/updatecli". | | fullnameOverride | string | `""` | Full override for the chart name used in resource names. | | imagePullSecrets | list | `[]` | Secrets for pulling images from private registries. | | images.front.pullPolicy | string | `"IfNotPresent"` | Image pull policy for the udash-front image. | @@ -200,17 +253,18 @@ helm uninstall udash | images.server.pullPolicy | string | `"IfNotPresent"` | Image pull policy for the udash-server image. | | images.server.repository | string | `"ghcr.io/updatecli/udash"` | Repository for the udash-server image. | | images.server.tag | string | `"v0.14.0@sha256:a52edcb9535d8c392a2e592bf7c3b4fc0c14ecd4b1360aa96639145038b5da75"` | Overrides the image tag whose default is the chart appVersion. | -| ingress.annotations | object | `{}` | Annotations to add to the front Ingress resource. | +| ingress.annotations | object | `{}` | Annotations to add to the front Ingress resource. For subpath routing, add the strip-prefix annotation for your ingress controller. nginx example: nginx.ingress.kubernetes.io/rewrite-target: /$2 nginx.ingress.kubernetes.io/use-regex: "true" (and set ingress.paths.front to "/updatecli(/|$)(.*)") traefik example (requires a Middleware CR for stripprefix): traefik.ingress.kubernetes.io/router.middlewares: -@kubernetescrd | | ingress.className | string | `""` | IngressClass name (Kubernetes >= 1.18). | | ingress.enabled | bool | `false` | Enable Ingress resource creation. | | ingress.hosts | list | `[{"host":"udash.local"}]` | Ingress host rules for the front. Traffic is forwarded according to ingress.paths. | -| ingress.paths.front | string | `"/"` | Path prefix for udash-front on same-host routing. | +| ingress.paths.front | string | `"/"` | Path prefix for udash-front on same-host routing. For subpath routing (e.g. "/updatecli"), also set front.appBasePath to the same value and add a strip-prefix annotation so nginx receives "/" instead of "/updatecli/...". | | ingress.paths.server | string | `"/api"` | Path prefix for udash-server on same-host routing (when ingress.server.host is empty). | -| ingress.server.annotations | object | `{}` | Annotations to add to the server Ingress resource (e.g. rewrite-target when path differs from /api). | +| ingress.server.annotations | object | `{}` | Annotations to add to the server Ingress resource. | | ingress.server.host | string | `""` | Optional separate hostname for the API server. When empty (default): udash-server is routed via ingress.paths.server on each front host. When set: a second Ingress is created for this host routing to udash-server. | | ingress.server.path | string | `"/api"` | Path prefix for udash-server on the separate server host. | | ingress.server.tls | list | `[]` | TLS configuration for the server Ingress. | | ingress.tls | list | `[]` | TLS configuration for the front Ingress. | +| ingress.traefik.stripPrefix.enabled | bool | `false` | When true, create Traefik Middleware resources and wire their annotations automatically. Front: a StripPrefix Middleware strips ingress.paths.front (useful when it is not "/"). Server: a StripPrefix + AddPrefix Middleware chain rewrites ingress.server.path to ingress.paths.server ("/api") — only rendered when ingress.server.host is set. Example: external /updatecli/* → strip /updatecli → add /api → backend sees /api/*. Requires Traefik CRDs (traefik.io/v1alpha1) to be installed in the cluster. | | nameOverride | string | `""` | Override for the chart name used in resource names. | | nodeSelector | object | `{}` | Node selector for pod scheduling. | | podAnnotations | object | `{}` | Annotations to add to all pods. | diff --git a/charts/udash/README.md.gotmpl b/charts/udash/README.md.gotmpl index f56ba0d..35ce8b1 100644 --- a/charts/udash/README.md.gotmpl +++ b/charts/udash/README.md.gotmpl @@ -6,7 +6,8 @@ {{ template "chart.description" . }} -[Udash](https://github.com/updatecli/udash) is the Updatecli Dashboard — a web application that collects and displays Updatecli reports, giving your team a centralised view of software update activity across all your repositories. +[Udash](https://github.com/updatecli/udash) is the Updatecli Dashboard, a web application that collects and displays Updatecli reports, +giving your team a centralised view of software update activity across all your repositories. ## Architecture @@ -102,6 +103,59 @@ helm install udash updatecli/udash \ --set "ingress.hosts[0].host=udash.example.com" ``` +### With Ingress (same host, subpath) + +Serves the front at `app.example.com/updatecli` and the API at `app.example.com/api`. +The front ingress must strip the `/updatecli` prefix before forwarding to nginx so that +the nginx container always receives paths starting with `/`. Configure the SPA base path +to match via `front.appBasePath`. + +**nginx ingress controller** (uses `rewrite-target` + `use-regex`): + +```yaml +ingress: + enabled: true + className: nginx + annotations: + nginx.ingress.kubernetes.io/rewrite-target: /$2 + nginx.ingress.kubernetes.io/use-regex: "true" + hosts: + - host: app.example.com + paths: + front: "/updatecli(/|$)(.*)" # regex captures the suffix to pass as $2 + server: "/api" # no rewrite needed; server handles /api natively + +front: + appBasePath: "/updatecli" # must match the path prefix (without regex) + apiBaseUrl: "/api" +``` + +**Traefik** — set `ingress.traefik.stripPrefix.enabled: true` and the chart creates the +`StripPrefix` Middleware CR and wires its annotation into the Ingress automatically. +Requires Traefik CRDs (`traefik.io/v1alpha1`) to be installed in the cluster: + +```yaml +ingress: + enabled: true + className: traefik + hosts: + - host: app.example.com + paths: + front: "/updatecli" + server: "/api" + traefik: + stripPrefix: + enabled: true # creates the Middleware CR and injects the router annotation + +front: + appBasePath: "/updatecli" + apiBaseUrl: "/api" +``` + +> **Note:** `front.appBasePath` tells the SPA JavaScript router which path prefix to use for +> client-side navigation. It must always match the **un-rewritten** path (e.g. `/updatecli`). +> The API server does not need a strip-prefix because it handles `/api` natively. + ### With Ingress (split domain) Routes the front and API to different hostnames. Set `front.apiBaseUrl` to the absolute API URL @@ -128,29 +182,19 @@ ingress: ### With Ingress (split domain and custom sub-paths) -Serves the front at `domain.example/project` and the API at `api.domain.example/myproject`. -When the external server sub-path differs from `/api`, Traefik requires a `Middleware` resource -to rewrite the path to the server's internal `/api` prefix. Create the middleware first: +Serves the front at `domain.example/project` and the API at `api.domain.example/updatecli`. +Set `ingress.traefik.stripPrefix.enabled: true` and the chart automatically creates all needed +Traefik Middlewares and wires their annotations: -```yaml -apiVersion: traefik.io/v1alpha1 -kind: Middleware -metadata: - name: udash-api-rewrite - namespace: default -spec: - replacePathRegex: - regex: "^/myproject(.*)" - replacement: "/api$1" -``` +- **Front**: `StripPrefix /project` → nginx receives `/` +- **Server**: `StripPrefix /updatecli` → `AddPrefix /api` → backend receives `/api/*` -Then reference it via `ingress.server.annotations` (the middleware name format is -`-@kubernetescrd`): +Requires Traefik CRDs (`traefik.io/v1alpha1`) to be installed: ```yaml front: appBasePath: "/project" - apiBaseUrl: "https://api.domain.example/myproject" + apiBaseUrl: "https://api.domain.example/updatecli" ingress: enabled: true @@ -159,13 +203,24 @@ ingress: - host: domain.example paths: front: "/project" + server: "/api" + traefik: + stripPrefix: + enabled: true # creates Middlewares for both front and server automatically server: host: api.domain.example - path: "/myproject" - annotations: - traefik.ingress.kubernetes.io/router.middlewares: default-udash-api-rewrite@kubernetescrd + path: "/updatecli" ``` +The chart renders: + +| Middleware | Type | Effect | +|---|---|---| +| `-strip-front` | StripPrefix `/project` | strips front sub-path before nginx | +| `-strip-server` | StripPrefix `/updatecli` | strips external prefix on API host | +| `-add-server` | AddPrefix `/api` | restores the `/api` prefix the backend expects | + + ## Uninstalling the Chart ```console diff --git a/charts/udash/templates/_helpers.tpl b/charts/udash/templates/_helpers.tpl index 574d9b0..35c403b 100644 --- a/charts/udash/templates/_helpers.tpl +++ b/charts/udash/templates/_helpers.tpl @@ -89,3 +89,24 @@ Create the name of the CloudNative-PG cluster {{- define "udash.cnpgClusterName" -}} {{- printf "%s-db" (include "udash.fullname" .) }} {{- end }} + +{{/* +Create the name of the Traefik StripPrefix Middleware for the front ingress +*/}} +{{- define "udash.traefikStripFrontMiddlewareName" -}} +{{- printf "%s-strip-front" (include "udash.fullname" .) }} +{{- end }} + +{{/* +Create the name of the Traefik StripPrefix Middleware for the server ingress +*/}} +{{- define "udash.traefikStripServerMiddlewareName" -}} +{{- printf "%s-strip-server" (include "udash.fullname" .) }} +{{- end }} + +{{/* +Create the name of the Traefik AddPrefix Middleware for the server ingress +*/}} +{{- define "udash.traefikAddServerMiddlewareName" -}} +{{- printf "%s-add-server" (include "udash.fullname" .) }} +{{- end }} diff --git a/charts/udash/templates/configmap-front.yaml b/charts/udash/templates/configmap-front.yaml index 97411dc..264d651 100644 --- a/charts/udash/templates/configmap-front.yaml +++ b/charts/udash/templates/configmap-front.yaml @@ -1,4 +1,3 @@ -{{ if not .Values.readonly }} apiVersion: v1 kind: ConfigMap metadata: @@ -13,10 +12,6 @@ data: # Config.json is used by Updatecli to get login information config.json: | { - "OAUTH_DOMAIN": "{{ .Values.secrets.auth.stringdata.issuer }}", - "OAUTH_CLIENTID": "{{ .Values.secrets.auth.stringdata.clientid }}", - "OAUTH_AUDIENCE": "{{ .Values.secrets.auth.stringdata.audience }}", "API_BASE_URL": "{{ .Values.front.apiBaseUrl }}", "APP_BASE_PATH": "{{ .Values.front.appBasePath }}" } -{{ end }} diff --git a/charts/udash/templates/deployment-front.yaml b/charts/udash/templates/deployment-front.yaml index 90eb3cb..93a5e7c 100644 --- a/charts/udash/templates/deployment-front.yaml +++ b/charts/udash/templates/deployment-front.yaml @@ -31,7 +31,7 @@ spec: containers: - name: {{ .Chart.Name }}-front env: - - name: VUE_APP_AUTH_ENABLED + - name: VITE_APP_AUTH_ENABLED {{- if .Values.readonly }} value: "false" {{- else }} @@ -55,16 +55,10 @@ spec: port: http resources: {{- toYaml .Values.resources | nindent 12 }} - {{- if not .Values.readonly }} volumeMounts: - name: "configjson" mountPath: "/usr/share/nginx/html/config.json" subPath: config.json - - name: "configjs" - mountPath: "/usr/share/nginx/html/config.js" - subPath: config.js - {{- end }} - {{- if not .Values.readonly}} volumes: - name: configjson configMap: @@ -72,13 +66,6 @@ spec: items: - key: config.json path: config.json - - name: configjs - configMap: - name: {{ include "udash.configMapName" . }}-front - items: - - key: config.js - path: config.js - {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/charts/udash/templates/ingress.yaml b/charts/udash/templates/ingress.yaml index 7a3527f..279c04a 100644 --- a/charts/udash/templates/ingress.yaml +++ b/charts/udash/templates/ingress.yaml @@ -6,6 +6,11 @@ {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} {{- end }} {{- end }} +{{- $annotations := .Values.ingress.annotations | deepCopy }} +{{- if and .Values.ingress.traefik.stripPrefix.enabled (ne .Values.ingress.paths.front "/") }} + {{- $middlewareRef := printf "%s-%s@kubernetescrd" .Release.Namespace (include "udash.traefikStripFrontMiddlewareName" .) }} + {{- $_ := set $annotations "traefik.ingress.kubernetes.io/router.middlewares" $middlewareRef }} +{{- end }} {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} apiVersion: networking.k8s.io/v1 {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} @@ -18,7 +23,7 @@ metadata: name: {{ $fullName }} labels: {{- include "udash.labels" . | nindent 4 }} - {{- with .Values.ingress.annotations }} + {{- with $annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} @@ -82,7 +87,13 @@ metadata: name: {{ $fullName }}-server labels: {{- include "udash.labels" . | nindent 4 }} - {{- with .Values.ingress.server.annotations }} + {{- $serverAnnotations := .Values.ingress.server.annotations | deepCopy }} + {{- if .Values.ingress.traefik.stripPrefix.enabled }} + {{- $stripRef := printf "%s-%s@kubernetescrd" .Release.Namespace (include "udash.traefikStripServerMiddlewareName" .) }} + {{- $addRef := printf "%s-%s@kubernetescrd" .Release.Namespace (include "udash.traefikAddServerMiddlewareName" .) }} + {{- $_ := set $serverAnnotations "traefik.ingress.kubernetes.io/router.middlewares" (printf "%s,%s" $stripRef $addRef) }} + {{- end }} + {{- with $serverAnnotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} diff --git a/charts/udash/templates/traefik-strip-front.yaml b/charts/udash/templates/traefik-strip-front.yaml new file mode 100644 index 0000000..d35c23f --- /dev/null +++ b/charts/udash/templates/traefik-strip-front.yaml @@ -0,0 +1,12 @@ +{{- if and .Values.ingress.enabled .Values.ingress.traefik.stripPrefix.enabled (ne .Values.ingress.paths.front "/") }} +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: {{ include "udash.traefikStripFrontMiddlewareName" . }} + labels: + {{- include "udash.labels" . | nindent 4 }} +spec: + stripPrefix: + prefixes: + - {{ .Values.ingress.paths.front | quote }} +{{- end }} diff --git a/charts/udash/templates/traefik-strip-server.yaml b/charts/udash/templates/traefik-strip-server.yaml new file mode 100644 index 0000000..7ade562 --- /dev/null +++ b/charts/udash/templates/traefik-strip-server.yaml @@ -0,0 +1,23 @@ +{{- if and .Values.ingress.enabled .Values.ingress.server.host .Values.ingress.traefik.stripPrefix.enabled }} +--- +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: {{ include "udash.traefikStripServerMiddlewareName" . }} + labels: + {{- include "udash.labels" . | nindent 4 }} +spec: + stripPrefix: + prefixes: + - {{ .Values.ingress.server.path | quote }} +--- +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: {{ include "udash.traefikAddServerMiddlewareName" . }} + labels: + {{- include "udash.labels" . | nindent 4 }} +spec: + addPrefix: + prefix: {{ .Values.ingress.paths.server | quote }} +{{- end }} diff --git a/charts/udash/values.yaml b/charts/udash/values.yaml index 329cff2..79cc2f6 100644 --- a/charts/udash/values.yaml +++ b/charts/udash/values.yaml @@ -93,6 +93,13 @@ ingress: # -- IngressClass name (Kubernetes >= 1.18). className: "" # -- Annotations to add to the front Ingress resource. + # For subpath routing, add the strip-prefix annotation for your ingress controller. + # nginx example: + # nginx.ingress.kubernetes.io/rewrite-target: /$2 + # nginx.ingress.kubernetes.io/use-regex: "true" + # (and set ingress.paths.front to "/updatecli(/|$)(.*)") + # traefik example (requires a Middleware CR for stripprefix): + # traefik.ingress.kubernetes.io/router.middlewares: -@kubernetescrd annotations: {} # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" @@ -106,6 +113,8 @@ ingress: # - chart-example.local paths: # -- Path prefix for udash-front on same-host routing. + # For subpath routing (e.g. "/updatecli"), also set front.appBasePath to the same value + # and add a strip-prefix annotation so nginx receives "/" instead of "/updatecli/...". front: "/" # -- Path prefix for udash-server on same-host routing (when ingress.server.host is empty). server: "/api" @@ -116,17 +125,27 @@ ingress: host: "" # -- Path prefix for udash-server on the separate server host. path: "/api" - # -- Annotations to add to the server Ingress resource (e.g. rewrite-target when path differs from /api). + # -- Annotations to add to the server Ingress resource. annotations: {} # -- TLS configuration for the server Ingress. tls: [] + traefik: + stripPrefix: + # -- When true, create Traefik Middleware resources and wire their annotations automatically. + # Front: a StripPrefix Middleware strips ingress.paths.front (useful when it is not "/"). + # Server: a StripPrefix + AddPrefix Middleware chain rewrites ingress.server.path to + # ingress.paths.server ("/api") — only rendered when ingress.server.host is set. + # Example: external /updatecli/* → strip /updatecli → add /api → backend sees /api/*. + # Requires Traefik CRDs (traefik.io/v1alpha1) to be installed in the cluster. + enabled: false front: # -- API base URL used by the browser. # Use a relative path (e.g. "/api") for same-host routing. # Use an absolute URL (e.g. "https://api.domain.example/api") for split-domain routing. apiBaseUrl: "/api" - # -- Base path for the SPA. Change when the front is mounted at a sub-path (e.g. "/project"). + # -- Base path for the SPA. Must match ingress.paths.front when using subpath routing. + # Example: set both ingress.paths.front and front.appBasePath to "/updatecli". appBasePath: "/" # -- Resource requests and limits for all containers. Ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ From 4953f69a17a2960ed7a34504cda77f88a530c043 Mon Sep 17 00:00:00 2001 From: Olivier Vernin Date: Mon, 18 May 2026 17:32:11 +0200 Subject: [PATCH 5/6] chore: update wording Signed-off-by: Olivier Vernin --- charts/udash/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/charts/udash/README.md b/charts/udash/README.md index 1653868..b5583cc 100644 --- a/charts/udash/README.md +++ b/charts/udash/README.md @@ -4,7 +4,8 @@ Udash, the Updatecli DASHboard -[Udash](https://github.com/updatecli/udash) is the Updatecli Dashboard — a web application that collects and displays Updatecli reports, giving your team a centralised view of software update activity across all your repositories. +[Udash](https://github.com/updatecli/udash) is the Updatecli Dashboard, a web application that collects and displays Updatecli reports, +giving your team a centralised view of software update activity across all your repositories. ## Architecture From 0d17d928f01025b29e3f000eff6b5df668b2a2b4 Mon Sep 17 00:00:00 2001 From: Olivier Vernin Date: Mon, 18 May 2026 17:34:27 +0200 Subject: [PATCH 6/6] fix: wrong formatting Signed-off-by: Olivier Vernin --- charts/udash-agent/templates/argoworkflow.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/charts/udash-agent/templates/argoworkflow.yaml b/charts/udash-agent/templates/argoworkflow.yaml index ecfba88..5fc9277 100644 --- a/charts/udash-agent/templates/argoworkflow.yaml +++ b/charts/udash-agent/templates/argoworkflow.yaml @@ -54,7 +54,7 @@ spec: key: "{{ $env }}" # {{- end }} resources: - {{ - toYaml $.Values.resources | nindent 14 }} + {{- toYaml $.Values.resources | nindent 14 }} args: - | cd /workspace @@ -118,9 +118,9 @@ spec: arguments: parameters: - name: repo-url - value: '{{ $workflow.url }}' + value: "{{ $workflow.url }}" - name: repo-branch - value: '{{ $workflow.branch }}' + value: "{{ $workflow.branch }}" - name: updatecli-compose-file - value: '{{ $workflow.composefile }}' + value: "{{ $workflow.composefile }}" #{{ end }}