Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion charts/plane-ce/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Meet Plane. An open-source software development tool to manage issu

type: application

version: 1.6.2
version: 1.6.3
appVersion: "1.4.1"

home: https://plane.so
Expand Down
5 changes: 3 additions & 2 deletions charts/plane-ce/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,8 +402,8 @@ The default value is `"traefik"`. If you previously relied on the implicit defau

When `ingress.ingressClass` starts with `traefik`, the chart deploys native Traefik CRDs instead of a standard `Ingress` resource:

- **`IngressRoute`** (`traefik.io/v1alpha1`) — routes traffic to each Plane service via `Host` + `PathPrefix` rules on the `websecure` entrypoint
- **`Middleware`** (`traefik.io/v1alpha1`) — enforces a request body size limit on every route (default 5 MiB, configurable via `ingress.traefik.maxRequestBodyBytes`)
- **`IngressRoute`** (`traefik.io/v1alpha1`) — routes traffic to each Plane service via `Host` + `PathPrefix` rules on the `websecure` entrypoint. Specific paths (`/god-mode`, `/api`, …) use a higher priority than the catch-all `/` route.
- **`Middleware`** (`traefik.io/v1alpha1`) — request body size limit on every route, plus a temporary redirect that adds a trailing slash for `/god-mode`, `/spaces`, and `/live` (required by those SPAs' React Router basenames).

This requires the Traefik Helm chart to be installed with `providers.kubernetesCRD.enabled=true` (enabled by default in Traefik v3), as shown in the pre-requisites above.

Expand Down Expand Up @@ -437,6 +437,7 @@ ssl:

| Setting | Default | Required | Description |
| -------------------------- | :------------------------------------------------: | :------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| env.web_url | | | Optional public URL for the app (include scheme), e.g. `https://plane.example.com`. When set, used as `WEB_URL` as-is. When empty, `WEB_URL` is derived from `ingress.appHost` using **https** if TLS is configured (`ssl.tls_secret_name`, cert-manager certs, or Traefik ingress), otherwise **http**. |
| env.secret_key | 60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5 | Yes | This must a random string which is used for hashing/encrypting the sensitive data within the application. Once set, changing this might impact the already hashed/encrypted data |
| env.default_cluster_domain | cluster.local | Yes | Set this value as configured in your kubernetes cluster. `cluster.local` is usally the default in most cases. |

Expand Down
8 changes: 7 additions & 1 deletion charts/plane-ce/templates/config-secrets/app-env.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,13 @@ data:
MINIO_ENDPOINT_SSL: {{ .Values.minio.env.minio_endpoint_ssl | default false | ternary "1" "0" | quote }}
API_KEY_RATE_LIMIT: {{ .Values.env.api_key_rate_limit | default "60/minute" | quote }}

{{- if .Values.ingress.appHost }}
{{- /* Prefer explicit env.web_url. Otherwise use https when TLS is configured
(tls_secret_name or cert-manager generated certs), matching plane-enterprise. */}}
{{- if .Values.env.web_url }}
WEB_URL: {{ .Values.env.web_url | quote }}
{{- else if and .Values.ingress.appHost (or .Values.ssl.tls_secret_name (and .Values.ssl.createIssuer .Values.ssl.generateCerts) (hasPrefix "traefik" (.Values.ingress.ingressClass | default ""))) }}
WEB_URL: "https://{{ .Values.ingress.appHost }}"
Comment on lines +57 to +60

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

render_web_url() {
  helm template review charts/plane-ce \
    --set ingress.appHost=plane.example.com \
    --set ingress.enabled="$1" \
    --set ingress.ingressClass="$2" \
    --set ssl.tls_secret_name="$3" \
    --set ssl.createIssuer="$4" \
    --set ssl.generateCerts="$5" \
    --set env.web_url="" |
    awk '/name: review-app-vars/{in_map=1} in_map && /WEB_URL:/{print; exit}'
}

test "$(render_web_url false traefik "" false false)" = 'WEB_URL: "http://plane.example.com"'
test "$(render_web_url true traefik "" false false)" = 'WEB_URL: "https://plane.example.com"'

Repository: makeplane/helm-charts

Length of output: 197


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate relevant files =="
git ls-files | rg 'charts/plane-ce/(templates/(ingress-traefik|config-secrets/app-env)\.yaml|values\.yaml)$'

echo
echo "== relevant snippets =="
for f in charts/plane-ce/templates/ingress-traefik.yaml charts/plane-ce/templates/config-secrets/app-env.yaml charts/plane-ce/values.yaml; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    wc -l "$f"
    sed -n "1,110p" "$f"
  fi
done

echo
echo "== check helm availability =="
command -v helm || true

Repository: makeplane/helm-charts

Length of output: 9902


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== templates/gate occurrences for ingress.enabled, ssl, and ingressClass =="
rg -n "ingress\.enabled|ingressClass|createIssuer|generateCerts|tls_secret_name|\{\{- *if .*ingress" charts/plane-ce/templates charts/plane-ce/functions 2>/dev/null || true

echo
echo "== all ingress-related templates =="
git ls-files 'charts/plane-ce/templates/*ingress*'
for f in $(git ls-files 'charts/plane-ce/templates/*ingress*' 'charts/plane-ce/templates/config-secrets/app-env.yaml'); do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    wc -l "$f"
    sed -n '1,120p' "$f"
  fi
done

Repository: makeplane/helm-charts

Length of output: 15004


🌐 Web query:

Helm template Go template hasPrefix string function syntax hasPrefix "traefik" .Values.ingress.ingressClass

💡 Result:

In Helm templates, the hasPrefix function is provided by the Sprig library [1][2]. It tests whether a string begins with a specified prefix and returns a boolean value [1][3]. Syntax The function takes two arguments: the prefix to search for, and the string to search within [1][2]. {{ hasPrefix "prefix" "your-string" }} Alternatively, it can be used in a pipeline: {{ "your-string" | hasPrefix "prefix" }} Example with.Values.ingress.ingressClass If you want to check if the ingressClass value starts with a specific string (for example, "traefik-"), you can use it in your Helm template as follows: {{- if hasPrefix "traefik-".Values.ingress.ingressClass }} # Logic to execute if it matches {{- end }} Or using the pipeline style: {{- if.Values.ingress.ingressClass | hasPrefix "traefik-" }} # Logic to execute if it matches {{- end }} Important Notes 1. Availability: Helm templates include Sprig functions by default [1]. You do not need to register them manually, unlike standard Go text/template which does not include this function natively [4][5]. 2. Traefik Context: While Traefik configuration often deals with ingressClass, ensure that the variable you are checking (.Values.ingress.ingressClass) is defined and of type string to avoid template execution errors [6][7]. If the value might be nil or empty, you may want to provide a default value: {{- if.Values.ingress.ingressClass | default "" | hasPrefix "traefik-" }}

Citations:


Gate HTTPS inference on chart-supported TLS/ingress.

charts/plane-ce/templates/config-secrets/app-env.yaml can emit WEB_URL: "https://..." when .Values.ingress.enabled=false and either .Values.ssl.tls_secret_name is set or .Values.ingress.ingressClass starts with traefik. Only charts/plane-ce/templates/ingress-traefik.yaml and the standard ingress use .Values.ingress.enabled, so an external ingress can receive a chart-generated HTTPS URL for a non-chart endpoint.

Require .Values.ingress.enabled or set env.web_url explicitly for external HTTPS routes.

Proposed fix
-  {{- else if and .Values.ingress.appHost (or .Values.ssl.tls_secret_name (and .Values.ssl.createIssuer .Values.ssl.generateCerts) (hasPrefix "traefik" (.Values.ingress.ingressClass | default ""))) }}
+  {{- else if and .Values.ingress.enabled .Values.ingress.appHost (or .Values.ssl.tls_secret_name (and .Values.ssl.createIssuer .Values.ssl.generateCerts) (hasPrefix "traefik" (.Values.ingress.ingressClass | default ""))) }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{{- if .Values.env.web_url }}
WEB_URL: {{ .Values.env.web_url | quote }}
{{- else if and .Values.ingress.appHost (or .Values.ssl.tls_secret_name (and .Values.ssl.createIssuer .Values.ssl.generateCerts) (hasPrefix "traefik" (.Values.ingress.ingressClass | default ""))) }}
WEB_URL: "https://{{ .Values.ingress.appHost }}"
{{- if .Values.env.web_url }}
WEB_URL: {{ .Values.env.web_url | quote }}
{{- else if and .Values.ingress.enabled .Values.ingress.appHost (or .Values.ssl.tls_secret_name (and .Values.ssl.createIssuer .Values.ssl.generateCerts) (hasPrefix "traefik" (.Values.ingress.ingressClass | default ""))) }}
WEB_URL: "https://{{ .Values.ingress.appHost }}"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@charts/plane-ce/templates/config-secrets/app-env.yaml` around lines 57 - 60,
Update the WEB_URL inference condition in the app-env template so the generated
HTTPS URL requires .Values.ingress.enabled in addition to the existing supported
TLS/ingress checks. Keep explicit .Values.env.web_url precedence unchanged,
ensuring external HTTPS routes require that explicit value when chart ingress is
disabled.

{{- else if .Values.ingress.appHost }}
WEB_URL: "http://{{ .Values.ingress.appHost }}"
{{- else }}
WEB_URL: ""
Expand Down
15 changes: 14 additions & 1 deletion charts/plane-ce/templates/ingress-traefik.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,34 @@ spec:

routes:

# IMPORTANT: specific paths FIRST
{{/*
Explicit priorities: Traefik can otherwise prefer the catch-all PathPrefix(`/`)
over more specific routes depending on rule length / middleware side effects.
*/}}

- match: Host(`{{ .Values.ingress.appHost }}`) && PathPrefix(`/spaces`)
kind: Rule
priority: 100
middlewares:
- name: {{ .Release.Name }}-trailing-slash
- name: {{ .Release.Name }}-body-limit
services:
- name: {{ .Release.Name }}-space
port: 3000

- match: Host(`{{ .Values.ingress.appHost }}`) && PathPrefix(`/god-mode`)
kind: Rule
priority: 100
middlewares:
- name: {{ .Release.Name }}-trailing-slash
- name: {{ .Release.Name }}-body-limit
services:
- name: {{ .Release.Name }}-admin
port: 3000

- match: Host(`{{ .Values.ingress.appHost }}`) && PathPrefix(`/api`)
kind: Rule
priority: 100
middlewares:
- name: {{ .Release.Name }}-body-limit
services:
Expand All @@ -41,6 +49,7 @@ spec:

- match: Host(`{{ .Values.ingress.appHost }}`) && PathPrefix(`/auth`)
kind: Rule
priority: 100
middlewares:
- name: {{ .Release.Name }}-body-limit
services:
Expand All @@ -49,7 +58,9 @@ spec:

- match: Host(`{{ .Values.ingress.appHost }}`) && PathPrefix(`/live`)
kind: Rule
priority: 100
middlewares:
- name: {{ .Release.Name }}-trailing-slash
- name: {{ .Release.Name }}-body-limit
services:
- name: {{ .Release.Name }}-live
Expand All @@ -58,6 +69,7 @@ spec:
{{- if and .Values.minio.local_setup .Values.env.docstore_bucket }}
- match: Host(`{{ .Values.ingress.appHost }}`) && PathPrefix(`/{{ .Values.env.docstore_bucket }}`)
kind: Rule
priority: 100
middlewares:
- name: {{ .Release.Name }}-body-limit
services:
Expand All @@ -68,6 +80,7 @@ spec:
# LAST: catch all
- match: Host(`{{ .Values.ingress.appHost }}`) && PathPrefix(`/`)
kind: Rule
priority: 1
middlewares:
- name: {{ .Release.Name }}-body-limit
services:
Expand Down
21 changes: 20 additions & 1 deletion charts/plane-ce/templates/traefik-middleware.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,24 @@ metadata:
spec:
buffering:
maxRequestBodyBytes: {{ .Values.ingress.traefik.maxRequestBodyBytes | default 5242880 }}

---
{{/*
Admin/space/live SPAs use React Router basenames with a trailing slash
(e.g. /god-mode/). Without a client-visible redirect, /god-mode serves HTML
but the router refuses to match. Use a temporary (302) redirect so browsers
do not permanently cache a bad Location (e.g. with the container port).
*/}}
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: {{ .Release.Name }}-trailing-slash
labels:
{{- include "plane.commonLabels" $ | nindent 4 }}
namespace: {{ .Release.Namespace }}
spec:
redirectRegex:
# Match full request URL; keep host/scheme, add trailing slash.
regex: "^(https://[^/]+)/(god-mode|spaces|live)$"
replacement: "${1}/${2}/"
Comment on lines +28 to +30

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

base="${BASE_URL%/}"

for path in /god-mode /spaces /live; do
  headers="$(curl -ksS -D - -o /dev/null --max-redirs 0 "$base${path}?probe=1")"
  status="$(printf '%s\n' "$headers" | awk 'NR == 1 {print $2}')"
  location="$(printf '%s\n' "$headers" | awk -F': ' 'tolower($1) == "location" {sub("\r$", "", $2); print $2; exit}')"

  test "$status" = "302"
  test "$location" = "$base${path}/?probe=1"
done

Repository: makeplane/helm-charts

Length of output: 200


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== candidate files =="
git ls-files | rg '(^|/)traefik-middleware\.yaml$|traefik|middleware' || true

echo "== file excerpt =="
if [ -f charts/plane-ce/templates/traefik-middleware.yaml ]; then
  cat -n charts/plane-ce/templates/traefik-middleware.yaml | sed -n '1,120p'
fi

echo "== route references =="
rg -n "god-mode|spaces|live|RedirectRegex|redirect" charts/plane-ce/templates || true

Repository: makeplane/helm-charts

Length of output: 6429


🌐 Web query:

Traefik RedirectRegex request URL RequestURI include query string documentation

💡 Result:

In Traefik, the RedirectRegex middleware performs redirection based on regex matching and replacement [1][2]. Regarding the inclusion of query strings, the behavior depends on the version and implementation details of how the redirect is handled: 1. Query String Handling: By default, the RedirectRegex middleware does not automatically append the original request's query string to the new location if the replacement URL is treated as an absolute URL [3][4]. When a redirect occurs, the final destination is determined by the replacement string provided in your configuration [1][5]. 2. Recent Improvements: Traefik has addressed issues where full URL replacements would drop the incoming query string [4]. In updated versions, logic has been introduced to carry over the original request's query string if the replacement URL does not already contain a query string [4]. 3. Implementation Notes: - Regex Capturing: The regex option is used to match and capture elements from the request URL, and the replacement option defines the new target URL [1][5]. - Variable Expansion: Use the ${1} syntax in the replacement field to reference captured groups [1][5]. When defining these in YAML, remember that certain characters might need double escaping [1][5]. - Security: Historically, there was an issue where query parameters could be incorrectly evaluated as template functions; this has been patched to ensure the middleware does not attempt to execute template logic from URL parameters [6]. If you find that your query strings are being dropped, ensure you are using a recent version of Traefik, as fixes for absolute-URL redirection parity with tools like NGINX have been implemented [4]. If the query string is not preserved automatically, you may need to explicitly include the query parameters in your regex/replacement logic or verify that your specific routing configuration allows for query string passthrough [3][4].

Citations:


Preserve query parameters in the trailing-slash redirect.

The middleware regex must match /god-mode?next=%2F, /spaces?probe=1, and /live?x=1. If the query string is captured as an optional group, include it in the replacement so the Location still carries the original parameters after the added trailing slash.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@charts/plane-ce/templates/traefik-middleware.yaml` around lines 28 - 30,
Update the Traefik middleware regex to match optional query strings for the
god-mode, spaces, and live paths, capture those parameters, and include the
captured query portion in the replacement after adding the trailing slash so
redirects preserve the original parameters.

Source: MCP tools

permanent: false
{{- end }}
7 changes: 7 additions & 0 deletions charts/plane-ce/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,13 @@ external_secrets:

env:

# Public app URL (optional). When set, used as WEB_URL as-is (include scheme).
# Example: "https://plane.example.com"
# If empty, WEB_URL is derived from ingress.appHost:
# https when TLS is configured (ssl.tls_secret_name, cert-manager certs, or Traefik ingress),
# otherwise http.
web_url: ""

# NEXT_PUBLIC_DEPLOY_URL: ""
# REDIS
remote_redis_url: "" #INCASE OF REMOTE REDIS ONLY
Expand Down