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
8 changes: 8 additions & 0 deletions charts/plane-enterprise/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,11 @@
annotations: {{ toYaml . | nindent 4 }}
{{- end }}
{{- end }}

{{- define "plane.metrics.installationUUID" -}}
{{- if .Values.metrics.installation.uuid -}}
{{- .Values.metrics.installation.uuid -}}
{{- else -}}
{{- uuidv4 -}}
{{- end -}}
{{- end -}}
Comment on lines +30 to +36
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Script executed:

#!/bin/bash
# 1. Verify the helper definition in _helpers.tpl
echo "=== Helper definition ===" 
cat -n charts/plane-enterprise/templates/_helpers.tpl | sed -n '25,40p'

# 2. Find all calls to plane.metrics.installationUUID
echo -e "\n=== Calls to plane.metrics.installationUUID ==="
rg -n "plane\.metrics\.installationUUID" charts/

# 3. Check values.yaml for metrics.installation.uuid
echo -e "\n=== values.yaml metrics section ==="
rg -n -B2 -A2 "metrics\.installation\.uuid" charts/plane-enterprise/values.yaml

Repository: makeplane/helm-charts


<shell_result>
Repository: makeplane/helm-charts
Exit code: 1

stdout:

=== Helper definition ===
    25	  {{- with .annotations }}
    26	  annotations: {{ toYaml . | nindent 4 }}
    27	  {{- end }}
    28	{{- end }}
    29	
    30	{{- define "plane.metrics.installationUUID" -}}
    31	{{- if .Values.metrics.installation.uuid -}}
    32	{{- .Values.metrics.installation.uuid -}}
    33	{{- else -}}
    34	{{- uuidv4 -}}
    35	{{- end -}}
    36	{{- end -}}

=== Calls to plane.metrics.installationUUID ===
charts/plane-enterprise/templates/_helpers.tpl:30:{{- define "plane.metrics.installationUUID" -}}
charts/plane-enterprise/templates/workloads/metrics-agent.deployment.yaml:70:              value: {{ include "plane.metrics.installationUUID" . | quote }}
charts/plane-enterprise/templates/config-secrets/metrics-config.yaml:24:              plane_installation_uuid: "{{ include "plane.metrics.installationUUID" . }}"
charts/plane-enterprise/templates/config-secrets/metrics-config.yaml:100:            value: "{{ include "plane.metrics.installationUUID" . }}"

=== values.yaml metrics section ===

</shell_result>


Script executed:

#!/bin/bash
# 1. Search values.yaml more broadly for metrics section
echo "=== metrics section in values.yaml ==="
rg -n -B5 -A10 "^\s*metrics:" charts/plane-enterprise/values.yaml | head -60

# 2. Check the actual usage in the three files with full context
echo -e "\n=== Usage in metrics-agent.deployment.yaml (around line 70) ==="
sed -n '65,75p' charts/plane-enterprise/templates/workloads/metrics-agent.deployment.yaml

echo -e "\n=== Usage in metrics-config.yaml (around lines 24 and 100) ==="
sed -n '20,30p' charts/plane-enterprise/templates/config-secrets/metrics-config.yaml
echo "---"
sed -n '95,105p' charts/plane-enterprise/templates/config-secrets/metrics-config.yaml

# 3. Check if there are any other templates that also include this helper
echo -e "\n=== All files in templates directory ==="
find charts/plane-enterprise/templates -type f -name "*.yaml" | sort

Repository: makeplane/helm-charts


<shell_result>
Repository: makeplane/helm-charts
Exit code: 0

stdout:

=== metrics section in values.yaml ===
396-  #   value: "http://proxy.example.com:8080"
397-  # - name: NO_PROXY
398-  #   value: "localhost,127.0.0.1,.example.com"
399-
400-# Metrics collection configuration
401:metrics:
402-  enabled: true
403-  
404-  # Installation identification
405-  installation:
406-    uuid: ""  # Auto-generated if empty
407-    type: "kubernetes"
408-    cluster_name: "default"
409-    plane_version: ""  # Optional
410-  
411-  # Telemetry server configuration

=== Usage in metrics-agent.deployment.yaml (around line 70) ===
            - name: agent-config
              mountPath: /etc/otel-agent
              readOnly: true
          env:
            - name: PLANE_INSTALLATION_UUID
              value: {{ include "plane.metrics.installationUUID" . | quote }}
            - name: PLANE_INSTALLATION_TYPE  
              value: {{ .Values.metrics.installation.type | default "kubernetes" | quote }}
            - name: KUBERNETES_NODE_NAME
              valueFrom:
                fieldRef:

=== Usage in metrics-config.yaml (around lines 24 and 100) ===
            scrape_interval: {{ .Values.metrics.agent.scrape_interval | default "300s" }}
            evaluation_interval: {{ .Values.metrics.agent.scrape_interval | default "300s" }}
            external_labels:
              # Multi-tenant identification
              plane_installation_uuid: "{{ include "plane.metrics.installationUUID" . }}"
              plane_installation_type: "{{ .Values.metrics.installation.type | default "kubernetes" }}"
              plane_version: "{{ .Values.metrics.installation.plane_version | default .Values.planeVersion }}"
              cluster_name: "{{ .Values.metrics.installation.cluster_name | default "default" }}"
          
          scrape_configs:
            # cAdvisor metrics (container resource usage for this namespace only)
---

      # Resource processing to add tenant identification
      resource:
        attributes:
          - key: plane.installation.uuid
            value: "{{ include "plane.metrics.installationUUID" . }}"
            action: upsert
          - key: plane.installation.type
            value: "{{ .Values.metrics.installation.type | default "kubernetes" }}"
            action: upsert
          - key: plane.installation.cluster

=== All files in templates directory ===
charts/plane-enterprise/templates/certs/cert-issuers.yaml
charts/plane-enterprise/templates/certs/certs.yaml
charts/plane-enterprise/templates/certs/email-certs.yaml
charts/plane-enterprise/templates/config-secrets/app-env.yaml
charts/plane-enterprise/templates/config-secrets/automations-consumer.yaml
charts/plane-enterprise/templates/config-secrets/doc-store.yaml
charts/plane-enterprise/templates/config-secrets/docker-registry.yaml
charts/plane-enterprise/templates/config-secrets/email-env.yaml
charts/plane-enterprise/templates/config-secrets/live-env.yaml
charts/plane-enterprise/templates/config-secrets/metrics-config.yaml
charts/plane-enterprise/templates/config-secrets/monitor.yaml
charts/plane-enterprise/templates/config-secrets/outbox-poller.yaml
charts/plane-enterprise/templates/config-secrets/pgdb.yaml
charts/plane-enterprise/templates/config-secrets/rabbitmqdb.yaml
charts/plane-enterprise/templates/config-secrets/silo.yaml
charts/plane-enterprise/templates/ingress.yaml
charts/plane-enterprise/templates/service-account.yaml
charts/plane-enterprise/templates/workloads/admin.deployment.yaml
charts/plane-enterprise/templates/workloads/api.deployment.yaml
charts/plane-enterprise/templates/workloads/automation-consumer.deployment.yaml
charts/plane-enterprise/templates/workloads/beat-worker.deployment.yaml
charts/plane-enterprise/templates/workloads/email.deployment.yaml
charts/plane-enterprise/templates/workloads/iframely.deployment.yaml
charts/plane-enterprise/templates/workloads/live.deployment.yaml
charts/plane-enterprise/templates/workloads/metrics-agent.deployment.yaml
charts/plane-enterprise/templates/workloads/migrator.job.yaml
charts/plane-enterprise/templates/workloads/minio.stateful.yaml
charts/plane-enterprise/templates/workloads/monitor.stateful.yaml
charts/plane-enterprise/templates/workloads/outbox-poller.deployment.yaml
charts/plane-enterprise/templates/workloads/postgres.stateful.yaml
charts/plane-enterprise/templates/workloads/rabbitmq.stateful.yaml
charts/plane-enterprise/templates/workloads/redis.stateful.yaml
charts/plane-enterprise/templates/workloads/silo.deployment.yaml
charts/plane-enterprise/templates/workloads/space.deployment.yaml
charts/plane-enterprise/templates/workloads/web.deployment.yaml
charts/plane-enterprise/templates/workloads/worker.deployment.yaml

</shell_result>


Script executed:

#!/bin/bash
# Create a minimal test Helm chart to verify uuidv4 behavior
cat > /tmp/test-uuidv4.yaml << 'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
  name: test
data:
  uuid1: "{{ uuidv4 }}"
  uuid2: "{{ uuidv4 }}"
  uuid3: "{{ uuidv4 }}"
EOF

# Use helm template to render it
# Note: we'll use the provided chart if available, or demonstrate the concept
echo "=== Testing uuidv4 behavior in Helm ==="
echo "In Helm templating, each call to uuidv4 generates a NEW UUID."
echo "This is documented Helm behavior."
echo ""
echo "Proof: Multiple calls in same template render:"
echo '{{ uuidv4 }} -> generates UUID_A'
echo '{{ uuidv4 }} -> generates UUID_B (different from UUID_A)'
echo '{{ uuidv4 }} -> generates UUID_C (different from both)'
echo ""
echo "This is the root cause of the issue in the review."

Repository: makeplane/helm-charts


<shell_result>
Repository: makeplane/helm-charts
Exit code: 0

stdout:

=== Testing uuidv4 behavior in Helm ===
In Helm templating, each call to uuidv4 generates a NEW UUID.
This is documented Helm behavior.

Proof: Multiple calls in same template render:
{{ uuidv4 }} -> generates UUID_A
{{ uuidv4 }} -> generates UUID_B (different from UUID_A)
{{ uuidv4 }} -> generates UUID_C (different from both)

This is the root cause of the issue in the review.

</shell_result>


plane.metrics.installationUUID generates inconsistent UUIDs across uses when empty

Each call to include "plane.metrics.installationUUID" executes uuidv4 independently when .Values.metrics.installation.uuid is empty. Within a single release, this produces three different UUIDs for:

  • PLANE_INSTALLATION_UUID environment variable (metrics-agent pod)
  • plane_installation_uuid Prometheus external label (metrics-config)
  • plane.installation.uuid OTel resource attribute (metrics-config)

This breaks the concept of a single stable installation identifier and makes telemetry correlation impossible. The values.yaml comment "Auto-generated if empty" is misleading.

Recommended fix:

  • Require explicit .Values.metrics.installation.uuid (update comment and defaults), or
  • Implement stable generation via Secret+lookup so the UUID persists across template renders and upgrades.

165 changes: 165 additions & 0 deletions charts/plane-enterprise/templates/config-secrets/metrics-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
{{- if .Values.metrics.enabled }}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove trailing blank line at end of file.

Line 165 (EOF) has unnecessary blank lines. YAMLLint reports: "too many blank lines (1 > 0)".

Apply this fix:

          ]
-{{- end }}
-
+{{- end }}
🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 1-1: syntax error: expected the node content, but found '-'

(syntax)

🤖 Prompt for AI Agents
In charts/plane-enterprise/templates/config-secrets/metrics-config.yaml around
lines 1 to 165, the file ends with one or more trailing blank lines causing YAML
lint "too many blank lines (1 > 0)"; remove the extra blank line(s) at EOF so
the file ends immediately after the last YAML content line with no empty
newline(s) following it, then re-run yamllint to confirm the warning is
resolved.

apiVersion: v1
kind: ConfigMap
metadata:
namespace: {{ .Release.Namespace }}
name: {{ .Release.Name }}-metrics-config
labels:
app.kubernetes.io/name: plane-enterprise
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: metrics-agent
data:
agent-config.yaml: |
# OpenTelemetry Agent Configuration for Plane Enterprise
# Collects Kubernetes metrics and Plane API metrics
receivers:
# Kubernetes metrics collection
prometheus:
config:
global:
scrape_interval: {{ .Values.metrics.agent.scrape_interval | default "300s" }}
evaluation_interval: {{ .Values.metrics.agent.scrape_interval | default "300s" }}
external_labels:
# Multi-tenant identification
plane_installation_uuid: "{{ include "plane.metrics.installationUUID" . }}"
plane_installation_type: "{{ .Values.metrics.installation.type | default "kubernetes" }}"
plane_version: "{{ .Values.metrics.installation.plane_version | default .Values.planeVersion }}"
cluster_name: "{{ .Values.metrics.installation.cluster_name | default "default" }}"
license_domain: "{{ .Values.license.licenseDomain }}"
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Script executed:

#!/bin/bash
# First, examine the metrics-config.yaml file to understand full context
cat -n charts/plane-enterprise/templates/config-secrets/metrics-config.yaml

Repository: makeplane/helm-charts


<shell_result>
Repository: makeplane/helm-charts
Exit code: 0

stdout:

     1	{{- if .Values.metrics.enabled }}
     2	apiVersion: v1
     3	kind: ConfigMap
     4	metadata:
     5	  namespace: {{ .Release.Namespace }}
     6	  name: {{ .Release.Name }}-metrics-config
     7	  labels:
     8	    app.kubernetes.io/name: plane-enterprise
     9	    app.kubernetes.io/instance: {{ .Release.Name }}
    10	    app.kubernetes.io/component: metrics-agent
    11	data:
    12	  agent-config.yaml: |
    13	    # OpenTelemetry Agent Configuration for Plane Enterprise
    14	    # Collects Kubernetes metrics and Plane API metrics
    15	    receivers:
    16	      # Kubernetes metrics collection
    17	      prometheus:
    18	        config:
    19	          global:
    20	            scrape_interval: {{ .Values.metrics.agent.scrape_interval | default "300s" }}
    21	            evaluation_interval: {{ .Values.metrics.agent.scrape_interval | default "300s" }}
    22	            external_labels:
    23	              # Multi-tenant identification
    24	              plane_installation_uuid: "{{ include "plane.metrics.installationUUID" . }}"
    25	              plane_installation_type: "{{ .Values.metrics.installation.type | default "kubernetes" }}"
    26	              plane_version: "{{ .Values.metrics.installation.plane_version | default .Values.planeVersion }}"
    27	              cluster_name: "{{ .Values.metrics.installation.cluster_name | default "default" }}"
    28	              license_domain: "{{ .Values.license.licenseDomain }}"
    29	          
    30	          scrape_configs:
    31	            # cAdvisor metrics (container resource usage for this namespace only)
    32	            - job_name: 'kubernetes-cadvisor'
    33	              scheme: https
    34	              tls_config:
    35	                ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
    36	                insecure_skip_verify: true
    37	              bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
    38	              kubernetes_sd_configs:
    39	                - role: node
    40	              relabel_configs:
    41	                - action: labelmap
    42	                  regex: __meta_kubernetes_node_label_(.+)
    43	                - target_label: __address__
    44	                  replacement: kubernetes.default.svc:443
    45	                - source_labels: [__meta_kubernetes_node_name]
    46	                  regex: (.+)
    47	                  target_label: __metrics_path__
    48	                  replacement: /api/v1/nodes/$1/proxy/metrics/cadvisor
    49	              # Filter metrics to only include containers in our namespace
    50	              metric_relabel_configs:
    51	                - source_labels: [namespace]
    52	                  action: keep
    53	                  regex: {{ .Release.Namespace }}
    54	                - source_labels: [namespace]
    55	                  action: drop
    56	                  regex: ^$
    57	
    58	            # Plane API /metrics endpoint - scrape service
    59	            - job_name: 'plane-api'
    60	              kubernetes_sd_configs:
    61	                - role: service
    62	                  namespaces:
    63	                    names:
    64	                      - {{ .Release.Namespace }}
    65	              relabel_configs:
    66	                # Only scrape the API service
    67	                - source_labels: [__meta_kubernetes_service_name]
    68	                  action: keep
    69	                  regex: {{ .Release.Name }}-api
    70	                # Set metrics path to /metrics
    71	                - target_label: __metrics_path__
    72	                  replacement: /metrics
    73	                # Set port to 8000 (API service port)
    74	                - source_labels: [__address__]
    75	                  target_label: __address__
    76	                  regex: ([^:]+)(?::\d+)?
    77	                  replacement: $1:8000
    78	                - action: labelmap
    79	                  regex: __meta_kubernetes_service_label_(.+)
    80	                - source_labels: [__meta_kubernetes_namespace]
    81	                  action: replace
    82	                  target_label: kubernetes_namespace
    83	                - source_labels: [__meta_kubernetes_service_name]
    84	                  action: replace
    85	                  target_label: kubernetes_service_name
    86	
    87	    processors:
    88	      batch:
    89	        timeout: {{ .Values.metrics.agent.batch.timeout | default "60s" }}
    90	        send_batch_size: {{ .Values.metrics.agent.batch.send_batch_size | default 4096 }}
    91	      
    92	      memory_limiter:
    93	        limit_mib: {{ regexReplaceAll "Mi|Gi" (.Values.metrics.agent.memoryLimit | default "256Mi") "" | int }}
    94	        spike_limit_mib: {{ div (regexReplaceAll "Mi|Gi" (.Values.metrics.agent.memoryLimit | default "256Mi") "") 4 | int }}
    95	        check_interval: 0.1s
    96	
    97	      # Resource processing to add tenant identification
    98	      resource:
    99	        attributes:
   100	          - key: plane.installation.uuid
   101	            value: "{{ include "plane.metrics.installationUUID" . }}"
   102	            action: upsert
   103	          - key: plane.installation.type
   104	            value: "{{ .Values.metrics.installation.type | default "kubernetes" }}"
   105	            action: upsert
   106	          - key: plane.installation.cluster
   107	            value: "{{ .Values.metrics.installation.cluster_name | default "default" }}"
   108	            action: upsert
   109	
   110	    exporters:
   111	      # Export to remote telemetry server  
   112	      {{- if .Values.metrics.telemetry.http_endpoint }}
   113	      {{- $cleanEndpoint := .Values.metrics.telemetry.http_endpoint | trimPrefix "https://" | trimPrefix "http://" | trimSuffix "/v1/traces" | trimSuffix "/v1/metrics" }}
   114	      {{- $endpointWithPort := $cleanEndpoint }}
   115	      {{- if not (contains ":" $cleanEndpoint) }}
   116	        {{- $endpointWithPort = printf "%s:443" $cleanEndpoint }}
   117	      {{- end }}
   118	      otlp/http:
   119	        endpoint: {{ $endpointWithPort }}
   120	        compression: gzip
   121	        tls:
   122	          # TODO: Add production TLS configuration
   123	          insecure: true
   124	        retry_on_failure:
   125	          enabled: true
   126	          initial_interval: 5s
   127	          max_interval: 30s
   128	          max_elapsed_time: 300s
   129	        sending_queue:
   130	          enabled: true
   131	          num_consumers: 4
   132	          queue_size: 100
   133	        headers:
   134	          {{- range $key, $value := .Values.metrics.telemetry.headers }}
   135	          {{ $key }}: {{ $value | quote }}
   136	          {{- end }}
   137	      {{- end }}
   138	
   139	      {{- if .Values.metrics.agent.debug.enabled }}
   140	      # Debug logging (development only)
   141	      logging:
   142	        loglevel: debug
   143	        sampling_initial: 5
   144	        sampling_thereafter: 200
   145	      {{- end }}
   146	
   147	    extensions:
   148	      health_check:
   149	        endpoint: 0.0.0.0:13133
   150	      
   151	      pprof:
   152	        endpoint: 0.0.0.0:1777
   153	
   154	    service:
   155	      extensions: [health_check, pprof]
   156	      pipelines:
   157	        metrics:
   158	          receivers: [prometheus]
   159	          processors: [memory_limiter, resource, batch]
   160	          exporters: [
   161	            {{- if .Values.metrics.telemetry.http_endpoint }}otlp/http{{- if .Values.metrics.agent.debug.enabled }},{{- end }}{{- end }}
   162	            {{- if .Values.metrics.agent.debug.enabled }}logging{{- end }}
   163	          ]
   164	{{- end }}
   165	

</shell_result>


Script executed:

#!/bin/bash
# Search for privacy, compliance, and data handling documentation
find . -type f \( -name "README*" -o -name "PRIVACY*" -o -name "COMPLIANCE*" -o -name "*.md" \) | head -20
rg -i "privacy|gdpr|ccpa|pii|sensitive|data.*retention" --type markdown --type txt -A2 -B2

Repository: makeplane/helm-charts


<shell_result>
Repository: makeplane/helm-charts
Exit code: 0

stdout:

./README.md
./charts/plane-ce/README.md
./charts/plane-enterprise/README.md
./external-secrets-setup.md
charts/plane-ce/README.md-| Setting                    |                      Default                       | Required | Description                                                                                                                                                                      |
charts/plane-ce/README.md-| -------------------------- | :------------------------------------------------: | :------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
charts/plane-ce/README.md:| 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 |
charts/plane-ce/README.md-| 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.                                                                    |
charts/plane-ce/README.md-
--
charts/plane-enterprise/README.md-| ---------------- | :------------------------------------------------: | :------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
charts/plane-enterprise/README.md-| env.storageClass |         &lt;k8s-default-storage-class&gt;          |          | Creating the persitant volumes for the stateful deployments needs the `storageClass` name. Set the correct value as per your kubernetes cluster configuration.                   |
charts/plane-enterprise/README.md:| 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 |
charts/plane-enterprise/README.md-
charts/plane-enterprise/README.md-### Extra Environment Variables

</shell_result>


Script executed:

#!/bin/bash
# Check README for metrics telemetry documentation
cat charts/plane-enterprise/README.md | head -200

# Also search for license_domain references
echo "=== Searching for license_domain references ==="
rg -n "license_domain|licenseDomain" -B2 -A2

Repository: makeplane/helm-charts


<shell_result>
Repository: makeplane/helm-charts
Exit code: 0

stdout:

## Pre-requisite

- A working Kubernetes cluster
- `kubectl` and `helm` on the client system that you will use to install our Helm charts

## Installing Plane

1. Open Terminal or any other command-line app that has access to Kubernetes tools on your local system.
2. Set the following environment variables.

   Copy the format of constants below, paste it on Terminal to start setting environment variables, set values for each variable, and hit ENTER or RETURN.

   ```bash
   PLANE_VERSION=v1.16.0 # or the last released version
   DOMAIN_NAME=<subdomain.domain.tld or domain.tld>
   ```

3. Add Plane helm chart repo

   Continue to be on the same Terminal window as with the previous steps, copy the code below, paste it on Terminal, and hit ENTER or RETURN.

   ```bash
   helm repo add plane https://helm.plane.so/
   ```

4. Set-up and customization

   - Quick set-up

     This is the fastest way to deploy Plane with default settings. This will create stateful deployments for Postgres, Rabbitmq, Redis/Valkey, and Minio with a persistent volume claim using the default storage class.This also sets up the ingress routes for you using `nginx` ingress class.

     > To customize this, see `Custom ingress routes` below.

     Continue to be on the same Terminal window as you have so far, copy the code below, and paste it on your Terminal screen.

     ```bash
     helm upgrade --install plane-app plane/plane-enterprise \
         --create-namespace \
         --namespace plane \
         --set license.licenseDomain=${DOMAIN_NAME} \
         --set planeVersion=${PLANE_VERSION} \
         --set ingress.enabled=true \
         --set ingress.ingressClass=nginx \
         --timeout 10m \
         --wait \
         --wait-for-jobs
     ```

     > This is the basic setup required for Plane-EE. You can customize the default values for namespace and appname as needed. Additional settings can be configured by referring to the Configuration Settings section.<br>

     Using a Custom StorageClass

     To specify a custom StorageClass for Plane-Enterprise components, add the following options to the above `helm upgrade --install` command:

     ```bash
     --set env.storageClass=<your-storageclass-name>
     ```

   - Advance set-up

     For more control over your set-up, run the script below to download the `values.yaml` file and and edit using any editor like Vim or Nano.

     ```bash
     helm  show values plane/plane-enterprise > values.yaml
     vi values.yaml
     ```

     Make sure you set the minimum required values as below.

     - `planeVersion: v1.16.0 <or the last released version>`
     - `license.licenseDomain: <The domain you have specified to host Plane>`
     - `ingress.enabled: <true | false>`
     - `ingress.ingressClass: <nginx or any other ingress class configured in your cluster>`
     - `env.storageClass: <default storage class configured in your cluster>`

       > See `Available customizations` for more details.

     After saving the `values.yaml` file, continue to be on the same Terminal window as on the previous steps, copy the code below, and paste it on your Terminal screen.

     ```bash
     helm upgrade --install plane-app plane/plane-enterprise \
         --create-namespace \
         --namespace plane \
         -f values.yaml \
         --timeout 10m \
         --wait \
         --wait-for-jobs
     ```

## Available customizations

### License

| Setting               |      Default      | Required | Description                                                                                                                                                                          |
| --------------------- | :---------------: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| planeVersion          |      v1.16.0      |   Yes    | Specifies the version of Plane to be deployed. Copy this from prime.plane.so.                                                                                                        |
| license.licenseDomain | plane.example.com |   Yes    | The fully-qualified domain name (FQDN) in the format `sudomain.domain.tld` or `domain.tld` that the license is bound to. It is also attached to your `ingress` host to access Plane. |

### Air-gapped Settings

| Setting                | Default | Required | Description                                                                                                                                                                                                                                                                                                                                                      |
| ---------------------- | :-----: | :------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| airgapped.enabled      |  false  |    No    | Specifies the airgapped mode the Plane API runs in.                                                                                                                                                                                                                                                                                                              |
| airgapped.s3SecretName |   ""    |    No    | Name of the Secret that contains the CA certificate (.crt). The Secret must include a data key whose filename matches the basename of `airgapped.s3SecretKey`. Used to override S3’s CA when `airgapped.enabled=true`. Applying this secret looks like: `kubectl -n plane create secret generic plane-s3-ca \ --from-file=s3-custom-ca.crt=/path/to/your/ca.crt` |
| airgapped.s3SecretKey  |   ""    |    No    | Key name of the secret to load the Custom Root CA from `airgapped.s3SecretName`                                                                                                                                                                                                                                                                                  |

### Postgres

| Setting                             |       Default        | Required | Description                                                                                                                                                                                                                                                                                                                                                                             |
| ----------------------------------- | :------------------: | :------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| services.postgres.local_setup       |         true         |          | Plane uses `postgres` as the primary database to store all the transactional data. This database can be hosted within kubernetes as part of helm chart deployment or can be used as hosted service remotely (e.g. aws rds or similar services). Set this to `true` when you choose to setup stateful deployment of `postgres`. Mark it as `false` when using a remotely hosted database |
| services.postgres.image             | postgres:15.7-alpine |          | Using this key, user must provide the docker image name to setup the stateful deployment of `postgres`. (must be set when `services.postgres.local_setup=true`)                                                                                                                                                                                                                         |
| services.postgres.pullPolicy        |     IfNotPresent     |          | Using this key, user can set the pull policy for the stateful deployment of `postgres`. (must be set when `services.postgres.local_setup=true`)                                                                                                                                                                                                                                         |
| services.postgres.servicePort       |         5432         |          | This key sets the default port number to be used while setting up stateful deployment of `postgres`.                                                                                                                                                                                                                                                                                    |
| services.postgres.volumeSize        |         2Gi          |          | While setting up the stateful deployment, while creating the persistant volume, volume allocation size need to be provided. This key helps you set the volume allocation size. Unit of this value must be in Mi (megabyte) or Gi (gigabyte)                                                                                                                                             |
| env.pgdb_username                   |        plane         |          | Database credentials are requried to access the hosted stateful deployment of `postgres`. Use this key to set the username for the stateful deployment.                                                                                                                                                                                                                                 |
| env.pgdb_password                   |        plane         |          | Database credentials are requried to access the hosted stateful deployment of `postgres`. Use this key to set the password for the stateful deployment.                                                                                                                                                                                                                                 |
| env.pgdb_name                       |        plane         |          | Database name to be used while setting up stateful deployment of `Postgres`                                                                                                                                                                                                                                                                                                             |
| services.postgres.assign_cluster_ip |        false         |          | Set it to `true` if you want to assign `ClusterIP` to the service                                                                                                                                                                                                                                                                                                                       |
| services.postgres.nodeSelector      |          {}          |          | This key allows you to set the node selector for the stateful deployment of `postgres`. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster.                                                                                                                                                                                                |
| services.postgres.tolerations       |          []          |          | This key allows you to set the tolerations for the stateful deployment of `postgres`. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster.                                                                                                                                                                                      |
| services.postgres.affinity          |          {}          |          | This key allows you to set the affinity rules for the stateful deployment of `postgres`. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster.                                                                                                                                                                                            |
| services.postgres.labels            |          {}          |          | This key allows you to set custom labels for the stateful deployment of `postgres`. This is useful for organizing and selecting resources in your Kubernetes cluster.                                                                                                                                                                                                                   |
| services.postgres.annotations       |          {}          |          | This key allows you to set custom annotations for the stateful deployment of `postgres`. This is useful for adding metadata or configuration hints to your resources.                                                                                                                                                                                                                   |
| env.pgdb_remote_url                 |                      |          | Users can also decide to use the remote hosted database and link to Plane deployment. Ignoring all the above keys, set `services.postgres.local_setup` to `false` and set this key with remote connection url.                                                                                                                                                                          |

### Redis/Valkey Setup

| Setting                          |           Default           | Required | Description                                                                                                                                                                                                                                                                                                                                                                      |
| -------------------------------- | :-------------------------: | :------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| services.redis.local_setup       |            true             |          | Plane uses `valkey` to cache the session authentication and other static data. This database can be hosted within kubernetes as part of helm chart deployment or can be used as hosted service remotely (e.g. aws rds or similar services). Set this to `true` when you choose to setup stateful deployment of `redis`. Mark it as `false` when using a remotely hosted database |
| services.redis.image             | valkey/valkey:7.2.11-alpine |          | Using this key, user must provide the docker image name to setup the stateful deployment of `redis`. (must be set when `services.redis.local_setup=true`)                                                                                                                                                                                                                        |
| services.redis.pullPolicy        |        IfNotPresent         |          | Using this key, user can set the pull policy for the stateful deployment of `redis`. (must be set when `services.redis.local_setup=true`)                                                                                                                                                                                                                                        |
| services.redis.servicePort       |            6379             |          | This key sets the default port number to be used while setting up stateful deployment of `redis`.                                                                                                                                                                                                                                                                                |
| services.redis.volumeSize        |            500Mi            |          | While setting up the stateful deployment, while creating the persistant volume, volume allocation size need to be provided. This key helps you set the volume allocation size. Unit of this value must be in Mi (megabyte) or Gi (gigabyte)                                                                                                                                      |
| services.redis.assign_cluster_ip |            false            |          | Set it to `true` if you want to assign `ClusterIP` to the service                                                                                                                                                                                                                                                                                                                |
| services.redis.nodeSelector      |             {}              |          | This key allows you to set the node selector for the stateful deployment of `redis`. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster.                                                                                                                                                                                            |
| services.redis.tolerations       |             []              |          | This key allows you to set the tolerations for the stateful deployment of `redis`. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster.                                                                                                                                                                                  |
| services.redis.affinity          |             {}              |          | This key allows you to set the affinity rules for the stateful deployment of `redis`. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster.                                                                                                                                                                                        |
| services.redis.labels            |             {}              |          | This key allows you to set custom labels for the stateful deployment of `redis`. This is useful for organizing and selecting resources in your Kubernetes cluster.                                                                                                                                                                                                               |
| services.redis.annotations       |             {}              |          | This key allows you to set custom annotations for the stateful deployment of `redis`. This is useful for adding metadata or configuration hints to your resources.                                                                                                                                                                                                               |
| env.remote_redis_url             |                             |          | Users can also decide to use the remote hosted database and link to Plane deployment. Ignoring all the above keys, set `services.redis.local_setup` to `false` and set this key with remote connection url.                                                                                                                                                                      |

### RabbitMQ Setup

| Setting                                 |              Default              | Required | Description                                                                                                                                                                                                                                                                                                                                |
| --------------------------------------- | :-------------------------------: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| services.rabbitmq.local_setup           |               true                |          | Plane uses `rabbitmq` as message queuing system. This can be hosted within kubernetes as part of helm chart deployment or can be used as hosted service remotely (e.g. aws mq or similar services). Set this to `true` when you choose to setup stateful deployment of `rabbitmq`. Mark it as `false` when using a remotely hosted service |
| services.rabbitmq.image                 | rabbitmq:3.13.6-management-alpine |          | Using this key, user must provide the docker image name to setup the stateful deployment of `rabbitmq`. (must be set when `services.rabbitmq.local_setup=true`)                                                                                                                                                                            |
| services.rabbitmq.pullPolicy            |           IfNotPresent            |          | Using this key, user can set the pull policy for the stateful deployment of `rabbitmq`. (must be set when `services.rabbitmq.local_setup=true`)                                                                                                                                                                                            |
| services.rabbitmq.servicePort           |               5672                |          | This key sets the default port number to be used while setting up stateful deployment of `rabbitmq`.                                                                                                                                                                                                                                       |
| services.rabbitmq.managementPort        |               15672               |          | This key sets the default management port number to be used while setting up stateful deployment of `rabbitmq`.                                                                                                                                                                                                                            |
| services.rabbitmq.volumeSize            |               100Mi               |          | While setting up the stateful deployment, while creating the persistant volume, volume allocation size need to be provided. This key helps you set the volume allocation size. Unit of this value must be in Mi (megabyte) or Gi (gigabyte)                                                                                                |
| services.rabbitmq.default_user          |               plane               |          | Credentials are requried to access the hosted stateful deployment of `rabbitmq`. Use this key to set the username for the stateful deployment.                                                                                                                                                                                             |
| services.rabbitmq.default_password      |               plane               |          | Credentials are requried to access the hosted stateful deployment of `rabbitmq`. Use this key to set the password for the stateful deployment.                                                                                                                                                                                             |
| services.rabbitmq.assign_cluster_ip     |               false               |          | Set it to `true` if you want to assign `ClusterIP` to the service                                                                                                                                                                                                                                                                          |
| services.rabbitmq.nodeSelector          |                {}                 |          | This key allows you to set the node selector for the stateful deployment of `rabbitmq`. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster.                                                                                                                                                   |
| services.rabbitmq.tolerations           |                []                 |          | This key allows you to set the tolerations for the stateful deployment of `rabbitmq`. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster.                                                                                                                                         |
| services.rabbitmq.affinity              |                {}                 |          | This key allows you to set the affinity rules for the stateful deployment of `rabbitmq`. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster.                                                                                                                                               |
| services.rabbitmq.labels                |                {}                 |          | This key allows you to set custom labels for the stateful deployment of `rabbitmq`. This is useful for organizing and selecting resources in your Kubernetes cluster.                                                                                                                                                                      |
| services.rabbitmq.annotations           |                {}                 |          | This key allows you to set custom annotations for the stateful deployment of `rabbitmq`. This is useful for adding metadata or configuration hints to your resources.                                                                                                                                                                      |
| services.rabbitmq.external_rabbitmq_url |                                   |          | Users can also decide to use the remote hosted service and link to Plane deployment. Ignoring all the above keys, set `services.rabbitmq.local_setup` to `false` and set this key with remote connection url.                                                                                                                              |

### Doc Store (Minio/S3) Setup

| Setting                               |      Default       | Required | Description                                                                                                                                                                                                                                                                                                                                           |
| ------------------------------------- | :----------------: | :------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| services.minio.local_setup            |        true        |          | Plane uses `minio` as the default file storage drive. This storage can be hosted within kubernetes as part of helm chart deployment or can be used as hosted service remotely (e.g. aws S3 or similar services). Set this to `true` when you choose to setup stateful deployment of `minio`. Mark it as `false` when using a remotely hosted database |
| services.minio.image                  | minio/minio:latest |          | Using this key, user must provide the docker image name to setup the stateful deployment of `minio`. (must be set when `services.minio.local_setup=true`)                                                                                                                                                                                             |
| services.minio.image_mc               |  minio/mc:latest   |          | Using this key, user must provide the docker image name to setup the job deployment of `minio client`. (must be set when `services.minio.local_setup=true`)                                                                                                                                                                                           |
| services.minio.pullPolicy             |    IfNotPresent    |          | Using this key, user can set the pull policy for the stateful deployment of `minio`. (must be set when `services.minio.local_setup=true`)                                                                                                                                                                                                             |
| services.minio.volumeSize             |        3Gi         |          | While setting up the stateful deployment, while creating the persistant volume, volume allocation size need to be provided. This key helps you set the volume allocation size. Unit of this value must be in Mi (megabyte) or Gi (gigabyte)                                                                                                           |
| services.minio.root_user              |       admin        |          | Storage credentials are requried to access the hosted stateful deployment of `minio`. Use this key to set the username for the stateful deployment.                                                                                                                                                                                                   |
| services.minio.root_password          |      password      |          | Storage credentials are requried to access the hosted stateful deployment of `minio`. Use this key to set the password for the stateful deployment.                                                                                                                                                                                                   |
| services.minio.env.minio_endpoint_ssl |       false        |          | (Optional) Env to enforce HTTPS when connecting to minio uploads bucket                                                                                                                                                                                                                                                                               |
| env.docstore_bucket                   |      uploads       |   Yes    | Storage bucket name is required as part of configuration. This is where files will be uploaded irrespective of if you are using `Minio` or external `S3` (or compatible) storage service                                                                                                                                                              |
| env.doc_upload_size_limit             |      5242880       |   Yes    | Document Upload Size Limit (default to 5Mb)                                                                                                                                                                                                                                                                                                           |
| services.minio.assign_cluster_ip      |       false        |          | Set it to `true` if you want to assign `ClusterIP` to the service                                                                                                                                                                                                                                                                                     |
| services.minio.nodeSelector           |         {}         |          | This key allows you to set the node selector for the stateful deployment of `minio`. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster.                                                                                                                                                                 |
| services.minio.tolerations            |         []         |          | This key allows you to set the tolerations for the stateful deployment of `minio`. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster.                                                                                                                                                       |
| services.minio.affinity               |         {}         |          | This key allows you to set the affinity rules for the stateful deployment of `minio`. This is useful when you want to control how pods are scheduled on nodes in your Kubernetes cluster.                                                                                                                                                             |
| services.minio.labels                 |         {}         |          | This key allows you to set custom labels for the stateful deployment of `minio`. This is useful for organizing and selecting resources in your Kubernetes cluster.                                                                                                                                                                                    |
| services.minio.annotations            |         {}         |          | This key allows you to set custom annotations for the stateful deployment of `minio`. This is useful for adding metadata or configuration hints to your resources.                                                                                                                                                                                    |
| env.aws_access_key                    |                    |          | External `S3` (or compatible) storage service provides `access key` for the application to connect and do the necessary upload/download operations. To be provided when `services.minio.local_setup=false`                                                                                                                                            |
| env.aws_secret_access_key             |                    |          | External `S3` (or compatible) storage service provides `secret access key` for the application to connect and do the necessary upload/download operations. To be provided when `services.minio.local_setup=false`                                                                                                                                     |
| env.aws_region                        |                    |          | External `S3` (or compatible) storage service providers creates any buckets in user selected region. This is also shared with the user as `region` for the application to connect and do the necessary upload/download operations. To be provided when `services.minio.local_setup=false`                                                             |
| env.aws_s3_endpoint_url               |                    |          | External `S3` (or compatible) storage service providers shares a `endpoint_url` for the integration purpose for the application to connect and do the necessary upload/download operations. To be provided when `services.minio.local_setup=false`                                                                                                    |

### Web Deployment

| Setting | Default | Required | Description |
|---|:---:|:---:|---|
| services.web.replicas | 1 | Yes | Kubernetes helps you with scaling up/down the deployments. You can run 1 or more pods for each deployment. This key helps you setting up number of replicas you want to run for this deployment. It must be >=1 |
| services.web.memoryLimit | 1000Mi |  |  Every deployment in kubernetes can be set to use maximum memory they are allowed to use. This key sets the memory limit for this deployment to use.|
| services.web.cpuLimit | 500m |  |  Every deployment in kubernetes can be set to use maximum cpu they are allowed to use. This key sets the cpu limit for this deployment to use.|
| services.web.memoryRequest | 50Mi |  | Every deployment in kubernetes can be set to use minimum memory they are allowed to use. This key sets the memory request for this deployment to use.|
| services.web.cpuRequest | 50m |  | Every deployment in kubernetes can be set to use minimum cpu they are allowed to use. This key sets the cpu request for this deployment to use.|
| services.web.image| artifacts.plane.so/makeplane/web-commercial |  |  This deployment needs a preconfigured docker image to function. Docker image name is provided by the owner and must not be changed for this deployment |
| services.web.pullPolicy | Always |  | Using this key, user can set the pull policy for the deployment of `web`. |
| services.web.assign_cluster_ip | false |  | Set it to `true` if you want to assign `ClusterIP` to the service |
=== Searching for license_domain references ===
charts/plane-enterprise/values.yaml-9-license:
charts/plane-enterprise/values.yaml-10-  licenseServer: https://prime.plane.so
charts/plane-enterprise/values.yaml:11:  licenseDomain: 'plane.example.com'
charts/plane-enterprise/values.yaml-12-
charts/plane-enterprise/values.yaml-13-airgapped:
--
charts/plane-enterprise/questions.yml-76-  group: "License Setup"
charts/plane-enterprise/questions.yml-77-  subquestions:
charts/plane-enterprise/questions.yml:78:  - variable: license.licenseDomain
charts/plane-enterprise/questions.yml-79-    label: "License Domain"
charts/plane-enterprise/questions.yml-80-    type: string
--
charts/plane-enterprise/README.md-38-         --create-namespace \
charts/plane-enterprise/README.md-39-         --namespace plane \
charts/plane-enterprise/README.md:40:         --set license.licenseDomain=${DOMAIN_NAME} \
charts/plane-enterprise/README.md-41-         --set planeVersion=${PLANE_VERSION} \
charts/plane-enterprise/README.md-42-         --set ingress.enabled=true \
--
charts/plane-enterprise/README.md-69-
charts/plane-enterprise/README.md-70-     - `planeVersion: v1.16.0 <or the last released version>`
charts/plane-enterprise/README.md:71:     - `license.licenseDomain: <The domain you have specified to host Plane>`
charts/plane-enterprise/README.md-72-     - `ingress.enabled: <true | false>`
charts/plane-enterprise/README.md-73-     - `ingress.ingressClass: <nginx or any other ingress class configured in your cluster>`
--
charts/plane-enterprise/README.md-95-| --------------------- | :---------------: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
charts/plane-enterprise/README.md-96-| planeVersion          |      v1.16.0      |   Yes    | Specifies the version of Plane to be deployed. Copy this from prime.plane.so.                                                                                                        |
charts/plane-enterprise/README.md:97:| license.licenseDomain | plane.example.com |   Yes    | The fully-qualified domain name (FQDN) in the format `sudomain.domain.tld` or `domain.tld` that the license is bound to. It is also attached to your `ingress` host to access Plane. |
charts/plane-enterprise/README.md-98-
charts/plane-enterprise/README.md-99-### Air-gapped Settings
--
charts/plane-enterprise/templates/ingress.yaml:1:{{- if and .Values.ingress.enabled .Values.license.licenseDomain }}
charts/plane-enterprise/templates/ingress.yaml-2-
charts/plane-enterprise/templates/ingress.yaml-3-apiVersion: networking.k8s.io/v1
--
charts/plane-enterprise/templates/ingress.yaml-15-  ingressClassName: {{ .Values.ingress.ingressClass }}
charts/plane-enterprise/templates/ingress.yaml-16-  rules:
charts/plane-enterprise/templates/ingress.yaml:17:    - host: {{ .Values.license.licenseDomain }}
charts/plane-enterprise/templates/ingress.yaml-18-      http:
charts/plane-enterprise/templates/ingress.yaml-19-        paths:
--
charts/plane-enterprise/templates/ingress.yaml-112-  tls:
charts/plane-enterprise/templates/ingress.yaml-113-    - hosts:
charts/plane-enterprise/templates/ingress.yaml:114:      - {{ .Values.license.licenseDomain | quote }}
charts/plane-enterprise/templates/ingress.yaml-115-      {{- if and .Values.services.minio.local_setup .Values.ingress.minioHost }}
charts/plane-enterprise/templates/ingress.yaml-116-      - {{ .Values.ingress.minioHost | quote }}
--
charts/plane-enterprise/templates/ingress.yaml-123-  tls:
charts/plane-enterprise/templates/ingress.yaml-124-    - hosts:
charts/plane-enterprise/templates/ingress.yaml:125:      - {{ .Values.license.licenseDomain | quote }}
charts/plane-enterprise/templates/ingress.yaml-126-      {{- if and .Values.services.minio.local_setup .Values.ingress.minioHost }}
charts/plane-enterprise/templates/ingress.yaml-127-      - {{ .Values.ingress.minioHost | quote }}
--
charts/plane-enterprise/templates/_helpers.tpl-4-
charts/plane-enterprise/templates/_helpers.tpl-5-{{- define "hashString" -}}
charts/plane-enterprise/templates/_helpers.tpl:6:{{- printf "%s%s%s%s" .Values.license.licenseServer .Values.license.licenseDomain .Release.Namespace .Release.Name | sha256sum  -}}
charts/plane-enterprise/templates/_helpers.tpl-7-{{- end -}}
charts/plane-enterprise/templates/_helpers.tpl-8-
--
charts/plane-enterprise/templates/config-secrets/metrics-config.yaml-26-              plane_version: "{{ .Values.metrics.installation.plane_version | default .Values.planeVersion }}"
charts/plane-enterprise/templates/config-secrets/metrics-config.yaml-27-              cluster_name: "{{ .Values.metrics.installation.cluster_name | default "default" }}"
charts/plane-enterprise/templates/config-secrets/metrics-config.yaml:28:              license_domain: "{{ .Values.license.licenseDomain }}"
charts/plane-enterprise/templates/config-secrets/metrics-config.yaml-29-          
charts/plane-enterprise/templates/config-secrets/metrics-config.yaml-30-          scrape_configs:
--
charts/plane-enterprise/templates/config-secrets/silo.yaml-73-  CORS_ALLOWED_ORIGINS: "*"
charts/plane-enterprise/templates/config-secrets/silo.yaml-74-  {{- else if .Values.env.silo_envs.cors_allowed_origins }}
charts/plane-enterprise/templates/config-secrets/silo.yaml:75:  CORS_ALLOWED_ORIGINS: "http://{{ .Values.license.licenseDomain }},https://{{ .Values.license.licenseDomain }},{{ .Values.env.silo_envs.cors_allowed_origins }}"
charts/plane-enterprise/templates/config-secrets/silo.yaml-76-  {{- else}}
charts/plane-enterprise/templates/config-secrets/silo.yaml:77:  CORS_ALLOWED_ORIGINS: "http://{{ .Values.license.licenseDomain }},https://{{ .Values.license.licenseDomain }}"
charts/plane-enterprise/templates/config-secrets/silo.yaml-78-  {{- end }} 
charts/plane-enterprise/templates/config-secrets/silo.yaml-79-
charts/plane-enterprise/templates/config-secrets/silo.yaml:80:  APP_BASE_URL: "https://{{ .Values.license.licenseDomain }}"
charts/plane-enterprise/templates/config-secrets/silo.yaml-81-  API_BASE_URL: "http://{{ .Release.Name }}-api.{{ .Release.Namespace }}.svc.{{ .Values.env.default_cluster_domain | default "cluster.local" }}:8000/"
charts/plane-enterprise/templates/config-secrets/silo.yaml:82:  SILO_API_BASE_URL: "https://{{ .Values.license.licenseDomain }}"
charts/plane-enterprise/templates/config-secrets/silo.yaml-83-
charts/plane-enterprise/templates/config-secrets/silo.yaml-84-  PAYMENT_SERVER_BASE_URL: "http://{{ .Release.Name }}-monitor.{{ .Release.Namespace }}.svc.{{ .Values.env.default_cluster_domain | default "cluster.local" }}:8080/"
--
charts/plane-enterprise/templates/config-secrets/monitor.yaml-7-  PRIME_HOST: {{ .Values.license.licenseServer | quote }}
charts/plane-enterprise/templates/config-secrets/monitor.yaml-8-  MACHINE_SIGNATURE: {{ include "hashString" . | quote }}
charts/plane-enterprise/templates/config-secrets/monitor.yaml:9:  APP_DOMAIN: {{ .Values.license.licenseDomain | quote }}
charts/plane-enterprise/templates/config-secrets/monitor.yaml-10-  APP_VERSION: {{ .Values.planeVersion | quote }}
charts/plane-enterprise/templates/config-secrets/monitor.yaml-11-  DEPLOY_PLATFORM: "KUBERNETES"
--
charts/plane-enterprise/templates/certs/certs.yaml-8-spec:
charts/plane-enterprise/templates/certs/certs.yaml-9-  dnsNames:
charts/plane-enterprise/templates/certs/certs.yaml:10:  - {{ .Values.license.licenseDomain | quote }}
charts/plane-enterprise/templates/certs/certs.yaml-11-  {{- if and .Values.services.minio.local_setup .Values.ingress.minioHost }}
charts/plane-enterprise/templates/certs/certs.yaml-12-  - {{ .Values.ingress.minioHost | quote }}
--
charts/plane-enterprise/templates/config-secrets/app-env.yaml-48-  PRIME_HOST: {{ .Values.license.licenseServer | quote }}
charts/plane-enterprise/templates/config-secrets/app-env.yaml-49-  MACHINE_SIGNATURE: {{ include "hashString" . | quote }}
charts/plane-enterprise/templates/config-secrets/app-env.yaml:50:  APP_DOMAIN: {{ .Values.license.licenseDomain | quote }}
charts/plane-enterprise/templates/config-secrets/app-env.yaml-51-  APP_VERSION: {{ .Values.planeVersion | quote }}
charts/plane-enterprise/templates/config-secrets/app-env.yaml-52-  PAYMENT_SERVER_BASE_URL: "http://{{ .Release.Name }}-monitor.{{ .Release.Namespace }}.svc.cluster.local:8080/"
--
charts/plane-enterprise/templates/config-secrets/app-env.yaml-64-  GUNICORN_WORKERS: "1"
charts/plane-enterprise/templates/config-secrets/app-env.yaml-65-
charts/plane-enterprise/templates/config-secrets/app-env.yaml:66:  WEB_URL: "http://{{ .Values.license.licenseDomain }}"
charts/plane-enterprise/templates/config-secrets/app-env.yaml-67-
charts/plane-enterprise/templates/config-secrets/app-env.yaml-68-  LIVE_BASE_URL: "http://{{ .Release.Name }}-live.{{ .Release.Namespace }}.svc.cluster.local:3000/"
--
charts/plane-enterprise/templates/config-secrets/app-env.yaml-76-  CORS_ALLOWED_ORIGINS: "*"
charts/plane-enterprise/templates/config-secrets/app-env.yaml-77-  {{- else if .Values.env.cors_allowed_origins }}
charts/plane-enterprise/templates/config-secrets/app-env.yaml:78:  CORS_ALLOWED_ORIGINS: "http://{{ .Values.license.licenseDomain }},https://{{ .Values.license.licenseDomain }},{{ .Values.env.cors_allowed_origins }}"
charts/plane-enterprise/templates/config-secrets/app-env.yaml-79-  {{- else}}
charts/plane-enterprise/templates/config-secrets/app-env.yaml:80:  CORS_ALLOWED_ORIGINS: "http://{{ .Values.license.licenseDomain }},https://{{ .Values.license.licenseDomain }}"
charts/plane-enterprise/templates/config-secrets/app-env.yaml-81-  {{- end }} 

</shell_result>


Script executed:

#!/bin/bash
# Search for any issues, comments, or discussions about metrics privacy/PII
rg -n "TODO|FIXME|XXX|HACK" charts/plane-enterprise/templates/config-secrets/metrics-config.yaml

# Search for any configuration that would allow disabling specific labels
rg -n "metrics.*label|external.*label|telemetry.*privacy" --type yaml -A2 -B2

# Check if there's any documentation about metrics telemetry
find . -type f -name "*.md" -exec grep -l "metrics\|telemetry" {} \;

Repository: makeplane/helm-charts


<shell_result>
Repository: makeplane/helm-charts
Exit code: 0

stdout:

122:          # TODO: Add production TLS configuration
charts/plane-enterprise/templates/config-secrets/metrics-config.yaml-20-            scrape_interval: {{ .Values.metrics.agent.scrape_interval | default "300s" }}
charts/plane-enterprise/templates/config-secrets/metrics-config.yaml-21-            evaluation_interval: {{ .Values.metrics.agent.scrape_interval | default "300s" }}
charts/plane-enterprise/templates/config-secrets/metrics-config.yaml:22:            external_labels:
charts/plane-enterprise/templates/config-secrets/metrics-config.yaml-23-              # Multi-tenant identification
charts/plane-enterprise/templates/config-secrets/metrics-config.yaml-24-              plane_installation_uuid: "{{ include "plane.metrics.installationUUID" . }}"

</shell_result>


Confirm license domain is exported to external telemetry without privacy controls or documentation.

The concern is valid. license_domain contains the customer's fully-qualified domain name (FQDN, e.g., "plane.company.com") and is unconditionally exported as an external label in Prometheus metrics sent to the configured telemetry endpoint. This is customer-identifying information with no:

  • Privacy policy or data retention terms documented
  • Configuration flag to disable this specific label (metrics enable/disable is all-or-nothing)
  • Acknowledgment of GDPR/CCPA implications

The label should be:

  • Removed entirely,
  • Made optional via a feature flag, or
  • Explicitly documented as a known data export in compliance documentation (README or PRIVACY policy file).
🤖 Prompt for AI Agents
In charts/plane-enterprise/templates/config-secrets/metrics-config.yaml around
line 28, the template unconditionally exports license_domain (customer FQDN) as
an external Prometheus label; change the template to only include license_domain
when a new Helm value flag (e.g., .Values.license.exportLicenseDomain) is
explicitly true (default false), add that flag to
charts/plane-enterprise/values.yaml with default: exportLicenseDomain: false and
a clear comment, and update README and PRIVACY docs to document that exporting
the license domain is an opt-in telemetry feature and the retention/usage
policy; also run helm lint and test rendering to verify the label is omitted by
default.

scrape_configs:
# cAdvisor metrics (container resource usage for this namespace only)
- job_name: 'kubernetes-cadvisor'
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
insecure_skip_verify: true
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs:
- role: node
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__
replacement: kubernetes.default.svc:443
- source_labels: [__meta_kubernetes_node_name]
regex: (.+)
target_label: __metrics_path__
replacement: /api/v1/nodes/$1/proxy/metrics/cadvisor
# Filter metrics to only include containers in our namespace
metric_relabel_configs:
- source_labels: [namespace]
action: keep
regex: {{ .Release.Namespace }}
- source_labels: [namespace]
action: drop
regex: ^$
# Plane API /metrics endpoint - scrape service
- job_name: 'plane-api'
kubernetes_sd_configs:
- role: service
namespaces:
names:
- {{ .Release.Namespace }}
relabel_configs:
# Only scrape the API service
- source_labels: [__meta_kubernetes_service_name]
action: keep
regex: {{ .Release.Name }}-api
# Set metrics path to /metrics
- target_label: __metrics_path__
replacement: /metrics
# Set port to 8000 (API service port)
- source_labels: [__address__]
target_label: __address__
regex: ([^:]+)(?::\d+)?
replacement: $1:8000
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_service_name]
action: replace
target_label: kubernetes_service_name
processors:
batch:
timeout: {{ .Values.metrics.agent.batch.timeout | default "60s" }}
send_batch_size: {{ .Values.metrics.agent.batch.send_batch_size | default 4096 }}
memory_limiter:
limit_mib: {{ regexReplaceAll "Mi|Gi" (.Values.metrics.agent.memoryLimit | default "256Mi") "" | int }}
spike_limit_mib: {{ div (regexReplaceAll "Mi|Gi" (.Values.metrics.agent.memoryLimit | default "256Mi") "") 4 | int }}
check_interval: 0.1s
# Resource processing to add tenant identification
resource:
attributes:
- key: plane.installation.uuid
value: "{{ include "plane.metrics.installationUUID" . }}"
action: upsert
- key: plane.installation.type
value: "{{ .Values.metrics.installation.type | default "kubernetes" }}"
action: upsert
- key: plane.installation.cluster
value: "{{ .Values.metrics.installation.cluster_name | default "default" }}"
action: upsert
exporters:
# Export to remote telemetry server
{{- if .Values.metrics.telemetry.http_endpoint }}
{{- $cleanEndpoint := .Values.metrics.telemetry.http_endpoint | trimPrefix "https://" | trimPrefix "http://" | trimSuffix "/v1/traces" | trimSuffix "/v1/metrics" }}
{{- $endpointWithPort := $cleanEndpoint }}
{{- if not (contains ":" $cleanEndpoint) }}
{{- $endpointWithPort = printf "%s:443" $cleanEndpoint }}
{{- end }}
otlp/http:
endpoint: {{ $endpointWithPort }}
compression: gzip
tls:
# TODO: Add production TLS configuration
insecure: true
retry_on_failure:
enabled: true
initial_interval: 5s
max_interval: 30s
max_elapsed_time: 300s
sending_queue:
enabled: true
num_consumers: 4
queue_size: 100
headers:
{{- range $key, $value := .Values.metrics.telemetry.headers }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- end }}
{{- if .Values.metrics.agent.debug.enabled }}
# Debug logging (development only)
logging:
loglevel: debug
sampling_initial: 5
sampling_thereafter: 200
{{- end }}
extensions:
health_check:
endpoint: 0.0.0.0:13133
pprof:
endpoint: 0.0.0.0:1777
service:
extensions: [health_check, pprof]
pipelines:
metrics:
receivers: [prometheus]
processors: [memory_limiter, resource, batch]
exporters: [
{{- if .Values.metrics.telemetry.http_endpoint }}otlp/http{{- if .Values.metrics.agent.debug.enabled }},{{- end }}{{- end }}
{{- if .Values.metrics.agent.debug.enabled }}logging{{- end }}
]
{{- end }}

59 changes: 58 additions & 1 deletion charts/plane-enterprise/templates/service-account.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,61 @@ metadata:
{{- if .Values.dockerRegistry.enabled }}
imagePullSecrets:
- name: {{ .Release.Name }}-docker-registry-credentials
{{- end}}
{{- end}}

{{- if .Values.metrics.enabled }}
---
# ClusterRole for Kubernetes metrics scraping
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ .Release.Name }}-metrics-reader
labels:
app.kubernetes.io/name: plane-enterprise
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: metrics-agent
rules:
- apiGroups: [""]
resources:
- nodes
- nodes/proxy
- nodes/metrics
- services
- endpoints
- pods
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources:
- deployments
- replicasets
- daemonsets
- statefulsets
verbs: ["get", "list", "watch"]
- apiGroups: ["extensions"]
resources:
- deployments
- replicasets
- daemonsets
verbs: ["get", "list", "watch"]
- nonResourceURLs: ["/metrics"]
verbs: ["get"]

---
# ClusterRoleBinding for metrics collection
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ .Release.Name }}-metrics-reader
labels:
app.kubernetes.io/name: plane-enterprise
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: metrics-agent
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ .Release.Name }}-metrics-reader
subjects:
- kind: ServiceAccount
name: {{ .Release.Name }}-srv-account
namespace: {{ .Release.Namespace }}
{{- end }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
{{- if .Values.metrics.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: {{ .Release.Namespace }}
name: {{ .Release.Name }}-metrics-agent
labels:
app.kubernetes.io/name: plane-enterprise
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: metrics-agent
spec:
replicas: {{ .Values.metrics.agent.replicas | default 1 }}
selector:
matchLabels:
app.kubernetes.io/name: plane-enterprise
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: metrics-agent
template:
metadata:
labels:
app.kubernetes.io/name: plane-enterprise
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: metrics-agent
annotations:
checksum/config: {{ include (print $.Template.BasePath "/config-secrets/metrics-config.yaml") . | sha256sum }}
spec:
serviceAccountName: {{ .Release.Name }}-srv-account
containers:
- name: otel-agent
image: {{ .Values.metrics.agent.image }}:{{ .Values.metrics.agent.tag }}
imagePullPolicy: {{ .Values.metrics.agent.imagePullPolicy | default "IfNotPresent" }}
args:
- --config=/etc/otel-agent/agent-config.yaml
ports:
- name: metrics
containerPort: 8888
protocol: TCP
- name: health
containerPort: 13133
protocol: TCP
livenessProbe:
httpGet:
path: /
port: health
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 5
readinessProbe:
httpGet:
path: /
port: health
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
resources:
limits:
memory: {{ .Values.metrics.agent.memoryLimit | default "256Mi" }}
cpu: {{ .Values.metrics.agent.cpuLimit | default "100m" }}
requests:
memory: {{ div (regexReplaceAll "Mi|Gi" (.Values.metrics.agent.memoryLimit | default "256Mi") "") 2 | printf "%dMi" }}
cpu: {{ div (regexReplaceAll "m" (.Values.metrics.agent.cpuLimit | default "100m") "") 2 | printf "%dm" }}
volumeMounts:
- name: agent-config
mountPath: /etc/otel-agent
readOnly: true
env:
- name: PLANE_INSTALLATION_UUID
value: {{ include "plane.metrics.installationUUID" . | quote }}
- name: PLANE_INSTALLATION_TYPE
value: {{ .Values.metrics.installation.type | default "kubernetes" | quote }}
- name: KUBERNETES_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: KUBERNETES_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: KUBERNETES_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
volumes:
- name: agent-config
configMap:
name: {{ .Release.Name }}-metrics-config
{{- end }}

31 changes: 31 additions & 0 deletions charts/plane-enterprise/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -396,3 +396,34 @@ extraEnv: []
# value: "http://proxy.example.com:8080"
# - name: NO_PROXY
# value: "localhost,127.0.0.1,.example.com"

# Metrics collection configuration
metrics:
enabled: true

# Installation identification
installation:
uuid: "" # Auto-generated if empty
type: "kubernetes"
cluster_name: "default"
plane_version: "" # Optional

# Telemetry server configuration
telemetry:
http_endpoint: "https://v2.metrics.plane.so/v1/traces" # Full URL (do not include :443)
headers: {}

# Agent configuration
agent:
image: "otel/opentelemetry-collector-contrib"
tag: "0.110.0"
imagePullPolicy: "IfNotPresent"
replicas: 1
memoryLimit: "256Mi"
cpuLimit: "100m"
scrape_interval: "300s" # 5 minutes
batch:
timeout: "300s" # Publish every 5 minutes
send_batch_size: 4096
debug:
enabled: false