diff --git a/applications/samples/deploy/values.yaml b/applications/samples/deploy/values.yaml index 607c99445..55bcb0d4e 100644 --- a/applications/samples/deploy/values.yaml +++ b/applications/samples/deploy/values.yaml @@ -7,6 +7,7 @@ harness: service: port: 8080 auto: true + route_pattern: '/(?!\/metrics\/?$)\/.*' use_services: - name: common deployment: @@ -96,4 +97,4 @@ harness: dockerfile: buildArgs: - TEST_ARGUMENT: example value \ No newline at end of file + TEST_ARGUMENT: example value diff --git a/deployment-configuration/helm/templates/certs/letsencrypt.yaml b/deployment-configuration/helm/templates/certs/letsencrypt.yaml index be45ac346..7135e79c0 100644 --- a/deployment-configuration/helm/templates/certs/letsencrypt.yaml +++ b/deployment-configuration/helm/templates/certs/letsencrypt.yaml @@ -12,5 +12,5 @@ spec: solvers: - http01: ingress: - class: nginx + class: {{ .Values.ingress.ingressClass }} {{ end }} diff --git a/deployment-configuration/helm/templates/ingress.yaml b/deployment-configuration/helm/templates/ingress.yaml index 5d3ea8ee7..b48309a40 100644 --- a/deployment-configuration/helm/templates/ingress.yaml +++ b/deployment-configuration/helm/templates/ingress.yaml @@ -39,7 +39,7 @@ {{- end }} {{- end }} {{- end }} - - path: /(.*) + - path: {{ default .root.Values.ingress.route_pattern $app.harness.route_pattern }} pathType: ImplementationSpecific backend: service: @@ -52,7 +52,8 @@ {{ $domain := .root.Values.domain }} {{ $secured_gatekeepers := and .root.Values.secured_gatekeepers }} {{ $app := get .root.Values.apps .service_name}} - - path: /proxy/{{ $app.harness.service.name }}/(.*) + {{ $routePattern := default .root.Values.ingress.route_pattern $app.harness.route_pattern }} + - path: /proxy/{{ $app.harness.service.name }}{{ $routePattern }} pathType: ImplementationSpecific backend: service: @@ -69,11 +70,12 @@ kind: Ingress metadata: name: {{ .Values.ingress.name | quote }} annotations: - kubernetes.io/ingress.class: nginx # Deprecated by Kubernetes, however still required for GKE + kubernetes.io/ingress.class: {{ .Values.ingress.ingressClass }} # Deprecated by Kubernetes, however still required for GKE {{- if and (not .Values.local) $tls }} kubernetes.io/tls-acme: 'true' cert-manager.io/issuer: {{ printf "%s-%s" "letsencrypt" .Values.namespace }} {{- end }} + {{- if eq .Values.ingress.ingressClass "nginx" }} nginx.ingress.kubernetes.io/ssl-redirect: {{ (and $tls .Values.ingress.ssl_redirect) | quote }} nginx.ingress.kubernetes.io/proxy-body-size: '{{ .Values.proxy.payload.max }}m' nginx.ingress.kubernetes.io/proxy-buffer-size: '128k' @@ -86,8 +88,15 @@ metadata: nginx.ingress.kubernetes.io/proxy-read-timeout: {{ .Values.proxy.timeout.read | quote }} nginx.ingress.kubernetes.io/proxy-send-timeout: {{ .Values.proxy.timeout.send | quote }} nginx.ingress.kubernetes.io/use-forwarded-headers: {{ .Values.proxy.forwardedHeaders | quote }} + {{- else if eq .Values.ingress.ingressClass "traefik" }} + traefik.ingress.kubernetes.io/router.pathmatcher: "PathRegexp" + traefik.ingress.kubernetes.io/router.middlewares: {{ printf "%s-ch-rewrite-target@kubernetescrd,%s-ch-body-size@kubernetescrd" .Values.namespace .Values.namespace }}{{ if and $tls .Values.ingress.ssl_redirect }},{{ printf "%s-ch-ssl-redirect@kubernetescrd" .Values.namespace }}{{ end }} + {{- end }} + {{- with .Values.ingress.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} spec: - ingressClassName: nginx + ingressClassName: {{ .Values.ingress.ingressClass }} rules: {{- range $app := .Values.apps }} {{- if (and $mainapp (and $app.harness.name (eq $app.harness.name $mainapp))) }} @@ -147,4 +156,4 @@ spec: {{- end }} secretName: tls-secret {{- end }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/deployment-configuration/helm/templates/traefik-middlewares.yaml b/deployment-configuration/helm/templates/traefik-middlewares.yaml new file mode 100644 index 000000000..42b600e25 --- /dev/null +++ b/deployment-configuration/helm/templates/traefik-middlewares.yaml @@ -0,0 +1,35 @@ +{{- if and .Values.ingress.enabled (eq .Values.ingress.ingressClass "traefik") }} +{{ $tls := not (not .Values.tls) }} +# Rewrite target: equivalent of nginx.ingress.kubernetes.io/rewrite-target: /$1 +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: ch-rewrite-target +spec: + replacePathRegex: + regex: "^/[^/]+(.*)" + replacement: "/$1" +--- +# Body size / buffering: equivalent of nginx proxy-body-size, proxy-buffer-size +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: ch-body-size +spec: + buffering: + maxRequestBodyBytes: {{ mul .Values.proxy.payload.max 1048576 }} + memRequestBodyBytes: 131072 + memResponseBodyBytes: 131072 +{{- if and $tls .Values.ingress.ssl_redirect }} +--- +# SSL redirect: equivalent of nginx.ingress.kubernetes.io/ssl-redirect +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: ch-ssl-redirect +spec: + redirectScheme: + scheme: https + permanent: true +{{- end }} +{{- end }} diff --git a/deployment-configuration/helm/values.yaml b/deployment-configuration/helm/values.yaml index 8b83c0477..c92544f86 100644 --- a/deployment-configuration/helm/values.yaml +++ b/deployment-configuration/helm/values.yaml @@ -35,11 +35,17 @@ ingress: enabled: true # -- K8s Name of ingress. name: cloudharness-ingress + # -- The ingress class to use (e.g. "nginx", "traefik"). + ingressClass: nginx + # -- Additional custom annotations to add to the Ingress resource. + annotations: {} # -- Enables/disables SSL redirect. ssl_redirect: true letsencrypt: # -- Email for letsencrypt. email: cloudharness@metacell.us + # -- Default regex segment for routes (used in paths like '/(pattern)'). + route_pattern: "/(.*)" backup: # -- Flag to enable/disable backups. active: false @@ -89,4 +95,4 @@ proxy: requests: memory: "32Mi" limits: - memory: "64Mi" \ No newline at end of file + memory: "64Mi" diff --git a/docs/ingress-domains-proxies.md b/docs/ingress-domains-proxies.md index 08b86f5ce..349ac53f8 100644 --- a/docs/ingress-domains-proxies.md +++ b/docs/ingress-domains-proxies.md @@ -68,4 +68,38 @@ proxy: Note that in the case that gatekeepers are enabled, the same configurations are applied to the gatekeepers, unless the application override them on `harness.proxy.*`. -See also the [gatekeepers documentation](./accounts.md#secure-and-enpoint-with-the-gatekeeper). \ No newline at end of file +See also the [gatekeepers documentation](./accounts.md#secure-and-enpoint-with-the-gatekeeper). + +## Route pattern configuration + +Cloud Harness allows customizing which request paths are routed to an application +via a glob-style regular expression. There are two levels where this can be set: + +- **Global**: `ingress.route_pattern` in `deployment-configuration/helm/values.yaml` (applies to all apps by default). +- **Application**: `harness.route_pattern` in an application's `values.yaml` (overrides the global value for that app). + +The Helm ingress template uses the application-level `harness.route_pattern` when present, +falling back to the global `ingress.route_pattern` otherwise. + +Example (global default in `deployment-configuration/helm/values.yaml`): + +```yaml +ingress: + # Default regex segment for routes (used in paths like '/(pattern)') + route_pattern: "/(.*)" +``` + +Example (application override in `applications//deploy/values.yaml`): + +```yaml +harness: + # route_pattern is used to build the Ingress path for the app + route_pattern: '/((?!(?:metrics)(?:/)?$).*)' # exclude only '/metrics' and '/metrics/' +``` + +Notes: +- The pattern is inserted into the generated Ingress `path` field. Make sure the regex + is valid for your ingress controller and matches the expected path syntax. +- If you only need to exclude a single exact path (for example `/metrics`), use a + negative lookahead like the example above. If you prefer a simpler global default, + leave `ingress.route_pattern` as `"/(.*)"` and override per-app when needed. diff --git a/tools/deployment-cli-tools/tests/test_codefresh.py b/tools/deployment-cli-tools/tests/test_codefresh.py index 5f096f334..65bd1fc20 100644 --- a/tools/deployment-cli-tools/tests/test_codefresh.py +++ b/tools/deployment-cli-tools/tests/test_codefresh.py @@ -483,7 +483,7 @@ def test_steps_ordered_by_stage_in_generated_config(): assert step_stage_indices == sorted(step_stage_indices), \ "Steps are not ordered by stage. Got stages: " + str( [step.get('stage') for step in steps.values() if step and isinstance(step, dict)] - ) + ) finally: import shutil shutil.rmtree(BUILD_MERGE_DIR, ignore_errors=True)