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-enterprise/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Meet Plane. An Enterprise software development tool to manage issue

type: application

version: 2.6.1
version: 2.7.0
appVersion: "2.6.3"

home: https://plane.so/
Expand Down
73 changes: 73 additions & 0 deletions charts/plane-enterprise/questions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1505,3 +1505,76 @@ questions:
type: string
default: ""
group: "External Secrets"

- variable: observability.otel.enabled
label: "Enable OpenTelemetry"
description: "Export traces, logs and metrics from the backend services over OTLP. Off by default."
type: boolean
default: false
group: "OpenTelemetry"
show_subquestion_if: true
subquestions:
- variable: observability.otel.endpoint
label: "OTLP Endpoint"
description: "OTLP collector endpoint. An https:// endpoint uses secure gRPC."
type: string
default: ""
- variable: observability.otel.protocol
label: "OTLP Protocol"
type: enum
options:
- "grpc"
- "http/protobuf"
default: "grpc"
- variable: observability.otel.headers
label: "OTLP Exporter Headers"
description: "Extra OTLP exporter headers as k1=v1,k2=v2 (e.g. a collector ingestion/auth key)."
type: string
default: ""
- variable: observability.otel.environment
label: "Deployment Environment"
description: "Emitted as deployment.environment.name (e.g. prod, staging). Use for cross-service environment filtering."
type: string
default: ""
- variable: observability.otel.resourceAttributes
label: "Extra Resource Attributes"
description: "Additional OTel resource attributes as k1=v1,k2=v2 (merged after environment)."
type: string
default: ""
- variable: observability.otel.sampler
label: "Trace Sampler"
description: "always_on captures every trace (recommended for test/debug). In production use parentbased_traceidratio; unlike always_on it honours an upstream traceparent's sampling decision."
type: enum
options:
- "always_on"
- "parentbased_traceidratio"
- "traceidratio"
- "always_off"
default: "always_on"
- variable: observability.otel.samplerArg
label: "Trace Sampler Ratio"
description: "Sampling ratio 0.0-1.0 for ratio-based samplers. Ignored by always_on."
type: string
default: "1.0"
show_if: "observability.otel.sampler=parentbased_traceidratio || observability.otel.sampler=traceidratio"
- variable: observability.otel.debugConsole
label: "Print Spans to Stdout (debug)"
type: boolean
default: false
- variable: observability.otel.frontend.enabled
label: "Enable Browser Tracing"
description: "Browser tracing for web/admin/space, served to browsers by the API."
type: boolean
default: false
- variable: observability.otel.frontend.endpoint
label: "Browser OTLP Endpoint"
description: "Public OTLP/HTTP endpoint the browser posts to (must be internet-reachable and CORS-enabled)."
type: string
default: ""
show_if: "observability.otel.frontend.enabled=true"
- variable: observability.otel.frontend.headers
label: "Browser OTLP Headers"
description: "A non-empty value forces the browser exporter onto XHR (required cross-origin; sendBeacon fails CORS)."
type: string
default: "x-otlp-browser=1"
show_if: "observability.otel.frontend.enabled=true"
32 changes: 32 additions & 0 deletions charts/plane-enterprise/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,35 @@ Caller must nindent to the correct depth.
value: "/ca-bundle/custom-ca-bundle.crt"
{{- end }}
{{- end -}}

{{/*
OpenTelemetry — returns "true" when observability.otel.enabled is set, else "".
*/}}
{{- define "plane.otel.enabled" -}}
{{- if and .Values.observability .Values.observability.otel .Values.observability.otel.enabled -}}true{{- end -}}
{{- end -}}

{{/*
envFrom entry for the shared OTEL ConfigMap. Call with the root context and
nindent to the envFrom list depth, e.g.
{{- include "plane.otel.envFrom" $ | nindent 10 }}
*/}}
{{- define "plane.otel.envFrom" -}}
{{- if eq (include "plane.otel.enabled" .) "true" -}}
- configMapRef:
name: {{ .Release.Name }}-otel-vars
optional: false
{{- end -}}
{{- end -}}

{{/*
Per-workload OTEL_SERVICE_NAME (overrides the shared ConfigMap so each workload
reports its own service.name). Call with a dict and nindent, e.g.
{{- include "plane.otel.serviceEnv" (dict "ctx" $ "service" "api") | nindent 10 }}
*/}}
{{- define "plane.otel.serviceEnv" -}}
{{- if eq (include "plane.otel.enabled" .ctx) "true" -}}
- name: OTEL_SERVICE_NAME
value: {{ .service | quote }}
{{- end -}}
{{- end -}}
42 changes: 42 additions & 0 deletions charts/plane-enterprise/templates/config-secrets/otel.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{{- if eq (include "plane.otel.enabled" .) "true" }}
# Shared OpenTelemetry env for the backend workloads. Mounted via envFrom; each
# workload additionally sets an inline OTEL_SERVICE_NAME (see plane.otel.serviceEnv).
apiVersion: v1
kind: ConfigMap
metadata:
namespace: {{ .Release.Namespace }}
name: {{ .Release.Name }}-otel-vars
data:
OTEL_ENABLED: "1"
{{- with .Values.observability.otel.endpoint }}
OTEL_EXPORTER_OTLP_ENDPOINT: {{ . | quote }}
{{- end }}
OTEL_EXPORTER_OTLP_PROTOCOL: {{ .Values.observability.otel.protocol | default "grpc" | quote }}
{{- with .Values.observability.otel.headers }}
OTEL_EXPORTER_OTLP_HEADERS: {{ . | quote }}
{{- end }}
Comment on lines +15 to +17

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.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Move OTLP auth headers out of ConfigMap

OTEL_EXPORTER_OTLP_HEADERS can carry ingestion credentials, but this template stores it in a ConfigMap. That weakens secret handling and broadens accidental exposure. Use a Secret for sensitive OTEL header values and mount via secretRef instead.

🤖 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-enterprise/templates/config-secrets/otel.yaml` around lines 15 -
17, The OTEL_EXPORTER_OTLP_HEADERS field containing authentication credentials
is currently stored in a ConfigMap, which is not encrypted and poses a security
risk. Remove the OTEL_EXPORTER_OTLP_HEADERS variable from the otel.yaml
ConfigMap template and instead create a separate Secret resource to store
sensitive OTEL header values. Update the pod specification to mount these values
from the Secret using secretRef instead of configMapRef, ensuring the ingestion
credentials are properly protected.

{{- $parts := list }}
{{- with .Values.observability.otel.environment }}{{- $parts = append $parts (printf "deployment.environment.name=%s" .) }}{{- end }}
{{- with .Values.observability.otel.resourceAttributes }}{{- $parts = append $parts . }}{{- end }}
{{- if $parts }}
OTEL_RESOURCE_ATTRIBUTES: {{ join "," $parts | quote }}
{{- end }}
{{- if .Values.observability.otel.debugConsole }}
OTEL_DEBUG_CONSOLE: "1"
{{- end }}
{{- with .Values.observability.otel.sampler }}
OTEL_TRACES_SAMPLER: {{ . | quote }}
{{- end }}
{{- with .Values.observability.otel.samplerArg }}
OTEL_TRACES_SAMPLER_ARG: {{ . | quote }}
{{- end }}
{{- if .Values.observability.otel.frontend.enabled }}
FRONTEND_OTEL_ENABLED: "1"
{{- with .Values.observability.otel.frontend.endpoint }}
FRONTEND_OTLP_ENDPOINT: {{ . | quote }}
{{- end }}
Comment on lines +33 to +37

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Frontend OTEL enablement is missing endpoint gating

Line 33 enables frontend OTEL based only on frontend.enabled, but the values contract says it should turn on only when both enabled and endpoint are set. This can emit FRONTEND_OTEL_ENABLED without a usable endpoint and produce broken browser exporter config.

Suggested template fix
-  {{- if .Values.observability.otel.frontend.enabled }}
+  {{- if and .Values.observability.otel.frontend.enabled .Values.observability.otel.frontend.endpoint }}
   FRONTEND_OTEL_ENABLED: "1"
   {{- with .Values.observability.otel.frontend.endpoint }}
   FRONTEND_OTLP_ENDPOINT: {{ . | quote }}
   {{- end }}
📝 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.observability.otel.frontend.enabled }}
FRONTEND_OTEL_ENABLED: "1"
{{- with .Values.observability.otel.frontend.endpoint }}
FRONTEND_OTLP_ENDPOINT: {{ . | quote }}
{{- end }}
{{- if and .Values.observability.otel.frontend.enabled .Values.observability.otel.frontend.endpoint }}
FRONTEND_OTEL_ENABLED: "1"
{{- with .Values.observability.otel.frontend.endpoint }}
FRONTEND_OTLP_ENDPOINT: {{ . | quote }}
{{- end }}
🤖 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-enterprise/templates/config-secrets/otel.yaml` around lines 33 -
37, The FRONTEND_OTEL_ENABLED configuration variable is currently being set to
"1" based only on the frontend.enabled flag, but according to the values
contract it should only be enabled when both frontend.enabled is true AND
frontend.endpoint is set. Update the conditional check at line 33 to verify both
conditions are met by combining the check for
observability.otel.frontend.enabled with a check that
observability.otel.frontend.endpoint is also defined and not empty before
setting FRONTEND_OTEL_ENABLED to "1".

{{- with .Values.observability.otel.frontend.headers }}
FRONTEND_OTLP_HEADERS: {{ . | quote }}
{{- end }}
{{- end }}
{{- end }}
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,14 @@ spec:
name: {{ if not (empty .Values.external_secrets.silo_env_existingSecret) }}{{ .Values.external_secrets.silo_env_existingSecret }}{{ else }}{{ .Release.Name }}-silo-secrets{{ end }}
optional: false
{{- end }}
{{- include "plane.otel.envFrom" $ | nindent 10 }}

{{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) }}
{{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) (eq (include "plane.otel.enabled" .) "true") }}
env:
{{- with (include "plane.s3CAEnvVars" .) }}
{{ . | indent 10 }}
{{- end }}
{{- include "plane.otel.serviceEnv" (dict "ctx" $ "service" "api") | nindent 10 }}
{{- if .Values.extraEnv }}
{{- toYaml .Values.extraEnv | nindent 10 }}
{{- end }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,13 @@ spec:
- secretRef:
name: {{ if not (empty .Values.external_secrets.opensearch_existingSecret) }}{{ .Values.external_secrets.opensearch_existingSecret }}{{ else }}{{ .Release.Name }}-opensearch-secrets{{ end }}
optional: false
{{- if .Values.extraEnv }}
{{- include "plane.otel.envFrom" $ | nindent 10 }}
{{- if or .Values.extraEnv (eq (include "plane.otel.enabled" .) "true") }}
env:
{{- include "plane.otel.serviceEnv" (dict "ctx" $ "service" "automation-consumer") | nindent 10 }}
{{- if .Values.extraEnv }}
{{- toYaml .Values.extraEnv | nindent 10 }}
{{- end }}
{{- end }}

serviceAccount: {{ .Release.Name }}-srv-account
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,13 @@ spec:
name: {{ if not (empty .Values.external_secrets.silo_env_existingSecret) }}{{ .Values.external_secrets.silo_env_existingSecret }}{{ else }}{{ .Release.Name }}-silo-secrets{{ end }}
optional: false
{{- end }}
{{- if .Values.extraEnv }}
{{- include "plane.otel.envFrom" $ | nindent 10 }}
{{- if or .Values.extraEnv (eq (include "plane.otel.enabled" .) "true") }}
env:
{{- include "plane.otel.serviceEnv" (dict "ctx" $ "service" "beat-worker") | nindent 10 }}
{{- if .Values.extraEnv }}
{{- toYaml .Values.extraEnv | nindent 10 }}
{{- end }}
{{- end }}

serviceAccount: {{ .Release.Name }}-srv-account
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,13 @@ spec:
- secretRef:
name: {{ if not (empty .Values.external_secrets.live_env_existingSecret) }}{{ .Values.external_secrets.live_env_existingSecret }}{{ else }}{{ .Release.Name }}-live-secrets{{ end }}
optional: false
{{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) }}
{{- include "plane.otel.envFrom" $ | nindent 10 }}
{{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) (eq (include "plane.otel.enabled" .) "true") }}
env:
{{- with (include "plane.s3CANodeEnvVars" .) }}
{{ . | indent 10 }}
{{- end }}
{{- include "plane.otel.serviceEnv" (dict "ctx" $ "service" "live") | nindent 10 }}
{{- if .Values.extraEnv }}
{{- toYaml .Values.extraEnv | nindent 10 }}
{{- end }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,13 @@ spec:
- secretRef:
name: {{ if not (empty .Values.external_secrets.app_env_existingSecret) }}{{ .Values.external_secrets.app_env_existingSecret }}{{ else }}{{ .Release.Name }}-app-secrets{{ end }}
optional: false
{{- if .Values.extraEnv }}
{{- include "plane.otel.envFrom" $ | nindent 10 }}
{{- if or .Values.extraEnv (eq (include "plane.otel.enabled" .) "true") }}
env:
{{- include "plane.otel.serviceEnv" (dict "ctx" $ "service" "outbox-poller") | nindent 10 }}
{{- if .Values.extraEnv }}
{{- toYaml .Values.extraEnv | nindent 10 }}
{{- end }}
{{- end }}

serviceAccount: {{ .Release.Name }}-srv-account
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,13 @@ spec:
- secretRef:
name: {{ if not (empty .Values.external_secrets.opensearch_existingSecret) }}{{ .Values.external_secrets.opensearch_existingSecret }}{{ else }}{{ .Release.Name }}-opensearch-secrets{{ end }}
optional: false
{{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) }}
{{- include "plane.otel.envFrom" $ | nindent 10 }}
{{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) (eq (include "plane.otel.enabled" .) "true") }}
env:
{{- with (include "plane.s3CAEnvVars" .) }}
{{ . | indent 10 }}
{{- end }}
{{- include "plane.otel.serviceEnv" (dict "ctx" $ "service" "pi-api") | nindent 10 }}
{{- if .Values.extraEnv }}
{{- toYaml .Values.extraEnv | nindent 10 }}
{{- end }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,13 @@ spec:
- secretRef:
name: {{ if not (empty .Values.external_secrets.opensearch_existingSecret) }}{{ .Values.external_secrets.opensearch_existingSecret }}{{ else }}{{ .Release.Name }}-opensearch-secrets{{ end }}
optional: false
{{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) }}
{{- include "plane.otel.envFrom" $ | nindent 10 }}
{{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) (eq (include "plane.otel.enabled" .) "true") }}
env:
{{- with (include "plane.s3CAEnvVars" .) }}
{{ . | indent 10 }}
{{- end }}
{{- include "plane.otel.serviceEnv" (dict "ctx" $ "service" "pi-beat") | nindent 10 }}
{{- if .Values.extraEnv }}
{{- toYaml .Values.extraEnv | nindent 10 }}
{{- end }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,13 @@ spec:
- secretRef:
name: {{ if not (empty .Values.external_secrets.opensearch_existingSecret) }}{{ .Values.external_secrets.opensearch_existingSecret }}{{ else }}{{ .Release.Name }}-opensearch-secrets{{ end }}
optional: false
{{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) }}
{{- include "plane.otel.envFrom" $ | nindent 10 }}
{{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) (eq (include "plane.otel.enabled" .) "true") }}
env:
{{- with (include "plane.s3CAEnvVars" .) }}
{{ . | indent 10 }}
{{- end }}
{{- include "plane.otel.serviceEnv" (dict "ctx" $ "service" "pi-worker") | nindent 10 }}
{{- if .Values.extraEnv }}
{{- toYaml .Values.extraEnv | nindent 10 }}
{{- end }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,13 @@ spec:
- secretRef:
name: {{ if not (empty .Values.external_secrets.doc_store_existingSecret) }}{{ .Values.external_secrets.doc_store_existingSecret }}{{ else }}{{ .Release.Name }}-doc-store-secrets{{ end }}
optional: false
{{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) }}
{{- include "plane.otel.envFrom" $ | nindent 10 }}
{{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) (eq (include "plane.otel.enabled" .) "true") }}
env:
{{- with (include "plane.s3CANodeEnvVars" .) }}
{{ . | indent 10 }}
{{- end }}
{{- include "plane.otel.serviceEnv" (dict "ctx" $ "service" "silo") | nindent 10 }}
{{- if .Values.extraEnv }}
{{- toYaml .Values.extraEnv | nindent 10 }}
{{- end }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,16 @@ spec:
limits:
memory: {{ .Values.services.space.memoryLimit | default "1000Mi" | quote }}
cpu: {{ .Values.services.space.cpuLimit | default "500m" | quote}}
{{- if .Values.extraEnv }}
{{- if eq (include "plane.otel.enabled" .) "true" }}
envFrom:
{{- include "plane.otel.envFrom" $ | nindent 10 }}
{{- end }}
{{- if or .Values.extraEnv (eq (include "plane.otel.enabled" .) "true") }}
env:
{{- include "plane.otel.serviceEnv" (dict "ctx" $ "service" "space") | nindent 10 }}
{{- if .Values.extraEnv }}
{{- toYaml .Values.extraEnv | nindent 10 }}
{{- end }}
{{- end }}
serviceAccount: {{ .Release.Name }}-srv-account
serviceAccountName: {{ .Release.Name }}-srv-account
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,14 @@ spec:
name: {{ if not (empty .Values.external_secrets.silo_env_existingSecret) }}{{ .Values.external_secrets.silo_env_existingSecret }}{{ else }}{{ .Release.Name }}-silo-secrets{{ end }}
optional: false
{{- end }}
{{- include "plane.otel.envFrom" $ | nindent 10 }}

{{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) }}
{{- if or .Values.extraEnv (include "plane.s3CAEnabled" .) (eq (include "plane.otel.enabled" .) "true") }}
env:
{{- with (include "plane.s3CAEnvVars" .) }}
{{ . | indent 10 }}
{{- end }}
{{- include "plane.otel.serviceEnv" (dict "ctx" $ "service" "worker") | nindent 10 }}
{{- if .Values.extraEnv }}
{{- toYaml .Values.extraEnv | nindent 10 }}
{{- end }}
Expand Down
43 changes: 43 additions & 0 deletions charts/plane-enterprise/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -636,3 +636,46 @@ extraEnv: []
# value: "http://proxy.example.com:8080"
# - name: NO_PROXY
# value: "localhost,127.0.0.1,.example.com"

# OpenTelemetry (traces + logs + metrics). Off by default. When enabled, a shared
# ConfigMap (<release>-otel-vars) is mounted via envFrom into the backend
# workloads (api, worker, beat-worker, automation-consumer, outbox-poller, silo,
# live, space, pi-api, pi-beat, pi-worker), and each gets an inline
# OTEL_SERVICE_NAME so it reports its own service.name. web/admin are NOT wired:
# their only OTel is browser tracing, configured from the API's instance config
# via the frontend.* keys below — not per-workload env.
observability:
otel:
enabled: false
# OTLP collector endpoint. An https:// endpoint => secure gRPC.
endpoint: ''
protocol: grpc
# Extra OTLP exporter headers, "k1=v1,k2=v2" (e.g. a collector ingestion key).
headers: ''
# Deployment environment tag. Emitted as deployment.environment.name (the
# CURRENT semconv key — matches the node/pi/browser services for cross-service
# filtering). Leave blank to omit.
environment: ''
# Any additional resource attributes, "k1=v1,k2=v2" (merged after environment).
resourceAttributes: ''
# Print spans to stdout (debug only).
debugConsole: false
# Trace sampler. always_on = export every span the service sees and, unlike
# parentbased_*, does NOT defer to an upstream traceparent's sampled flag — so
# browser-initiated POST/user-action traces aren't silently dropped (the "only
# GET shows up" symptom). For prod use parentbased_traceidratio with a ratio
# AND ensure browser tracing samples consistently. samplerArg is ignored by
# always_on.
sampler: always_on
samplerArg: '1.0'
# Browser/client tracing for web/admin/space — served to browsers by the API
# (read only by the API container). Turns on when enabled AND endpoint is set.
frontend:
enabled: false
# PUBLIC OTLP/HTTP endpoint the browser posts to (must be internet-reachable
# and CORS-enabled for the Plane web origin; the client appends /v1/traces).
endpoint: ''
# A non-empty header is REQUIRED cross-origin: it forces the web exporter
# onto XHR instead of navigator.sendBeacon (sendBeacon sends credentials,
# which CORS rejects against a wildcard ACAO). Value is arbitrary.
headers: 'x-otlp-browser=1'