From 50f75ee19ffe7c43fc1d40537d4a783e5732865f Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Wed, 11 Mar 2026 16:15:27 +0100 Subject: [PATCH 01/34] feat: add new must-gather chart --- charts/must-gather/.helmignore | 23 +++ charts/must-gather/Chart.yaml | 26 ++++ charts/must-gather/templates/NOTES.txt | 42 ++++++ charts/must-gather/templates/_helpers.tpl | 62 ++++++++ .../templates/data-retriever-pod.yaml | 50 +++++++ charts/must-gather/templates/job.yaml | 126 ++++++++++++++++ charts/must-gather/templates/pvc.yaml | 15 ++ charts/must-gather/templates/rbac.yaml | 58 +++++++ .../must-gather/templates/serviceaccount.yaml | 13 ++ charts/must-gather/values.yaml | 141 ++++++++++++++++++ 10 files changed, 556 insertions(+) create mode 100644 charts/must-gather/.helmignore create mode 100644 charts/must-gather/Chart.yaml create mode 100644 charts/must-gather/templates/NOTES.txt create mode 100644 charts/must-gather/templates/_helpers.tpl create mode 100644 charts/must-gather/templates/data-retriever-pod.yaml create mode 100644 charts/must-gather/templates/job.yaml create mode 100644 charts/must-gather/templates/pvc.yaml create mode 100644 charts/must-gather/templates/rbac.yaml create mode 100644 charts/must-gather/templates/serviceaccount.yaml create mode 100644 charts/must-gather/values.yaml diff --git a/charts/must-gather/.helmignore b/charts/must-gather/.helmignore new file mode 100644 index 00000000..0e8a0eb3 --- /dev/null +++ b/charts/must-gather/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/must-gather/Chart.yaml b/charts/must-gather/Chart.yaml new file mode 100644 index 00000000..5d455b4b --- /dev/null +++ b/charts/must-gather/Chart.yaml @@ -0,0 +1,26 @@ +apiVersion: v2 +name: rhdh-must-gather +description: A Helm chart for deploying the RHDH Must-Gather diagnostic tool on Kubernetes + +type: application + +# Chart version - increment for chart changes +version: 0.1.0 + +# Application version - matches the must-gather tool version +appVersion: "1.0.0" + +keywords: + - rhdh + - red-hat-developer-hub + - backstage + - diagnostics + - must-gather + - troubleshooting + +home: https://github.com/redhat-developer/rhdh-must-gather +sources: + - https://github.com/redhat-developer/rhdh-must-gather + +maintainers: + - name: Red Hat Developer Hub Team diff --git a/charts/must-gather/templates/NOTES.txt b/charts/must-gather/templates/NOTES.txt new file mode 100644 index 00000000..e466a3f6 --- /dev/null +++ b/charts/must-gather/templates/NOTES.txt @@ -0,0 +1,42 @@ +RHDH Must-Gather has been deployed! + +To retrieve the gathered diagnostic data: + +1. Wait for the job to complete: + kubectl -n {{ .Release.Namespace }} wait --for=condition=complete job/{{ include "rhdh-must-gather.fullname" . }} --timeout={{ .Values.job.activeDeadlineSeconds }}s + +{{- if .Values.dataRetriever.enabled }} + +2. Wait for the data retriever pod to be ready: + kubectl -n {{ .Release.Namespace }} wait --for=condition=ready pod/{{ include "rhdh-must-gather.fullname" . }}-data-retriever --timeout=60s + +3. Download the gathered data: + kubectl -n {{ .Release.Namespace }} exec {{ include "rhdh-must-gather.fullname" . }}-data-retriever -- tar czf - -C /data . > rhdh-must-gather-output.tar.gz + +{{- else }} + +2. Find the completed job pod: + kubectl -n {{ .Release.Namespace }} get pods -l app.kubernetes.io/name={{ include "rhdh-must-gather.name" . }},app.kubernetes.io/component=gather + +3. Copy the data from the PVC (you may need to create a temporary pod to access it) + +{{- end }} + +4. Clean up the must-gather resources: + helm uninstall {{ .Release.Name }} -n {{ .Release.Namespace }} + +Configuration used: + - Log level: {{ .Values.gather.logLevel }} + - Job timeout: {{ .Values.job.activeDeadlineSeconds }}s + - Storage size: {{ .Values.persistence.size }} +{{- if .Values.gather.namespaces }} + - Target namespaces: {{ .Values.gather.namespaces }} +{{- end }} +{{- if .Values.gather.withSecrets }} + - Secrets collection: enabled (sanitized) +{{- end }} +{{- if .Values.gather.withHeapDumps }} + - Heap dump collection: enabled +{{- end }} + +For more information, visit: https://github.com/redhat-developer/rhdh-must-gather diff --git a/charts/must-gather/templates/_helpers.tpl b/charts/must-gather/templates/_helpers.tpl new file mode 100644 index 00000000..bb199ec8 --- /dev/null +++ b/charts/must-gather/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "rhdh-must-gather.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "rhdh-must-gather.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "rhdh-must-gather.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "rhdh-must-gather.labels" -}} +helm.sh/chart: {{ include "rhdh-must-gather.chart" . }} +{{ include "rhdh-must-gather.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "rhdh-must-gather.selectorLabels" -}} +app.kubernetes.io/name: {{ include "rhdh-must-gather.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "rhdh-must-gather.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "rhdh-must-gather.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/charts/must-gather/templates/data-retriever-pod.yaml b/charts/must-gather/templates/data-retriever-pod.yaml new file mode 100644 index 00000000..a749f69a --- /dev/null +++ b/charts/must-gather/templates/data-retriever-pod.yaml @@ -0,0 +1,50 @@ +{{- if .Values.dataRetriever.enabled -}} +apiVersion: v1 +kind: Pod +metadata: + name: {{ include "rhdh-must-gather.fullname" . }}-data-retriever + labels: + {{- include "rhdh-must-gather.labels" . | nindent 4 }} + app.kubernetes.io/component: data-retriever +spec: + restartPolicy: Never + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 4 }} + {{- end }} + affinity: + podAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + {{- include "rhdh-must-gather.selectorLabels" . | nindent 14 }} + app.kubernetes.io/component: gather + topologyKey: kubernetes.io/hostname + containers: + - name: data-retriever + image: "{{ .Values.dataRetriever.image.repository }}:{{ .Values.dataRetriever.image.tag }}" + imagePullPolicy: {{ .Values.dataRetriever.image.pullPolicy }} + command: + - sleep + - infinity + volumeMounts: + - name: must-gather-output + mountPath: /data + readOnly: true + {{- with .Values.dataRetriever.resources }} + resources: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: must-gather-output + persistentVolumeClaim: + claimName: {{ include "rhdh-must-gather.fullname" . }}-pvc + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/must-gather/templates/job.yaml b/charts/must-gather/templates/job.yaml new file mode 100644 index 00000000..317a5723 --- /dev/null +++ b/charts/must-gather/templates/job.yaml @@ -0,0 +1,126 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "rhdh-must-gather.fullname" . }} + labels: + {{- include "rhdh-must-gather.labels" . | nindent 4 }} + app.kubernetes.io/component: gather +spec: + activeDeadlineSeconds: {{ .Values.job.activeDeadlineSeconds }} + backoffLimit: {{ .Values.job.backoffLimit }} + {{- if .Values.job.ttlSecondsAfterFinished }} + ttlSecondsAfterFinished: {{ .Values.job.ttlSecondsAfterFinished }} + {{- end }} + template: + metadata: + labels: + {{- include "rhdh-must-gather.labels" . | nindent 8 }} + app.kubernetes.io/component: gather + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ include "rhdh-must-gather.serviceAccountName" . }} + restartPolicy: Never + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: gather + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: BASE_COLLECTION_PATH + value: "/must-gather" + - name: LOG_LEVEL + value: {{ .Values.gather.logLevel | quote }} + - name: CMD_TIMEOUT + value: {{ .Values.gather.cmdTimeout | quote }} + {{- if .Values.gather.since }} + - name: MUST_GATHER_SINCE + value: {{ .Values.gather.since | quote }} + {{- end }} + {{- if .Values.gather.sinceTime }} + - name: MUST_GATHER_SINCE_TIME + value: {{ .Values.gather.sinceTime | quote }} + {{- end }} + {{- if or .Values.gather.withSecrets .Values.gather.withHeapDumps .Values.gather.clusterInfo .Values.gather.withoutOperator .Values.gather.withoutOrchestrator .Values.gather.withoutHelm .Values.gather.withoutPlatform .Values.gather.withoutRoute .Values.gather.withoutIngress .Values.gather.withoutNamespaceInspect .Values.gather.namespaces .Values.gather.extraArgs }} + args: + {{- if .Values.gather.withSecrets }} + - "--with-secrets" + {{- end }} + {{- if .Values.gather.withHeapDumps }} + - "--with-heap-dumps" + {{- end }} + {{- if .Values.gather.clusterInfo }} + - "--cluster-info" + {{- end }} + {{- if .Values.gather.withoutOperator }} + - "--without-operator" + {{- end }} + {{- if .Values.gather.withoutOrchestrator }} + - "--without-orchestrator" + {{- end }} + {{- if .Values.gather.withoutHelm }} + - "--without-helm" + {{- end }} + {{- if .Values.gather.withoutPlatform }} + - "--without-platform" + {{- end }} + {{- if .Values.gather.withoutRoute }} + - "--without-route" + {{- end }} + {{- if .Values.gather.withoutIngress }} + - "--without-ingress" + {{- end }} + {{- if .Values.gather.withoutNamespaceInspect }} + - "--without-namespace-inspect" + {{- end }} + {{- if .Values.gather.namespaces }} + - "--namespaces" + - {{ .Values.gather.namespaces | quote }} + {{- end }} + {{- with .Values.gather.extraArgs }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- end }} + volumeMounts: + - name: output + mountPath: /must-gather + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumes: + - name: output + persistentVolumeClaim: + claimName: {{ include "rhdh-must-gather.fullname" . }}-pvc + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/must-gather/templates/pvc.yaml b/charts/must-gather/templates/pvc.yaml new file mode 100644 index 00000000..0504a3c8 --- /dev/null +++ b/charts/must-gather/templates/pvc.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "rhdh-must-gather.fullname" . }}-pvc + labels: + {{- include "rhdh-must-gather.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.persistence.accessMode }} + {{- if .Values.persistence.storageClass }} + storageClassName: {{ .Values.persistence.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.size }} diff --git a/charts/must-gather/templates/rbac.yaml b/charts/must-gather/templates/rbac.yaml new file mode 100644 index 00000000..823a4ef9 --- /dev/null +++ b/charts/must-gather/templates/rbac.yaml @@ -0,0 +1,58 @@ +{{- if .Values.rbac.create -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "rhdh-must-gather.fullname" . }} + labels: + {{- include "rhdh-must-gather.labels" . | nindent 4 }} +rules: + - apiGroups: [""] + resources: ["*"] + verbs: ["get", "list"] + - apiGroups: ["apps", "extensions"] + resources: ["*"] + verbs: ["get", "list"] + - apiGroups: ["networking.k8s.io"] + resources: ["*"] + verbs: ["get", "list"] + - apiGroups: ["rbac.authorization.k8s.io"] + resources: ["*"] + verbs: ["get", "list"] + - apiGroups: ["storage.k8s.io"] + resources: ["*"] + verbs: ["get", "list"] + - apiGroups: ["apiextensions.k8s.io"] + resources: ["customresourcedefinitions"] + verbs: ["get", "list"] + - apiGroups: ["rhdh.redhat.com"] + resources: ["*"] + verbs: ["get", "list"] + - apiGroups: ["operators.coreos.com"] + resources: ["*"] + verbs: ["get", "list"] + # Orchestrator-related permissions + - apiGroups: ["sonataflow.org"] + resources: ["sonataflowplatforms", "sonataflows", "sonataflowclusterplatforms", "sonataflowbuilds"] + verbs: ["get", "list"] + - apiGroups: ["operator.knative.dev"] + resources: ["knativeservings", "knativeeventings"] + verbs: ["get", "list"] + - apiGroups: ["operator.serverless.openshift.io"] + resources: ["knativekafkas"] + verbs: ["get", "list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "rhdh-must-gather.fullname" . }} + labels: + {{- include "rhdh-must-gather.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "rhdh-must-gather.fullname" . }} +subjects: + - kind: ServiceAccount + name: {{ include "rhdh-must-gather.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/must-gather/templates/serviceaccount.yaml b/charts/must-gather/templates/serviceaccount.yaml new file mode 100644 index 00000000..81c615af --- /dev/null +++ b/charts/must-gather/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "rhdh-must-gather.serviceAccountName" . }} + labels: + {{- include "rhdh-must-gather.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} diff --git a/charts/must-gather/values.yaml b/charts/must-gather/values.yaml new file mode 100644 index 00000000..4f8c06bb --- /dev/null +++ b/charts/must-gather/values.yaml @@ -0,0 +1,141 @@ +# Default values for rhdh-must-gather. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# Container image configuration +image: + repository: quay.io/rhdh-community/rhdh-must-gather + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "latest" + +# Secrets for pulling images from a private registry +imagePullSecrets: [] + +# Override the chart name +nameOverride: "" +fullnameOverride: "" + +# Service account configuration +serviceAccount: + # Specifies whether a service account should be created + create: true + # Automatically mount a ServiceAccount's API credentials + automount: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use + # If not set and create is true, a name is generated using the fullname template + name: "" + +# RBAC configuration +rbac: + # Create ClusterRole and ClusterRoleBinding for cluster-wide read access + create: true + +# Job configuration +job: + # Job timeout in seconds (default: 1 hour) + activeDeadlineSeconds: 3600 + # Number of retries before marking job as failed + backoffLimit: 3 + # TTL for automatic cleanup after job finishes (seconds) + # Set to a positive value to enable automatic cleanup + ttlSecondsAfterFinished: "" + +# Gather script configuration +gather: + # Log level: INFO, DEBUG, TRACE + logLevel: "INFO" + # Command timeout for individual kubectl/helm commands (seconds) + cmdTimeout: "30" + # Relative time for log collection (e.g., "2h", "30m") + since: "" + # Absolute timestamp for log collection (RFC3339 format) + sinceTime: "" + + # Optional collection features (disabled by default) + withSecrets: false + withHeapDumps: false + clusterInfo: false + + # Exclusion options (set to true to skip collection) + withoutOperator: false + withoutOrchestrator: false + withoutHelm: false + withoutPlatform: false + withoutRoute: false + withoutIngress: false + withoutNamespaceInspect: false + + # Limit collection to specific namespaces (comma-separated) + # Example: "rhdh-prod,rhdh-staging" + namespaces: "" + + # Additional custom arguments to pass to the gather script + extraArgs: [] + +# Resource requests and limits for the gather job +resources: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + +# Persistent volume configuration for storing gathered data +persistence: + # Storage size + size: 1Gi + # Storage class (empty = use cluster default) + storageClass: "" + # Access mode + accessMode: ReadWriteOnce + +# Data retriever pod configuration +# This pod allows you to retrieve the gathered data after the job completes +dataRetriever: + # Enable the data retriever pod + enabled: true + # Image for the data retriever pod + image: + repository: registry.access.redhat.com/ubi9 + tag: "latest" + pullPolicy: IfNotPresent + # Resource configuration + resources: + requests: + memory: "64Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" + +# Pod annotations +podAnnotations: {} + +# Pod labels +podLabels: {} + +# Pod security context +podSecurityContext: {} + # fsGroup: 2000 + +# Container security context +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +# Node selector for pod scheduling +nodeSelector: {} + +# Tolerations for pod scheduling +tolerations: [] + +# Affinity rules for pod scheduling +affinity: {} From b673df4617c82e1a472574960421a66c77fcb23a Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Wed, 11 Mar 2026 16:26:56 +0100 Subject: [PATCH 02/34] Add README template and apply pre-commit hooks --- charts/must-gather/Chart.yaml | 22 +++-- charts/must-gather/README.md | 128 ++++++++++++++++++++++++++++ charts/must-gather/README.md.gotmpl | 77 +++++++++++++++++ charts/must-gather/values.yaml | 110 ++++++++++++------------ 4 files changed, 277 insertions(+), 60 deletions(-) create mode 100644 charts/must-gather/README.md create mode 100644 charts/must-gather/README.md.gotmpl diff --git a/charts/must-gather/Chart.yaml b/charts/must-gather/Chart.yaml index 5d455b4b..ae1997e4 100644 --- a/charts/must-gather/Chart.yaml +++ b/charts/must-gather/Chart.yaml @@ -1,12 +1,15 @@ +annotations: + charts.openshift.io/name: Red Hat Developer Hub Must Gather + charts.openshift.io/provider: Red Hat + charts.openshift.io/archs: x86_64 + charts.openshift.io/supportURL: https://access.redhat.com/support apiVersion: v2 name: rhdh-must-gather -description: A Helm chart for deploying the RHDH Must-Gather diagnostic tool on Kubernetes +description: | + A Helm chart for deploying the RHDH Must-Gather diagnostic tool on Kubernetes type: application -# Chart version - increment for chart changes -version: 0.1.0 - # Application version - matches the must-gather tool version appVersion: "1.0.0" @@ -22,5 +25,14 @@ home: https://github.com/redhat-developer/rhdh-must-gather sources: - https://github.com/redhat-developer/rhdh-must-gather +kubeVersion: ">= 1.27.0-0" maintainers: - - name: Red Hat Developer Hub Team + - name: Red Hat + url: https://redhat.com + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +# Note that when this chart is published to https://github.com/openshift-helm-charts/charts +# it will follow the RHDH versioning 1.y.z +version: 0.1.0 diff --git a/charts/must-gather/README.md b/charts/must-gather/README.md new file mode 100644 index 00000000..30d464f7 --- /dev/null +++ b/charts/must-gather/README.md @@ -0,0 +1,128 @@ + +# Must Gather Chart for Red Hat Developer Hub (RHDH) + +![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) +![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) + +A Helm chart for deploying the RHDH Must-Gather diagnostic tool on Kubernetes + +**Homepage:** + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| Red Hat | | | + +## Source Code + +* + +## Requirements + +Kubernetes: `>= 1.27.0-0` + +## TL;DR + +```console +helm install my-rhdh-must-gather rhdh-must-gather \ + --repo https://redhat-developer.github.io/rhdh-chart \ + --version 0.1.0 +``` + +> **Tip**: List all releases using `helm list` + +## Testing a Release + +Once an Helm Release has been deployed, you can test it using the [`helm test`](https://helm.sh/docs/helm/helm_test/) command: + +```sh +helm test +``` + +This will run a simple Pod in the cluster to check that the required resources have been created. + +You can control whether to disable this test pod or you can also customize the image it leverages. +See the `test.enabled` and `test.image` parameters in the [`values.yaml`](./values.yaml) file. + +> **Tip**: Disabling the test pod will not prevent the `helm test` command from passing later on. It will simply report that no test suite is available. + +Below are a few examples: + +
+ +Disabling the test pod + +```sh +helm install \ + --set test.enabled=false +``` + +
+ +
+ +Customizing the test pod image + +```sh +helm install \ + --set test.image= +``` + +
+ +## Uninstalling the Chart + +To uninstall/delete a Helm release named `my-rhdh-must-gather`: + +```console +helm uninstall my-rhdh-must-gather +``` + +The command removes all the Kubernetes resources associated with the chart and deletes the release. + +## Values + +| Key | Description | Type | Default | +|-----|-------------|------|---------| +| affinity | Affinity rules for pod scheduling | object | `{}` | +| dataRetriever | This pod allows you to retrieve the gathered data after the job completes | object | `{"enabled":true,"image":{"pullPolicy":"IfNotPresent","repository":"registry.access.redhat.com/ubi9","tag":"latest"},"resources":{"limits":{"cpu":"100m","memory":"128Mi"},"requests":{"cpu":"50m","memory":"64Mi"}}}` | +| dataRetriever.enabled | Enable the data retriever pod | bool | `true` | +| dataRetriever.image | Image for the data retriever pod | object | `{"pullPolicy":"IfNotPresent","repository":"registry.access.redhat.com/ubi9","tag":"latest"}` | +| dataRetriever.resources | Resource configuration | object | `{"limits":{"cpu":"100m","memory":"128Mi"},"requests":{"cpu":"50m","memory":"64Mi"}}` | +| fullnameOverride | | string | `""` | +| gather | Gather script configuration | object | `{"clusterInfo":false,"cmdTimeout":"30","extraArgs":[],"logLevel":"INFO","namespaces":"","since":"","sinceTime":"","withHeapDumps":false,"withSecrets":false,"withoutHelm":false,"withoutIngress":false,"withoutNamespaceInspect":false,"withoutOperator":false,"withoutOrchestrator":false,"withoutPlatform":false,"withoutRoute":false}` | +| gather.cmdTimeout | Command timeout for individual kubectl/helm commands (seconds) | string | `"30"` | +| gather.extraArgs | Additional custom arguments to pass to the gather script | list | `[]` | +| gather.logLevel | Log level: INFO, DEBUG, TRACE | string | `"INFO"` | +| gather.namespaces | Example: "rhdh-prod,rhdh-staging" | string | `""` | +| gather.since | Relative time for log collection (e.g., "2h", "30m") | string | `""` | +| gather.sinceTime | Absolute timestamp for log collection (RFC3339 format) | string | `""` | +| gather.withSecrets | Optional collection features (disabled by default) | bool | `false` | +| gather.withoutOperator | Exclusion options (set to true to skip collection) | bool | `false` | +| image | Container image configuration | object | `{"pullPolicy":"IfNotPresent","repository":"quay.io/rhdh-community/rhdh-must-gather","tag":"latest"}` | +| imagePullSecrets | Secrets for pulling images from a private registry | list | `[]` | +| job | Job configuration | object | `{"activeDeadlineSeconds":3600,"backoffLimit":3,"ttlSecondsAfterFinished":""}` | +| job.activeDeadlineSeconds | Job timeout in seconds (default: 1 hour) | int | `3600` | +| job.backoffLimit | Number of retries before marking job as failed | int | `3` | +| job.ttlSecondsAfterFinished | Set to a positive value to enable automatic cleanup | string | `""` | +| nameOverride | Override the chart name | string | `""` | +| nodeSelector | Node selector for pod scheduling | object | `{}` | +| persistence | Persistent volume configuration for storing gathered data | object | `{"accessMode":"ReadWriteOnce","size":"1Gi","storageClass":""}` | +| persistence.accessMode | Access mode | string | `"ReadWriteOnce"` | +| persistence.size | Storage size | string | `"1Gi"` | +| persistence.storageClass | Storage class (empty = use cluster default) | string | `""` | +| podAnnotations | Pod annotations | object | `{}` | +| podLabels | Pod labels | object | `{}` | +| podSecurityContext | Pod security context | object | `{}` | +| rbac | RBAC configuration | object | `{"create":true}` | +| rbac.create | Create ClusterRole and ClusterRoleBinding for cluster-wide read access | bool | `true` | +| resources | Resource requests and limits for the gather job | object | `{"limits":{"cpu":"500m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"128Mi"}}` | +| securityContext | Container security context | object | `{}` | +| serviceAccount | Service account configuration | object | `{"annotations":{},"automount":true,"create":true,"name":""}` | +| serviceAccount.annotations | Annotations to add to the service account | object | `{}` | +| serviceAccount.automount | Automatically mount a ServiceAccount's API credentials | bool | `true` | +| serviceAccount.create | Specifies whether a service account should be created | bool | `true` | +| serviceAccount.name | If not set and create is true, a name is generated using the fullname template | string | `""` | +| tolerations | Tolerations for pod scheduling | list | `[]` | + diff --git a/charts/must-gather/README.md.gotmpl b/charts/must-gather/README.md.gotmpl new file mode 100644 index 00000000..b713fc97 --- /dev/null +++ b/charts/must-gather/README.md.gotmpl @@ -0,0 +1,77 @@ +# Must Gather Chart for Red Hat Developer Hub (RHDH) + +{{ template "chart.deprecationWarning" . }} + +{{ template "chart.versionBadge" . }} +{{ template "chart.typeBadge" . }} + +{{ template "chart.description" . }} + +{{ template "chart.homepageLine" . }} + +{{ template "chart.maintainersSection" . }} + +{{ template "chart.sourcesSection" . }} + +{{ template "chart.requirementsSection" . }} + +## TL;DR + +```console +helm install my-rhdh-must-gather rhdh-must-gather \ + --repo https://redhat-developer.github.io/rhdh-chart \ + --version {{ template "chart.version" . }} +``` + +> **Tip**: List all releases using `helm list` + +## Testing a Release + +Once an Helm Release has been deployed, you can test it using the [`helm test`](https://helm.sh/docs/helm/helm_test/) command: + +```sh +helm test +``` + +This will run a simple Pod in the cluster to check that the required resources have been created. + +You can control whether to disable this test pod or you can also customize the image it leverages. +See the `test.enabled` and `test.image` parameters in the [`values.yaml`](./values.yaml) file. + +> **Tip**: Disabling the test pod will not prevent the `helm test` command from passing later on. It will simply report that no test suite is available. + +Below are a few examples: + +
+ +Disabling the test pod + +```sh +helm install \ + --set test.enabled=false +``` + +
+ +
+ +Customizing the test pod image + +```sh +helm install \ + --set test.image= +``` + +
+ +## Uninstalling the Chart + +To uninstall/delete a Helm release named `my-rhdh-must-gather`: + +```console +helm uninstall my-rhdh-must-gather +``` + +The command removes all the Kubernetes resources associated with the chart and deletes the release. + +{{ template "chart.valuesSection" . }} diff --git a/charts/must-gather/values.yaml b/charts/must-gather/values.yaml index 4f8c06bb..1b277e04 100644 --- a/charts/must-gather/values.yaml +++ b/charts/must-gather/values.yaml @@ -1,65 +1,65 @@ -# Default values for rhdh-must-gather. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. +# -- Default values for rhdh-must-gather. +# -- This is a YAML-formatted file. +# -- Declare variables to be passed into your templates. -# Container image configuration +# -- Container image configuration image: repository: quay.io/rhdh-community/rhdh-must-gather pullPolicy: IfNotPresent - # Overrides the image tag whose default is the chart appVersion. + # -- -- Overrides the image tag whose default is the chart appVersion. tag: "latest" -# Secrets for pulling images from a private registry +# -- Secrets for pulling images from a private registry imagePullSecrets: [] -# Override the chart name +# -- Override the chart name nameOverride: "" fullnameOverride: "" -# Service account configuration +# -- Service account configuration serviceAccount: - # Specifies whether a service account should be created + # -- Specifies whether a service account should be created create: true - # Automatically mount a ServiceAccount's API credentials + # -- Automatically mount a ServiceAccount's API credentials automount: true - # Annotations to add to the service account + # -- Annotations to add to the service account annotations: {} - # The name of the service account to use - # If not set and create is true, a name is generated using the fullname template + # -- The name of the service account to use + # -- If not set and create is true, a name is generated using the fullname template name: "" -# RBAC configuration +# -- RBAC configuration rbac: - # Create ClusterRole and ClusterRoleBinding for cluster-wide read access + # -- Create ClusterRole and ClusterRoleBinding for cluster-wide read access create: true -# Job configuration +# -- Job configuration job: - # Job timeout in seconds (default: 1 hour) + # -- Job timeout in seconds (default: 1 hour) activeDeadlineSeconds: 3600 - # Number of retries before marking job as failed + # -- Number of retries before marking job as failed backoffLimit: 3 - # TTL for automatic cleanup after job finishes (seconds) - # Set to a positive value to enable automatic cleanup + # -- TTL for automatic cleanup after job finishes (seconds) + # -- Set to a positive value to enable automatic cleanup ttlSecondsAfterFinished: "" -# Gather script configuration +# -- Gather script configuration gather: - # Log level: INFO, DEBUG, TRACE + # -- Log level: INFO, DEBUG, TRACE logLevel: "INFO" - # Command timeout for individual kubectl/helm commands (seconds) + # -- Command timeout for individual kubectl/helm commands (seconds) cmdTimeout: "30" - # Relative time for log collection (e.g., "2h", "30m") + # -- Relative time for log collection (e.g., "2h", "30m") since: "" - # Absolute timestamp for log collection (RFC3339 format) + # -- Absolute timestamp for log collection (RFC3339 format) sinceTime: "" - # Optional collection features (disabled by default) + # -- Optional collection features (disabled by default) withSecrets: false withHeapDumps: false clusterInfo: false - # Exclusion options (set to true to skip collection) + # -- Exclusion options (set to true to skip collection) withoutOperator: false withoutOrchestrator: false withoutHelm: false @@ -68,14 +68,14 @@ gather: withoutIngress: false withoutNamespaceInspect: false - # Limit collection to specific namespaces (comma-separated) - # Example: "rhdh-prod,rhdh-staging" + # -- Limit collection to specific namespaces (comma-separated) + # -- Example: "rhdh-prod,rhdh-staging" namespaces: "" - # Additional custom arguments to pass to the gather script + # -- Additional custom arguments to pass to the gather script extraArgs: [] -# Resource requests and limits for the gather job +# -- Resource requests and limits for the gather job resources: requests: memory: "128Mi" @@ -84,26 +84,26 @@ resources: memory: "512Mi" cpu: "500m" -# Persistent volume configuration for storing gathered data +# -- Persistent volume configuration for storing gathered data persistence: - # Storage size + # -- Storage size size: 1Gi - # Storage class (empty = use cluster default) + # -- Storage class (empty = use cluster default) storageClass: "" - # Access mode + # -- Access mode accessMode: ReadWriteOnce -# Data retriever pod configuration -# This pod allows you to retrieve the gathered data after the job completes +# -- Data retriever pod configuration +# -- This pod allows you to retrieve the gathered data after the job completes dataRetriever: - # Enable the data retriever pod + # -- Enable the data retriever pod enabled: true - # Image for the data retriever pod + # -- Image for the data retriever pod image: repository: registry.access.redhat.com/ubi9 tag: "latest" pullPolicy: IfNotPresent - # Resource configuration + # -- Resource configuration resources: requests: memory: "64Mi" @@ -112,30 +112,30 @@ dataRetriever: memory: "128Mi" cpu: "100m" -# Pod annotations +# -- Pod annotations podAnnotations: {} -# Pod labels +# -- Pod labels podLabels: {} -# Pod security context +# -- Pod security context podSecurityContext: {} - # fsGroup: 2000 + # -- fsGroup: 2000 -# Container security context +# -- Container security context securityContext: {} - # capabilities: - # drop: - # - ALL - # readOnlyRootFilesystem: true - # runAsNonRoot: true - # runAsUser: 1000 - -# Node selector for pod scheduling + # -- capabilities: + # -- drop: + # -- - ALL + # -- readOnlyRootFilesystem: true + # -- runAsNonRoot: true + # -- runAsUser: 1000 + +# -- Node selector for pod scheduling nodeSelector: {} -# Tolerations for pod scheduling +# -- Tolerations for pod scheduling tolerations: [] -# Affinity rules for pod scheduling +# -- Affinity rules for pod scheduling affinity: {} From c36f8a06d4d44079c7f59df2d13b7f37ff649592 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Wed, 11 Mar 2026 16:39:18 +0100 Subject: [PATCH 03/34] add test hook template --- charts/must-gather/README.md | 7 +- charts/must-gather/README.md.gotmpl | 4 +- charts/must-gather/templates/tests/test.yaml | 110 +++++++++++++++++++ charts/must-gather/values.yaml | 10 ++ 4 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 charts/must-gather/templates/tests/test.yaml diff --git a/charts/must-gather/README.md b/charts/must-gather/README.md index 30d464f7..d3fd1de3 100644 --- a/charts/must-gather/README.md +++ b/charts/must-gather/README.md @@ -30,11 +30,13 @@ helm install my-rhdh-must-gather rhdh-must-gather \ --version 0.1.0 ``` +Then follow the instructions that will be printed to retrieve the gathered data. + > **Tip**: List all releases using `helm list` ## Testing a Release -Once an Helm Release has been deployed, you can test it using the [`helm test`](https://helm.sh/docs/helm/helm_test/) command: +Once a Helm Release has been deployed, you can test it using the [`helm test`](https://helm.sh/docs/helm/helm_test/) command: ```sh helm test @@ -124,5 +126,8 @@ The command removes all the Kubernetes resources associated with the chart and d | serviceAccount.automount | Automatically mount a ServiceAccount's API credentials | bool | `true` | | serviceAccount.create | Specifies whether a service account should be created | bool | `true` | | serviceAccount.name | If not set and create is true, a name is generated using the fullname template | string | `""` | +| test | Helm test configuration | object | `{"enabled":true,"image":{"pullPolicy":"IfNotPresent","repository":"bitnami/kubectl","tag":"latest"}}` | +| test.enabled | Enable the Helm test | bool | `true` | +| test.image | Image for the test pod | object | `{"pullPolicy":"IfNotPresent","repository":"bitnami/kubectl","tag":"latest"}` | | tolerations | Tolerations for pod scheduling | list | `[]` | diff --git a/charts/must-gather/README.md.gotmpl b/charts/must-gather/README.md.gotmpl index b713fc97..088439d1 100644 --- a/charts/must-gather/README.md.gotmpl +++ b/charts/must-gather/README.md.gotmpl @@ -23,11 +23,13 @@ helm install my-rhdh-must-gather rhdh-must-gather \ --version {{ template "chart.version" . }} ``` +Then follow the instructions that will be printed to retrieve the gathered data. + > **Tip**: List all releases using `helm list` ## Testing a Release -Once an Helm Release has been deployed, you can test it using the [`helm test`](https://helm.sh/docs/helm/helm_test/) command: +Once a Helm Release has been deployed, you can test it using the [`helm test`](https://helm.sh/docs/helm/helm_test/) command: ```sh helm test diff --git a/charts/must-gather/templates/tests/test.yaml b/charts/must-gather/templates/tests/test.yaml new file mode 100644 index 00000000..13689339 --- /dev/null +++ b/charts/must-gather/templates/tests/test.yaml @@ -0,0 +1,110 @@ +{{- if and .Values.test.enabled .Values.dataRetriever.enabled -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "rhdh-must-gather.fullname" . }}-test + labels: + {{- include "rhdh-must-gather.labels" . | nindent 4 }} + app.kubernetes.io/component: test + annotations: + helm.sh/hook: test + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded + helm.sh/hook-weight: "-1" +rules: + - apiGroups: ["batch"] + resources: ["jobs"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "watch", "list"] + - apiGroups: [""] + resources: ["pods/exec"] + verbs: ["create"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "rhdh-must-gather.fullname" . }}-test + labels: + {{- include "rhdh-must-gather.labels" . | nindent 4 }} + app.kubernetes.io/component: test + annotations: + helm.sh/hook: test + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded + helm.sh/hook-weight: "-1" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "rhdh-must-gather.fullname" . }}-test +subjects: + - kind: ServiceAccount + name: {{ include "rhdh-must-gather.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +--- +apiVersion: v1 +kind: Pod +metadata: + name: {{ include "rhdh-must-gather.fullname" . }}-test + labels: + {{- include "rhdh-must-gather.labels" . | nindent 4 }} + app.kubernetes.io/component: test + annotations: + helm.sh/hook: test + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded +spec: + serviceAccountName: {{ include "rhdh-must-gather.serviceAccountName" . }} + restartPolicy: Never + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 4 }} + {{- end }} + containers: + - name: test + image: "{{ .Values.test.image.repository }}:{{ .Values.test.image.tag }}" + imagePullPolicy: {{ .Values.test.image.pullPolicy }} + env: + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: JOB_NAME + value: {{ include "rhdh-must-gather.fullname" . }} + - name: DATA_RETRIEVER_POD + value: {{ include "rhdh-must-gather.fullname" . }}-data-retriever + - name: JOB_TIMEOUT + value: "{{ .Values.job.activeDeadlineSeconds }}" + command: ["/bin/sh", "-c"] + args: + - | + set -e + echo "Step 1: Waiting for the gather job to complete..." + kubectl -n "$NAMESPACE" wait --for=condition=complete "job/$JOB_NAME" --timeout="${JOB_TIMEOUT}s" + + echo "Step 2: Waiting for the data retriever pod to be ready..." + kubectl -n "$NAMESPACE" wait --for=condition=ready "pod/$DATA_RETRIEVER_POD" --timeout=60s + + echo "Step 3: Retrieving gathered data from the data retriever pod..." + kubectl -n "$NAMESPACE" exec "$DATA_RETRIEVER_POD" -- tar czf - -C /data . > /tmp/rhdh-must-gather-output.tar.gz + + size=$(stat -c%s /tmp/rhdh-must-gather-output.tar.gz 2>/dev/null || stat -f%z /tmp/rhdh-must-gather-output.tar.gz 2>/dev/null) + if [ "$size" -le 0 ] 2>/dev/null; then + echo "FAIL: retrieved archive is empty" + exit 1 + fi + echo "PASS: gathered data retrieved successfully (${size} bytes)" + resources: + requests: + memory: "64Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/must-gather/values.yaml b/charts/must-gather/values.yaml index 1b277e04..7fb44060 100644 --- a/charts/must-gather/values.yaml +++ b/charts/must-gather/values.yaml @@ -112,6 +112,16 @@ dataRetriever: memory: "128Mi" cpu: "100m" +# -- Helm test configuration +test: + # -- Enable the Helm test + enabled: true + # -- Image for the test pod + image: + repository: bitnami/kubectl + tag: "latest" + pullPolicy: IfNotPresent + # -- Pod annotations podAnnotations: {} From a1e7a954bb86a621e867a657e5463c6d7bd11d17 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Wed, 11 Mar 2026 16:51:01 +0100 Subject: [PATCH 04/34] ci: add a set of relevant test values files for CI --- charts/must-gather/ci/default-values.yaml | 1 + .../ci/with-data-retriever-disabled-values.yaml | 3 +++ charts/must-gather/ci/with-minimal-collection-values.yaml | 7 +++++++ charts/must-gather/ci/with-namespace-scoped-values.yaml | 4 ++++ .../ci/with-secrets-and-cluster-info-values.yaml | 5 +++++ charts/must-gather/ci/with-test-disabled-values.yaml | 3 +++ 6 files changed, 23 insertions(+) create mode 100644 charts/must-gather/ci/default-values.yaml create mode 100644 charts/must-gather/ci/with-data-retriever-disabled-values.yaml create mode 100644 charts/must-gather/ci/with-minimal-collection-values.yaml create mode 100644 charts/must-gather/ci/with-namespace-scoped-values.yaml create mode 100644 charts/must-gather/ci/with-secrets-and-cluster-info-values.yaml create mode 100644 charts/must-gather/ci/with-test-disabled-values.yaml diff --git a/charts/must-gather/ci/default-values.yaml b/charts/must-gather/ci/default-values.yaml new file mode 100644 index 00000000..f819fdc3 --- /dev/null +++ b/charts/must-gather/ci/default-values.yaml @@ -0,0 +1 @@ +# Default values — tests the chart with all defaults diff --git a/charts/must-gather/ci/with-data-retriever-disabled-values.yaml b/charts/must-gather/ci/with-data-retriever-disabled-values.yaml new file mode 100644 index 00000000..5d88433a --- /dev/null +++ b/charts/must-gather/ci/with-data-retriever-disabled-values.yaml @@ -0,0 +1,3 @@ +# Data retriever disabled — users retrieve data directly from the PVC +dataRetriever: + enabled: false diff --git a/charts/must-gather/ci/with-minimal-collection-values.yaml b/charts/must-gather/ci/with-minimal-collection-values.yaml new file mode 100644 index 00000000..9c1f242f --- /dev/null +++ b/charts/must-gather/ci/with-minimal-collection-values.yaml @@ -0,0 +1,7 @@ +# Minimal collection — skip optional components to speed up gathering +gather: + withoutOperator: true + withoutOrchestrator: true + withoutHelm: true + withoutRoute: true + withoutIngress: true diff --git a/charts/must-gather/ci/with-namespace-scoped-values.yaml b/charts/must-gather/ci/with-namespace-scoped-values.yaml new file mode 100644 index 00000000..99db13cb --- /dev/null +++ b/charts/must-gather/ci/with-namespace-scoped-values.yaml @@ -0,0 +1,4 @@ +# Scope collection to specific namespaces +gather: + namespaces: "rhdh-prod,rhdh-staging" + since: "2h" diff --git a/charts/must-gather/ci/with-secrets-and-cluster-info-values.yaml b/charts/must-gather/ci/with-secrets-and-cluster-info-values.yaml new file mode 100644 index 00000000..f52888cd --- /dev/null +++ b/charts/must-gather/ci/with-secrets-and-cluster-info-values.yaml @@ -0,0 +1,5 @@ +# Collect secrets (sanitized) and cluster-level info +gather: + withSecrets: true + clusterInfo: true + logLevel: "DEBUG" diff --git a/charts/must-gather/ci/with-test-disabled-values.yaml b/charts/must-gather/ci/with-test-disabled-values.yaml new file mode 100644 index 00000000..695bbdeb --- /dev/null +++ b/charts/must-gather/ci/with-test-disabled-values.yaml @@ -0,0 +1,3 @@ +# Helm test disabled +test: + enabled: false From fe503b6f909629dc8739beb21b3b895df5bdcf0d Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Wed, 11 Mar 2026 17:16:12 +0100 Subject: [PATCH 05/34] add values.schema.tmpl.json and generate from pre-commit hooks --- charts/must-gather/README.md | 10 +- charts/must-gather/values.schema.json | 1511 ++++++++++++++++++++ charts/must-gather/values.schema.tmpl.json | 467 ++++++ charts/must-gather/values.yaml | 6 +- 4 files changed, 1986 insertions(+), 8 deletions(-) create mode 100644 charts/must-gather/values.schema.json create mode 100644 charts/must-gather/values.schema.tmpl.json diff --git a/charts/must-gather/README.md b/charts/must-gather/README.md index d3fd1de3..4107c200 100644 --- a/charts/must-gather/README.md +++ b/charts/must-gather/README.md @@ -88,9 +88,9 @@ The command removes all the Kubernetes resources associated with the chart and d | Key | Description | Type | Default | |-----|-------------|------|---------| | affinity | Affinity rules for pod scheduling | object | `{}` | -| dataRetriever | This pod allows you to retrieve the gathered data after the job completes | object | `{"enabled":true,"image":{"pullPolicy":"IfNotPresent","repository":"registry.access.redhat.com/ubi9","tag":"latest"},"resources":{"limits":{"cpu":"100m","memory":"128Mi"},"requests":{"cpu":"50m","memory":"64Mi"}}}` | +| dataRetriever | This pod allows you to retrieve the gathered data after the job completes | object | `{"enabled":true,"image":{"pullPolicy":"","repository":"registry.access.redhat.com/ubi9","tag":"latest"},"resources":{"limits":{"cpu":"100m","memory":"128Mi"},"requests":{"cpu":"50m","memory":"64Mi"}}}` | | dataRetriever.enabled | Enable the data retriever pod | bool | `true` | -| dataRetriever.image | Image for the data retriever pod | object | `{"pullPolicy":"IfNotPresent","repository":"registry.access.redhat.com/ubi9","tag":"latest"}` | +| dataRetriever.image | Image for the data retriever pod | object | `{"pullPolicy":"","repository":"registry.access.redhat.com/ubi9","tag":"latest"}` | | dataRetriever.resources | Resource configuration | object | `{"limits":{"cpu":"100m","memory":"128Mi"},"requests":{"cpu":"50m","memory":"64Mi"}}` | | fullnameOverride | | string | `""` | | gather | Gather script configuration | object | `{"clusterInfo":false,"cmdTimeout":"30","extraArgs":[],"logLevel":"INFO","namespaces":"","since":"","sinceTime":"","withHeapDumps":false,"withSecrets":false,"withoutHelm":false,"withoutIngress":false,"withoutNamespaceInspect":false,"withoutOperator":false,"withoutOrchestrator":false,"withoutPlatform":false,"withoutRoute":false}` | @@ -102,7 +102,7 @@ The command removes all the Kubernetes resources associated with the chart and d | gather.sinceTime | Absolute timestamp for log collection (RFC3339 format) | string | `""` | | gather.withSecrets | Optional collection features (disabled by default) | bool | `false` | | gather.withoutOperator | Exclusion options (set to true to skip collection) | bool | `false` | -| image | Container image configuration | object | `{"pullPolicy":"IfNotPresent","repository":"quay.io/rhdh-community/rhdh-must-gather","tag":"latest"}` | +| image | Container image configuration | object | `{"pullPolicy":"","repository":"quay.io/rhdh-community/rhdh-must-gather","tag":"latest"}` | | imagePullSecrets | Secrets for pulling images from a private registry | list | `[]` | | job | Job configuration | object | `{"activeDeadlineSeconds":3600,"backoffLimit":3,"ttlSecondsAfterFinished":""}` | | job.activeDeadlineSeconds | Job timeout in seconds (default: 1 hour) | int | `3600` | @@ -126,8 +126,8 @@ The command removes all the Kubernetes resources associated with the chart and d | serviceAccount.automount | Automatically mount a ServiceAccount's API credentials | bool | `true` | | serviceAccount.create | Specifies whether a service account should be created | bool | `true` | | serviceAccount.name | If not set and create is true, a name is generated using the fullname template | string | `""` | -| test | Helm test configuration | object | `{"enabled":true,"image":{"pullPolicy":"IfNotPresent","repository":"bitnami/kubectl","tag":"latest"}}` | +| test | Helm test configuration | object | `{"enabled":true,"image":{"pullPolicy":"","repository":"bitnami/kubectl","tag":"latest"}}` | | test.enabled | Enable the Helm test | bool | `true` | -| test.image | Image for the test pod | object | `{"pullPolicy":"IfNotPresent","repository":"bitnami/kubectl","tag":"latest"}` | +| test.image | Image for the test pod | object | `{"pullPolicy":"","repository":"bitnami/kubectl","tag":"latest"}` | | tolerations | Tolerations for pod scheduling | list | `[]` | diff --git a/charts/must-gather/values.schema.json b/charts/must-gather/values.schema.json new file mode 100644 index 00000000..083d6af8 --- /dev/null +++ b/charts/must-gather/values.schema.json @@ -0,0 +1,1511 @@ +{ + "$id": "https://raw.githubusercontent.com/redhat-developer/rhdh-chart/main/charts/must-gather/values.schema.json", + "properties": { + "affinity": { + "default": {}, + "description": "Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity", + "properties": { + "nodeAffinity": { + "description": "Node affinity is a group of node affinity scheduling rules.", + "properties": { + "preferredDuringSchedulingIgnoredDuringExecution": { + "description": "The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding \"weight\" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred.", + "items": { + "description": "An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op).", + "properties": { + "preference": { + "description": "A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm.", + "properties": { + "matchExpressions": { + "description": "A list of node selector requirements by node's labels.", + "items": { + "description": "A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "properties": { + "key": { + "description": "The label key that the selector applies to.", + "type": "string" + }, + "operator": { + "description": "Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.", + "type": "string" + }, + "values": { + "description": "An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "matchFields": { + "description": "A list of node selector requirements by node's fields.", + "items": { + "description": "A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "properties": { + "key": { + "description": "The label key that the selector applies to.", + "type": "string" + }, + "operator": { + "description": "Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.", + "type": "string" + }, + "values": { + "description": "An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic" + }, + "weight": { + "description": "Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100.", + "type": "integer" + } + }, + "required": [ + "weight", + "preference" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "requiredDuringSchedulingIgnoredDuringExecution": { + "description": "A node selector represents the union of the results of one or more label queries over a set of nodes; that is, it represents the OR of the selectors represented by the node selector terms.", + "properties": { + "nodeSelectorTerms": { + "description": "Required. A list of node selector terms. The terms are ORed.", + "items": { + "description": "A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm.", + "properties": { + "matchExpressions": { + "description": "A list of node selector requirements by node's labels.", + "items": { + "description": "A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "properties": { + "key": { + "description": "The label key that the selector applies to.", + "type": "string" + }, + "operator": { + "description": "Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.", + "type": "string" + }, + "values": { + "description": "An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "matchFields": { + "description": "A list of node selector requirements by node's fields.", + "items": { + "description": "A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "properties": { + "key": { + "description": "The label key that the selector applies to.", + "type": "string" + }, + "operator": { + "description": "Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.", + "type": "string" + }, + "values": { + "description": "An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "nodeSelectorTerms" + ], + "type": "object", + "x-kubernetes-map-type": "atomic" + } + }, + "type": "object" + }, + "podAffinity": { + "description": "Pod affinity is a group of inter pod affinity scheduling rules.", + "properties": { + "preferredDuringSchedulingIgnoredDuringExecution": { + "description": "The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred.", + "items": { + "description": "The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s)", + "properties": { + "podAffinityTerm": { + "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running", + "properties": { + "labelSelector": { + "description": "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.", + "properties": { + "matchExpressions": { + "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed.", + "items": { + "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "properties": { + "key": { + "description": "key is the label key that the selector applies to.", + "type": "string" + }, + "operator": { + "description": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.", + "type": "string" + }, + "values": { + "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", + "type": "object" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic" + }, + "matchLabelKeys": { + "description": "MatchLabelKeys is a set of pod label keys to select which pods will be taken into consideration. The keys are used to lookup values from the incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` to select the group of existing pods which pods will be taken into consideration for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "mismatchLabelKeys": { + "description": "MismatchLabelKeys is a set of pod label keys to select which pods will be taken into consideration. The keys are used to lookup values from the incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` to select the group of existing pods which pods will be taken into consideration for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "namespaceSelector": { + "description": "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.", + "properties": { + "matchExpressions": { + "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed.", + "items": { + "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "properties": { + "key": { + "description": "key is the label key that the selector applies to.", + "type": "string" + }, + "operator": { + "description": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.", + "type": "string" + }, + "values": { + "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", + "type": "object" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic" + }, + "namespaces": { + "description": "namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "topologyKey": { + "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.", + "type": "string" + } + }, + "required": [ + "topologyKey" + ], + "type": "object" + }, + "weight": { + "description": "weight associated with matching the corresponding podAffinityTerm, in the range 1-100.", + "type": "integer" + } + }, + "required": [ + "weight", + "podAffinityTerm" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "requiredDuringSchedulingIgnoredDuringExecution": { + "description": "If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied.", + "items": { + "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running", + "properties": { + "labelSelector": { + "description": "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.", + "properties": { + "matchExpressions": { + "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed.", + "items": { + "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "properties": { + "key": { + "description": "key is the label key that the selector applies to.", + "type": "string" + }, + "operator": { + "description": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.", + "type": "string" + }, + "values": { + "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", + "type": "object" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic" + }, + "matchLabelKeys": { + "description": "MatchLabelKeys is a set of pod label keys to select which pods will be taken into consideration. The keys are used to lookup values from the incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` to select the group of existing pods which pods will be taken into consideration for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "mismatchLabelKeys": { + "description": "MismatchLabelKeys is a set of pod label keys to select which pods will be taken into consideration. The keys are used to lookup values from the incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` to select the group of existing pods which pods will be taken into consideration for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "namespaceSelector": { + "description": "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.", + "properties": { + "matchExpressions": { + "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed.", + "items": { + "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "properties": { + "key": { + "description": "key is the label key that the selector applies to.", + "type": "string" + }, + "operator": { + "description": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.", + "type": "string" + }, + "values": { + "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", + "type": "object" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic" + }, + "namespaces": { + "description": "namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "topologyKey": { + "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.", + "type": "string" + } + }, + "required": [ + "topologyKey" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object" + }, + "podAntiAffinity": { + "description": "Pod anti affinity is a group of inter pod anti affinity scheduling rules.", + "properties": { + "preferredDuringSchedulingIgnoredDuringExecution": { + "description": "The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred.", + "items": { + "description": "The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s)", + "properties": { + "podAffinityTerm": { + "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running", + "properties": { + "labelSelector": { + "description": "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.", + "properties": { + "matchExpressions": { + "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed.", + "items": { + "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "properties": { + "key": { + "description": "key is the label key that the selector applies to.", + "type": "string" + }, + "operator": { + "description": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.", + "type": "string" + }, + "values": { + "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", + "type": "object" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic" + }, + "matchLabelKeys": { + "description": "MatchLabelKeys is a set of pod label keys to select which pods will be taken into consideration. The keys are used to lookup values from the incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` to select the group of existing pods which pods will be taken into consideration for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "mismatchLabelKeys": { + "description": "MismatchLabelKeys is a set of pod label keys to select which pods will be taken into consideration. The keys are used to lookup values from the incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` to select the group of existing pods which pods will be taken into consideration for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "namespaceSelector": { + "description": "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.", + "properties": { + "matchExpressions": { + "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed.", + "items": { + "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "properties": { + "key": { + "description": "key is the label key that the selector applies to.", + "type": "string" + }, + "operator": { + "description": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.", + "type": "string" + }, + "values": { + "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", + "type": "object" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic" + }, + "namespaces": { + "description": "namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "topologyKey": { + "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.", + "type": "string" + } + }, + "required": [ + "topologyKey" + ], + "type": "object" + }, + "weight": { + "description": "weight associated with matching the corresponding podAffinityTerm, in the range 1-100.", + "type": "integer" + } + }, + "required": [ + "weight", + "podAffinityTerm" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "requiredDuringSchedulingIgnoredDuringExecution": { + "description": "If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied.", + "items": { + "description": "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running", + "properties": { + "labelSelector": { + "description": "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.", + "properties": { + "matchExpressions": { + "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed.", + "items": { + "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "properties": { + "key": { + "description": "key is the label key that the selector applies to.", + "type": "string" + }, + "operator": { + "description": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.", + "type": "string" + }, + "values": { + "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", + "type": "object" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic" + }, + "matchLabelKeys": { + "description": "MatchLabelKeys is a set of pod label keys to select which pods will be taken into consideration. The keys are used to lookup values from the incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` to select the group of existing pods which pods will be taken into consideration for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "mismatchLabelKeys": { + "description": "MismatchLabelKeys is a set of pod label keys to select which pods will be taken into consideration. The keys are used to lookup values from the incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` to select the group of existing pods which pods will be taken into consideration for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "namespaceSelector": { + "description": "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.", + "properties": { + "matchExpressions": { + "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed.", + "items": { + "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "properties": { + "key": { + "description": "key is the label key that the selector applies to.", + "type": "string" + }, + "operator": { + "description": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.", + "type": "string" + }, + "values": { + "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", + "type": "object" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic" + }, + "namespaces": { + "description": "namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "topologyKey": { + "description": "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.", + "type": "string" + } + }, + "required": [ + "topologyKey" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object" + } + }, + "title": "Affinity for pod assignment", + "type": "object" + }, + "dataRetriever": { + "additionalProperties": false, + "properties": { + "enabled": { + "default": true, + "title": "Enable the data retriever pod.", + "type": "boolean" + }, + "image": { + "additionalProperties": false, + "properties": { + "pullPolicy": { + "default": "", + "enum": [ + "Always", + "IfNotPresent", + "Never", + "" + ], + "title": "Image pull policy.", + "type": "string" + }, + "repository": { + "default": "registry.access.redhat.com/ubi9", + "title": "Image repository.", + "type": "string" + }, + "tag": { + "default": "latest", + "title": "Image tag.", + "type": "string" + } + }, + "title": "Image for the data retriever pod.", + "type": "object" + }, + "resources": { + "additionalProperties": false, + "properties": { + "limits": { + "additionalProperties": false, + "properties": { + "cpu": { + "default": "100m", + "title": "CPU limit.", + "type": "string" + }, + "memory": { + "default": "128Mi", + "title": "Memory limit.", + "type": "string" + } + }, + "title": "Resource limits.", + "type": "object" + }, + "requests": { + "additionalProperties": false, + "properties": { + "cpu": { + "default": "50m", + "title": "CPU request.", + "type": "string" + }, + "memory": { + "default": "64Mi", + "title": "Memory request.", + "type": "string" + } + }, + "title": "Resource requests.", + "type": "object" + } + }, + "title": "Resource configuration for the data retriever pod.", + "type": "object" + } + }, + "title": "Data retriever pod configuration. This pod allows you to retrieve the gathered data after the job completes.", + "type": "object" + }, + "fullnameOverride": { + "default": "", + "title": "Override the full release name.", + "type": "string" + }, + "gather": { + "additionalProperties": false, + "properties": { + "clusterInfo": { + "default": false, + "title": "Enable collection of cluster-wide info (disabled by default).", + "type": "boolean" + }, + "cmdTimeout": { + "default": "30", + "title": "Command timeout for individual kubectl/helm commands (seconds).", + "type": "string" + }, + "extraArgs": { + "default": [], + "items": { + "type": "string" + }, + "title": "Additional custom arguments to pass to the gather script.", + "type": "array" + }, + "logLevel": { + "default": "INFO", + "enum": [ + "INFO", + "DEBUG", + "TRACE" + ], + "title": "Log level for the gather script.", + "type": "string" + }, + "namespaces": { + "default": "", + "title": "Limit collection to specific namespaces (comma-separated). Example: 'rhdh-prod,rhdh-staging'.", + "type": "string" + }, + "since": { + "default": "", + "title": "Relative time for log collection (e.g., '2h', '30m').", + "type": "string" + }, + "sinceTime": { + "default": "", + "title": "Absolute timestamp for log collection (RFC3339 format).", + "type": "string" + }, + "withHeapDumps": { + "default": false, + "title": "Enable collection of heap dumps (disabled by default).", + "type": "boolean" + }, + "withSecrets": { + "default": false, + "title": "Enable collection of Secrets (disabled by default).", + "type": "boolean" + }, + "withoutHelm": { + "default": false, + "title": "Skip collection of Helm release information.", + "type": "boolean" + }, + "withoutIngress": { + "default": false, + "title": "Skip collection of Ingress resources.", + "type": "boolean" + }, + "withoutNamespaceInspect": { + "default": false, + "title": "Skip namespace-level inspection.", + "type": "boolean" + }, + "withoutOperator": { + "default": false, + "title": "Skip collection of RHDH Operator resources.", + "type": "boolean" + }, + "withoutOrchestrator": { + "default": false, + "title": "Skip collection of Orchestrator resources.", + "type": "boolean" + }, + "withoutPlatform": { + "default": false, + "title": "Skip collection of platform-level resources.", + "type": "boolean" + }, + "withoutRoute": { + "default": false, + "title": "Skip collection of OpenShift Route resources.", + "type": "boolean" + } + }, + "title": "Gather script configuration.", + "type": "object" + }, + "image": { + "additionalProperties": false, + "properties": { + "pullPolicy": { + "default": "", + "enum": [ + "Always", + "IfNotPresent", + "Never", + "" + ], + "title": "Image pull policy.", + "type": "string" + }, + "repository": { + "default": "quay.io/rhdh-community/rhdh-must-gather", + "title": "Image repository.", + "type": "string" + }, + "tag": { + "default": "latest", + "title": "Overrides the image tag whose default is the chart appVersion.", + "type": "string" + } + }, + "title": "Container image configuration.", + "type": "object" + }, + "imagePullSecrets": { + "default": [], + "items": { + "properties": { + "name": { + "type": "string" + } + }, + "type": "object" + }, + "title": "Secrets for pulling images from a private registry.", + "type": "array" + }, + "job": { + "additionalProperties": false, + "properties": { + "activeDeadlineSeconds": { + "default": 3600, + "title": "Job timeout in seconds.", + "type": "integer" + }, + "backoffLimit": { + "default": 3, + "title": "Number of retries before marking job as failed.", + "type": "integer" + }, + "ttlSecondsAfterFinished": { + "default": "", + "title": "TTL for automatic cleanup after job finishes (seconds). Set to a positive value to enable automatic cleanup.", + "type": [ + "integer", + "string" + ] + } + }, + "title": "Job configuration.", + "type": "object" + }, + "nameOverride": { + "default": "", + "title": "Override the chart name.", + "type": "string" + }, + "nodeSelector": { + "additionalProperties": { + "type": "string" + }, + "default": {}, + "description": "Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector", + "title": "Node labels for pod assignment", + "type": "object" + }, + "persistence": { + "additionalProperties": false, + "properties": { + "accessMode": { + "default": "ReadWriteOnce", + "enum": [ + "ReadWriteOnce", + "ReadOnlyMany", + "ReadWriteMany" + ], + "title": "Access mode.", + "type": "string" + }, + "size": { + "default": "1Gi", + "title": "Storage size.", + "type": "string" + }, + "storageClass": { + "default": "", + "title": "Storage class (empty = use cluster default).", + "type": "string" + } + }, + "title": "Persistent volume configuration for storing gathered data.", + "type": "object" + }, + "podAnnotations": { + "default": {}, + "title": "Pod annotations.", + "type": "object" + }, + "podLabels": { + "default": {}, + "title": "Pod labels.", + "type": "object" + }, + "podSecurityContext": { + "description": "SecurityContext holds security configuration that will be applied to a container. Some fields are present in both SecurityContext and PodSecurityContext. When both are set, the values in SecurityContext take precedence.", + "properties": { + "allowPrivilegeEscalation": { + "description": "AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN Note that this field cannot be set when spec.os.name is windows.", + "type": "boolean" + }, + "appArmorProfile": { + "description": "AppArmorProfile defines a pod or container's AppArmor settings.", + "properties": { + "localhostProfile": { + "description": "localhostProfile indicates a profile loaded on the node that should be used. The profile must be preconfigured on the node to work. Must match the loaded name of the profile. Must be set if and only if type is \"Localhost\".", + "type": "string" + }, + "type": { + "description": "type indicates which kind of AppArmor profile will be applied. Valid options are:\n Localhost - a profile pre-loaded on the node.\n RuntimeDefault - the container runtime's default profile.\n Unconfined - no AppArmor enforcement.", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-unions": [ + { + "discriminator": "type", + "fields-to-discriminateBy": { + "localhostProfile": "LocalhostProfile" + } + } + ] + }, + "capabilities": { + "description": "Adds and removes POSIX capabilities from running containers.", + "properties": { + "add": { + "description": "Added capabilities", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "drop": { + "description": "Removed capabilities", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object" + }, + "privileged": { + "description": "Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. Note that this field cannot be set when spec.os.name is windows.", + "type": "boolean" + }, + "procMount": { + "description": "procMount denotes the type of proc mount to use for the containers. The default value is Default which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. Note that this field cannot be set when spec.os.name is windows.", + "type": "string" + }, + "readOnlyRootFilesystem": { + "description": "Whether this container has a read-only root filesystem. Default is false. Note that this field cannot be set when spec.os.name is windows.", + "type": "boolean" + }, + "runAsGroup": { + "description": "The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows.", + "type": "integer" + }, + "runAsNonRoot": { + "description": "Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", + "type": "boolean" + }, + "runAsUser": { + "description": "The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows.", + "type": "integer" + }, + "seLinuxOptions": { + "description": "SELinuxOptions are the labels to be applied to the container", + "properties": { + "level": { + "description": "Level is SELinux level label that applies to the container.", + "type": "string" + }, + "role": { + "description": "Role is a SELinux role label that applies to the container.", + "type": "string" + }, + "type": { + "description": "Type is a SELinux type label that applies to the container.", + "type": "string" + }, + "user": { + "description": "User is a SELinux user label that applies to the container.", + "type": "string" + } + }, + "type": "object" + }, + "seccompProfile": { + "description": "SeccompProfile defines a pod/container's seccomp profile settings. Only one profile source may be set.", + "properties": { + "localhostProfile": { + "description": "localhostProfile indicates a profile defined in a file on the node should be used. The profile must be preconfigured on the node to work. Must be a descending path, relative to the kubelet's configured seccomp profile location. Must be set if type is \"Localhost\". Must NOT be set for any other type.", + "type": "string" + }, + "type": { + "description": "type indicates which kind of seccomp profile will be applied. Valid options are:\n\nLocalhost - a profile defined in a file on the node should be used. RuntimeDefault - the container runtime default profile should be used. Unconfined - no profile should be applied.", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-unions": [ + { + "discriminator": "type", + "fields-to-discriminateBy": { + "localhostProfile": "LocalhostProfile" + } + } + ] + }, + "windowsOptions": { + "description": "WindowsSecurityContextOptions contain Windows-specific options and credentials.", + "properties": { + "gmsaCredentialSpec": { + "description": "GMSACredentialSpec is where the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field.", + "type": "string" + }, + "gmsaCredentialSpecName": { + "description": "GMSACredentialSpecName is the name of the GMSA credential spec to use.", + "type": "string" + }, + "hostProcess": { + "description": "HostProcess determines if a container should be run as a 'Host Process' container. All of a Pod's containers must have the same effective HostProcess value (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). In addition, if HostProcess is true then HostNetwork must also be set to true.", + "type": "boolean" + }, + "runAsUserName": { + "description": "The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "rbac": { + "additionalProperties": false, + "properties": { + "create": { + "default": true, + "title": "Create ClusterRole and ClusterRoleBinding for cluster-wide read access.", + "type": "boolean" + } + }, + "title": "RBAC configuration.", + "type": "object" + }, + "resources": { + "additionalProperties": false, + "properties": { + "limits": { + "additionalProperties": false, + "properties": { + "cpu": { + "default": "500m", + "title": "CPU limit.", + "type": "string" + }, + "memory": { + "default": "512Mi", + "title": "Memory limit.", + "type": "string" + } + }, + "title": "Resource limits.", + "type": "object" + }, + "requests": { + "additionalProperties": false, + "properties": { + "cpu": { + "default": "100m", + "title": "CPU request.", + "type": "string" + }, + "memory": { + "default": "128Mi", + "title": "Memory request.", + "type": "string" + } + }, + "title": "Resource requests.", + "type": "object" + } + }, + "title": "Resource requests and limits for the gather job.", + "type": "object" + }, + "securityContext": { + "description": "SecurityContext holds security configuration that will be applied to a container. Some fields are present in both SecurityContext and PodSecurityContext. When both are set, the values in SecurityContext take precedence.", + "properties": { + "allowPrivilegeEscalation": { + "description": "AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN Note that this field cannot be set when spec.os.name is windows.", + "type": "boolean" + }, + "appArmorProfile": { + "description": "AppArmorProfile defines a pod or container's AppArmor settings.", + "properties": { + "localhostProfile": { + "description": "localhostProfile indicates a profile loaded on the node that should be used. The profile must be preconfigured on the node to work. Must match the loaded name of the profile. Must be set if and only if type is \"Localhost\".", + "type": "string" + }, + "type": { + "description": "type indicates which kind of AppArmor profile will be applied. Valid options are:\n Localhost - a profile pre-loaded on the node.\n RuntimeDefault - the container runtime's default profile.\n Unconfined - no AppArmor enforcement.", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-unions": [ + { + "discriminator": "type", + "fields-to-discriminateBy": { + "localhostProfile": "LocalhostProfile" + } + } + ] + }, + "capabilities": { + "description": "Adds and removes POSIX capabilities from running containers.", + "properties": { + "add": { + "description": "Added capabilities", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "drop": { + "description": "Removed capabilities", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object" + }, + "privileged": { + "description": "Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. Note that this field cannot be set when spec.os.name is windows.", + "type": "boolean" + }, + "procMount": { + "description": "procMount denotes the type of proc mount to use for the containers. The default value is Default which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. Note that this field cannot be set when spec.os.name is windows.", + "type": "string" + }, + "readOnlyRootFilesystem": { + "description": "Whether this container has a read-only root filesystem. Default is false. Note that this field cannot be set when spec.os.name is windows.", + "type": "boolean" + }, + "runAsGroup": { + "description": "The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows.", + "type": "integer" + }, + "runAsNonRoot": { + "description": "Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", + "type": "boolean" + }, + "runAsUser": { + "description": "The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows.", + "type": "integer" + }, + "seLinuxOptions": { + "description": "SELinuxOptions are the labels to be applied to the container", + "properties": { + "level": { + "description": "Level is SELinux level label that applies to the container.", + "type": "string" + }, + "role": { + "description": "Role is a SELinux role label that applies to the container.", + "type": "string" + }, + "type": { + "description": "Type is a SELinux type label that applies to the container.", + "type": "string" + }, + "user": { + "description": "User is a SELinux user label that applies to the container.", + "type": "string" + } + }, + "type": "object" + }, + "seccompProfile": { + "description": "SeccompProfile defines a pod/container's seccomp profile settings. Only one profile source may be set.", + "properties": { + "localhostProfile": { + "description": "localhostProfile indicates a profile defined in a file on the node should be used. The profile must be preconfigured on the node to work. Must be a descending path, relative to the kubelet's configured seccomp profile location. Must be set if type is \"Localhost\". Must NOT be set for any other type.", + "type": "string" + }, + "type": { + "description": "type indicates which kind of seccomp profile will be applied. Valid options are:\n\nLocalhost - a profile defined in a file on the node should be used. RuntimeDefault - the container runtime default profile should be used. Unconfined - no profile should be applied.", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object", + "x-kubernetes-unions": [ + { + "discriminator": "type", + "fields-to-discriminateBy": { + "localhostProfile": "LocalhostProfile" + } + } + ] + }, + "windowsOptions": { + "description": "WindowsSecurityContextOptions contain Windows-specific options and credentials.", + "properties": { + "gmsaCredentialSpec": { + "description": "GMSACredentialSpec is where the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field.", + "type": "string" + }, + "gmsaCredentialSpecName": { + "description": "GMSACredentialSpecName is the name of the GMSA credential spec to use.", + "type": "string" + }, + "hostProcess": { + "description": "HostProcess determines if a container should be run as a 'Host Process' container. All of a Pod's containers must have the same effective HostProcess value (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). In addition, if HostProcess is true then HostNetwork must also be set to true.", + "type": "boolean" + }, + "runAsUserName": { + "description": "The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "serviceAccount": { + "additionalProperties": false, + "properties": { + "annotations": { + "default": {}, + "title": "Annotations to add to the service account.", + "type": "object" + }, + "automount": { + "default": true, + "title": "Automatically mount a ServiceAccount's API credentials.", + "type": "boolean" + }, + "create": { + "default": true, + "title": "Specifies whether a service account should be created.", + "type": "boolean" + }, + "name": { + "default": "", + "title": "The name of the service account to use. If not set and create is true, a name is generated using the fullname template.", + "type": "string" + } + }, + "title": "Service account configuration.", + "type": "object" + }, + "test": { + "additionalProperties": false, + "properties": { + "enabled": { + "default": true, + "title": "Enable the Helm test.", + "type": "boolean" + }, + "image": { + "additionalProperties": false, + "properties": { + "pullPolicy": { + "default": "", + "enum": [ + "Always", + "IfNotPresent", + "Never", + "" + ], + "title": "Image pull policy.", + "type": "string" + }, + "repository": { + "default": "bitnami/kubectl", + "title": "Image repository.", + "type": "string" + }, + "tag": { + "default": "latest", + "title": "Image tag.", + "type": "string" + } + }, + "title": "Image for the test pod.", + "type": "object" + } + }, + "title": "Helm test configuration.", + "type": "object" + }, + "tolerations": { + "default": [], + "description": "Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/", + "items": { + "description": "The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator .", + "properties": { + "effect": { + "description": "Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.", + "type": "string" + }, + "key": { + "description": "Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys.", + "type": "string" + }, + "operator": { + "description": "Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category.", + "type": "string" + }, + "tolerationSeconds": { + "description": "TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system.", + "type": "integer" + }, + "value": { + "description": "Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string.", + "type": "string" + } + }, + "type": "object" + }, + "title": "Node tolerations for server scheduling to nodes with taints", + "type": "array" + } + }, + "title": "Root Schema", + "type": "object" +} \ No newline at end of file diff --git a/charts/must-gather/values.schema.tmpl.json b/charts/must-gather/values.schema.tmpl.json new file mode 100644 index 00000000..0f7677b7 --- /dev/null +++ b/charts/must-gather/values.schema.tmpl.json @@ -0,0 +1,467 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/redhat-developer/rhdh-chart/main/charts/must-gather/values.schema.json", + "type": "object", + "title": "Root Schema", + "properties": { + "image": { + "title": "Container image configuration.", + "type": "object", + "additionalProperties": false, + "properties": { + "repository": { + "title": "Image repository.", + "type": "string", + "default": "quay.io/rhdh-community/rhdh-must-gather" + }, + "pullPolicy": { + "title": "Image pull policy.", + "type": "string", + "default": "", + "enum": [ + "Always", + "IfNotPresent", + "Never", + "" + ] + }, + "tag": { + "title": "Overrides the image tag whose default is the chart appVersion.", + "type": "string", + "default": "latest" + } + } + }, + "imagePullSecrets": { + "title": "Secrets for pulling images from a private registry.", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }, + "default": [] + }, + "nameOverride": { + "title": "Override the chart name.", + "type": "string", + "default": "" + }, + "fullnameOverride": { + "title": "Override the full release name.", + "type": "string", + "default": "" + }, + "serviceAccount": { + "title": "Service account configuration.", + "type": "object", + "additionalProperties": false, + "properties": { + "create": { + "title": "Specifies whether a service account should be created.", + "type": "boolean", + "default": true + }, + "automount": { + "title": "Automatically mount a ServiceAccount's API credentials.", + "type": "boolean", + "default": true + }, + "annotations": { + "title": "Annotations to add to the service account.", + "type": "object", + "default": {} + }, + "name": { + "title": "The name of the service account to use. If not set and create is true, a name is generated using the fullname template.", + "type": "string", + "default": "" + } + } + }, + "rbac": { + "title": "RBAC configuration.", + "type": "object", + "additionalProperties": false, + "properties": { + "create": { + "title": "Create ClusterRole and ClusterRoleBinding for cluster-wide read access.", + "type": "boolean", + "default": true + } + } + }, + "job": { + "title": "Job configuration.", + "type": "object", + "additionalProperties": false, + "properties": { + "activeDeadlineSeconds": { + "title": "Job timeout in seconds.", + "type": "integer", + "default": 3600 + }, + "backoffLimit": { + "title": "Number of retries before marking job as failed.", + "type": "integer", + "default": 3 + }, + "ttlSecondsAfterFinished": { + "title": "TTL for automatic cleanup after job finishes (seconds). Set to a positive value to enable automatic cleanup.", + "type": [ + "integer", + "string" + ], + "default": "" + } + } + }, + "gather": { + "title": "Gather script configuration.", + "type": "object", + "additionalProperties": false, + "properties": { + "logLevel": { + "title": "Log level for the gather script.", + "type": "string", + "default": "INFO", + "enum": [ + "INFO", + "DEBUG", + "TRACE" + ] + }, + "cmdTimeout": { + "title": "Command timeout for individual kubectl/helm commands (seconds).", + "type": "string", + "default": "30" + }, + "since": { + "title": "Relative time for log collection (e.g., '2h', '30m').", + "type": "string", + "default": "" + }, + "sinceTime": { + "title": "Absolute timestamp for log collection (RFC3339 format).", + "type": "string", + "default": "" + }, + "withSecrets": { + "title": "Enable collection of Secrets (disabled by default).", + "type": "boolean", + "default": false + }, + "withHeapDumps": { + "title": "Enable collection of heap dumps (disabled by default).", + "type": "boolean", + "default": false + }, + "clusterInfo": { + "title": "Enable collection of cluster-wide info (disabled by default).", + "type": "boolean", + "default": false + }, + "withoutOperator": { + "title": "Skip collection of RHDH Operator resources.", + "type": "boolean", + "default": false + }, + "withoutOrchestrator": { + "title": "Skip collection of Orchestrator resources.", + "type": "boolean", + "default": false + }, + "withoutHelm": { + "title": "Skip collection of Helm release information.", + "type": "boolean", + "default": false + }, + "withoutPlatform": { + "title": "Skip collection of platform-level resources.", + "type": "boolean", + "default": false + }, + "withoutRoute": { + "title": "Skip collection of OpenShift Route resources.", + "type": "boolean", + "default": false + }, + "withoutIngress": { + "title": "Skip collection of Ingress resources.", + "type": "boolean", + "default": false + }, + "withoutNamespaceInspect": { + "title": "Skip namespace-level inspection.", + "type": "boolean", + "default": false + }, + "namespaces": { + "title": "Limit collection to specific namespaces (comma-separated). Example: 'rhdh-prod,rhdh-staging'.", + "type": "string", + "default": "" + }, + "extraArgs": { + "title": "Additional custom arguments to pass to the gather script.", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + } + } + }, + "resources": { + "title": "Resource requests and limits for the gather job.", + "type": "object", + "additionalProperties": false, + "properties": { + "requests": { + "title": "Resource requests.", + "type": "object", + "additionalProperties": false, + "properties": { + "memory": { + "title": "Memory request.", + "type": "string", + "default": "128Mi" + }, + "cpu": { + "title": "CPU request.", + "type": "string", + "default": "100m" + } + } + }, + "limits": { + "title": "Resource limits.", + "type": "object", + "additionalProperties": false, + "properties": { + "memory": { + "title": "Memory limit.", + "type": "string", + "default": "512Mi" + }, + "cpu": { + "title": "CPU limit.", + "type": "string", + "default": "500m" + } + } + } + } + }, + "persistence": { + "title": "Persistent volume configuration for storing gathered data.", + "type": "object", + "additionalProperties": false, + "properties": { + "size": { + "title": "Storage size.", + "type": "string", + "default": "1Gi" + }, + "storageClass": { + "title": "Storage class (empty = use cluster default).", + "type": "string", + "default": "" + }, + "accessMode": { + "title": "Access mode.", + "type": "string", + "default": "ReadWriteOnce", + "enum": [ + "ReadWriteOnce", + "ReadOnlyMany", + "ReadWriteMany" + ] + } + } + }, + "dataRetriever": { + "title": "Data retriever pod configuration. This pod allows you to retrieve the gathered data after the job completes.", + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "title": "Enable the data retriever pod.", + "type": "boolean", + "default": true + }, + "image": { + "title": "Image for the data retriever pod.", + "type": "object", + "additionalProperties": false, + "properties": { + "repository": { + "title": "Image repository.", + "type": "string", + "default": "registry.access.redhat.com/ubi9" + }, + "tag": { + "title": "Image tag.", + "type": "string", + "default": "latest" + }, + "pullPolicy": { + "title": "Image pull policy.", + "type": "string", + "default": "", + "enum": [ + "Always", + "IfNotPresent", + "Never", + "" + ] + } + } + }, + "resources": { + "title": "Resource configuration for the data retriever pod.", + "type": "object", + "additionalProperties": false, + "properties": { + "requests": { + "title": "Resource requests.", + "type": "object", + "additionalProperties": false, + "properties": { + "memory": { + "title": "Memory request.", + "type": "string", + "default": "64Mi" + }, + "cpu": { + "title": "CPU request.", + "type": "string", + "default": "50m" + } + } + }, + "limits": { + "title": "Resource limits.", + "type": "object", + "additionalProperties": false, + "properties": { + "memory": { + "title": "Memory limit.", + "type": "string", + "default": "128Mi" + }, + "cpu": { + "title": "CPU limit.", + "type": "string", + "default": "100m" + } + } + } + } + } + } + }, + "test": { + "title": "Helm test configuration.", + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "title": "Enable the Helm test.", + "type": "boolean", + "default": true + }, + "image": { + "title": "Image for the test pod.", + "type": "object", + "additionalProperties": false, + "properties": { + "repository": { + "title": "Image repository.", + "type": "string", + "default": "bitnami/kubectl" + }, + "tag": { + "title": "Image tag.", + "type": "string", + "default": "latest" + }, + "pullPolicy": { + "title": "Image pull policy.", + "type": "string", + "default": "", + "enum": [ + "Always", + "IfNotPresent", + "Never", + "" + ] + } + } + } + } + }, + "podAnnotations": { + "title": "Pod annotations.", + "type": "object", + "default": {} + }, + "podLabels": { + "title": "Pod labels.", + "type": "object", + "default": {} + }, + "podSecurityContext": { + "title": "Security settings for a Pod.", + "description": "The security settings that you specify for a Pod apply to all Containers in the Pod. Ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod", + "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.4/_definitions.json#/definitions/io.k8s.api.core.v1.SecurityContext", + "default": {} + }, + "securityContext": { + "title": "Security settings for a Container.", + "description": "Ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container", + "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.4/_definitions.json#/definitions/io.k8s.api.core.v1.SecurityContext", + "default": {} + }, + "nodeSelector": { + "title": "Node labels for pod assignment", + "description": "Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector", + "type": "object", + "additionalProperties": { + "type": "string" + }, + "default": {} + }, + "tolerations": { + "title": "Node tolerations for server scheduling to nodes with taints", + "description": "Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/", + "type": "array", + "items": { + "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.4/_definitions.json#/definitions/io.k8s.api.core.v1.Toleration" + }, + "default": [] + }, + "affinity": { + "default": {}, + "description": "Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity", + "properties": { + "nodeAffinity": { + "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.4/_definitions.json#/definitions/io.k8s.api.core.v1.NodeAffinity", + "description": "Describes node affinity scheduling rules for the pod." + }, + "podAffinity": { + "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.4/_definitions.json#/definitions/io.k8s.api.core.v1.PodAffinity", + "description": "Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s))." + }, + "podAntiAffinity": { + "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.4/_definitions.json#/definitions/io.k8s.api.core.v1.PodAntiAffinity", + "description": "Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s))." + } + }, + "title": "Affinity for pod assignment", + "type": "object" + } + } +} diff --git a/charts/must-gather/values.yaml b/charts/must-gather/values.yaml index 7fb44060..d59b8e66 100644 --- a/charts/must-gather/values.yaml +++ b/charts/must-gather/values.yaml @@ -5,7 +5,7 @@ # -- Container image configuration image: repository: quay.io/rhdh-community/rhdh-must-gather - pullPolicy: IfNotPresent + pullPolicy: "" # -- -- Overrides the image tag whose default is the chart appVersion. tag: "latest" @@ -102,7 +102,7 @@ dataRetriever: image: repository: registry.access.redhat.com/ubi9 tag: "latest" - pullPolicy: IfNotPresent + pullPolicy: "" # -- Resource configuration resources: requests: @@ -120,7 +120,7 @@ test: image: repository: bitnami/kubectl tag: "latest" - pullPolicy: IfNotPresent + pullPolicy: "" # -- Pod annotations podAnnotations: {} From 697a5d0ce333e7e3283ed3672b0c96b5beef0b0b Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Wed, 11 Mar 2026 18:05:42 +0100 Subject: [PATCH 06/34] fix: address SonarCloud warnings --- .../templates/data-retriever-pod.yaml | 1 + charts/must-gather/templates/job.yaml | 1 + charts/must-gather/templates/rbac.yaml | 16 ++++++++-------- charts/must-gather/templates/tests/test.yaml | 1 + charts/must-gather/values.schema.json | 10 ++++++++++ charts/must-gather/values.schema.tmpl.json | 10 ++++++++++ charts/must-gather/values.yaml | 2 ++ 7 files changed, 33 insertions(+), 8 deletions(-) diff --git a/charts/must-gather/templates/data-retriever-pod.yaml b/charts/must-gather/templates/data-retriever-pod.yaml index a749f69a..cde28c86 100644 --- a/charts/must-gather/templates/data-retriever-pod.yaml +++ b/charts/must-gather/templates/data-retriever-pod.yaml @@ -7,6 +7,7 @@ metadata: {{- include "rhdh-must-gather.labels" . | nindent 4 }} app.kubernetes.io/component: data-retriever spec: + automountServiceAccountToken: false restartPolicy: Never {{- with .Values.imagePullSecrets }} imagePullSecrets: diff --git a/charts/must-gather/templates/job.yaml b/charts/must-gather/templates/job.yaml index 317a5723..deebc666 100644 --- a/charts/must-gather/templates/job.yaml +++ b/charts/must-gather/templates/job.yaml @@ -25,6 +25,7 @@ spec: {{- end }} spec: serviceAccountName: {{ include "rhdh-must-gather.serviceAccountName" . }} + automountServiceAccountToken: true # NOSONAR - gather pod requires K8s API access; SA is bound via ClusterRole/ClusterRoleBinding in rbac.yaml restartPolicy: Never {{- with .Values.imagePullSecrets }} imagePullSecrets: diff --git a/charts/must-gather/templates/rbac.yaml b/charts/must-gather/templates/rbac.yaml index 823a4ef9..12f0753f 100644 --- a/charts/must-gather/templates/rbac.yaml +++ b/charts/must-gather/templates/rbac.yaml @@ -7,28 +7,28 @@ metadata: {{- include "rhdh-must-gather.labels" . | nindent 4 }} rules: - apiGroups: [""] - resources: ["*"] + resources: ["pods", "pods/log", "services", "endpoints", "configmaps", "secrets", "events", "persistentvolumeclaims", "serviceaccounts", "namespaces", "nodes", "replicationcontrollers", "resourcequotas", "limitranges"] verbs: ["get", "list"] - - apiGroups: ["apps", "extensions"] - resources: ["*"] + - apiGroups: ["apps"] + resources: ["deployments", "replicasets", "statefulsets", "daemonsets"] verbs: ["get", "list"] - apiGroups: ["networking.k8s.io"] - resources: ["*"] + resources: ["ingresses", "networkpolicies", "ingressclasses"] verbs: ["get", "list"] - apiGroups: ["rbac.authorization.k8s.io"] - resources: ["*"] + resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"] verbs: ["get", "list"] - apiGroups: ["storage.k8s.io"] - resources: ["*"] + resources: ["storageclasses", "volumeattachments"] verbs: ["get", "list"] - apiGroups: ["apiextensions.k8s.io"] resources: ["customresourcedefinitions"] verbs: ["get", "list"] - apiGroups: ["rhdh.redhat.com"] - resources: ["*"] + resources: ["backstages"] verbs: ["get", "list"] - apiGroups: ["operators.coreos.com"] - resources: ["*"] + resources: ["subscriptions", "clusterserviceversions", "installplans", "catalogsources", "operatorgroups"] verbs: ["get", "list"] # Orchestrator-related permissions - apiGroups: ["sonataflow.org"] diff --git a/charts/must-gather/templates/tests/test.yaml b/charts/must-gather/templates/tests/test.yaml index 13689339..10f8632a 100644 --- a/charts/must-gather/templates/tests/test.yaml +++ b/charts/must-gather/templates/tests/test.yaml @@ -53,6 +53,7 @@ metadata: helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded spec: serviceAccountName: {{ include "rhdh-must-gather.serviceAccountName" . }} + automountServiceAccountToken: true # NOSONAR - test pod runs kubectl commands; SA is bound via Role/RoleBinding above restartPolicy: Never {{- with .Values.imagePullSecrets }} imagePullSecrets: diff --git a/charts/must-gather/values.schema.json b/charts/must-gather/values.schema.json index 083d6af8..0bafb5e6 100644 --- a/charts/must-gather/values.schema.json +++ b/charts/must-gather/values.schema.json @@ -1226,6 +1226,11 @@ "title": "CPU limit.", "type": "string" }, + "ephemeral-storage": { + "default": "128Mi", + "title": "Ephemeral storage limit.", + "type": "string" + }, "memory": { "default": "512Mi", "title": "Memory limit.", @@ -1243,6 +1248,11 @@ "title": "CPU request.", "type": "string" }, + "ephemeral-storage": { + "default": "64Mi", + "title": "Ephemeral storage request.", + "type": "string" + }, "memory": { "default": "128Mi", "title": "Memory request.", diff --git a/charts/must-gather/values.schema.tmpl.json b/charts/must-gather/values.schema.tmpl.json index 0f7677b7..e5bcc0d8 100644 --- a/charts/must-gather/values.schema.tmpl.json +++ b/charts/must-gather/values.schema.tmpl.json @@ -233,6 +233,11 @@ "title": "CPU request.", "type": "string", "default": "100m" + }, + "ephemeral-storage": { + "title": "Ephemeral storage request.", + "type": "string", + "default": "64Mi" } } }, @@ -250,6 +255,11 @@ "title": "CPU limit.", "type": "string", "default": "500m" + }, + "ephemeral-storage": { + "title": "Ephemeral storage limit.", + "type": "string", + "default": "128Mi" } } } diff --git a/charts/must-gather/values.yaml b/charts/must-gather/values.yaml index d59b8e66..01ed6e2e 100644 --- a/charts/must-gather/values.yaml +++ b/charts/must-gather/values.yaml @@ -80,9 +80,11 @@ resources: requests: memory: "128Mi" cpu: "100m" + ephemeral-storage: "64Mi" limits: memory: "512Mi" cpu: "500m" + ephemeral-storage: "128Mi" # -- Persistent volume configuration for storing gathered data persistence: From 9f68b8d32407e21c3eeadb06dbc08bf26e038bec Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Wed, 11 Mar 2026 18:08:02 +0100 Subject: [PATCH 07/34] run pre-commit hooks --- charts/must-gather/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/must-gather/README.md b/charts/must-gather/README.md index 4107c200..f18ecdc4 100644 --- a/charts/must-gather/README.md +++ b/charts/must-gather/README.md @@ -119,7 +119,7 @@ The command removes all the Kubernetes resources associated with the chart and d | podSecurityContext | Pod security context | object | `{}` | | rbac | RBAC configuration | object | `{"create":true}` | | rbac.create | Create ClusterRole and ClusterRoleBinding for cluster-wide read access | bool | `true` | -| resources | Resource requests and limits for the gather job | object | `{"limits":{"cpu":"500m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"128Mi"}}` | +| resources | Resource requests and limits for the gather job | object | `{"limits":{"cpu":"500m","ephemeral-storage":"128Mi","memory":"512Mi"},"requests":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"}}` | | securityContext | Container security context | object | `{}` | | serviceAccount | Service account configuration | object | `{"annotations":{},"automount":true,"create":true,"name":""}` | | serviceAccount.annotations | Annotations to add to the service account | object | `{}` | From bed5953ff66c325e3fe47be31602165215d68a49 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Wed, 11 Mar 2026 18:16:17 +0100 Subject: [PATCH 08/34] fix: address SonarCloud warnings --- charts/must-gather/README.md | 4 ++-- .../templates/data-retriever-pod.yaml | 2 +- charts/must-gather/templates/job.yaml | 6 ++--- charts/must-gather/templates/tests/test.yaml | 24 +++++++++++++++---- charts/must-gather/values.schema.json | 10 ++++++++ charts/must-gather/values.schema.tmpl.json | 10 ++++++++ charts/must-gather/values.yaml | 2 ++ 7 files changed, 47 insertions(+), 11 deletions(-) diff --git a/charts/must-gather/README.md b/charts/must-gather/README.md index f18ecdc4..6d4903d8 100644 --- a/charts/must-gather/README.md +++ b/charts/must-gather/README.md @@ -88,10 +88,10 @@ The command removes all the Kubernetes resources associated with the chart and d | Key | Description | Type | Default | |-----|-------------|------|---------| | affinity | Affinity rules for pod scheduling | object | `{}` | -| dataRetriever | This pod allows you to retrieve the gathered data after the job completes | object | `{"enabled":true,"image":{"pullPolicy":"","repository":"registry.access.redhat.com/ubi9","tag":"latest"},"resources":{"limits":{"cpu":"100m","memory":"128Mi"},"requests":{"cpu":"50m","memory":"64Mi"}}}` | +| dataRetriever | This pod allows you to retrieve the gathered data after the job completes | object | `{"enabled":true,"image":{"pullPolicy":"","repository":"registry.access.redhat.com/ubi9","tag":"latest"},"resources":{"limits":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"},"requests":{"cpu":"50m","ephemeral-storage":"32Mi","memory":"64Mi"}}}` | | dataRetriever.enabled | Enable the data retriever pod | bool | `true` | | dataRetriever.image | Image for the data retriever pod | object | `{"pullPolicy":"","repository":"registry.access.redhat.com/ubi9","tag":"latest"}` | -| dataRetriever.resources | Resource configuration | object | `{"limits":{"cpu":"100m","memory":"128Mi"},"requests":{"cpu":"50m","memory":"64Mi"}}` | +| dataRetriever.resources | Resource configuration | object | `{"limits":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"},"requests":{"cpu":"50m","ephemeral-storage":"32Mi","memory":"64Mi"}}` | | fullnameOverride | | string | `""` | | gather | Gather script configuration | object | `{"clusterInfo":false,"cmdTimeout":"30","extraArgs":[],"logLevel":"INFO","namespaces":"","since":"","sinceTime":"","withHeapDumps":false,"withSecrets":false,"withoutHelm":false,"withoutIngress":false,"withoutNamespaceInspect":false,"withoutOperator":false,"withoutOrchestrator":false,"withoutPlatform":false,"withoutRoute":false}` | | gather.cmdTimeout | Command timeout for individual kubectl/helm commands (seconds) | string | `"30"` | diff --git a/charts/must-gather/templates/data-retriever-pod.yaml b/charts/must-gather/templates/data-retriever-pod.yaml index cde28c86..c112adcb 100644 --- a/charts/must-gather/templates/data-retriever-pod.yaml +++ b/charts/must-gather/templates/data-retriever-pod.yaml @@ -23,7 +23,7 @@ spec: topologyKey: kubernetes.io/hostname containers: - name: data-retriever - image: "{{ .Values.dataRetriever.image.repository }}:{{ .Values.dataRetriever.image.tag }}" + image: "{{ .Values.dataRetriever.image.repository }}:{{ .Values.dataRetriever.image.tag }}" # NOSONAR - tag is user-configurable via Helm values imagePullPolicy: {{ .Values.dataRetriever.image.pullPolicy }} command: - sleep diff --git a/charts/must-gather/templates/job.yaml b/charts/must-gather/templates/job.yaml index deebc666..e7610493 100644 --- a/charts/must-gather/templates/job.yaml +++ b/charts/must-gather/templates/job.yaml @@ -24,8 +24,8 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} spec: - serviceAccountName: {{ include "rhdh-must-gather.serviceAccountName" . }} - automountServiceAccountToken: true # NOSONAR - gather pod requires K8s API access; SA is bound via ClusterRole/ClusterRoleBinding in rbac.yaml + serviceAccountName: {{ include "rhdh-must-gather.serviceAccountName" . }} # NOSONAR - SA is bound via ClusterRole/ClusterRoleBinding in rbac.yaml + automountServiceAccountToken: true restartPolicy: Never {{- with .Values.imagePullSecrets }} imagePullSecrets: @@ -37,7 +37,7 @@ spec: {{- end }} containers: - name: gather - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" # NOSONAR - tag is user-configurable via Helm values; defaults to Chart.AppVersion imagePullPolicy: {{ .Values.image.pullPolicy }} {{- with .Values.securityContext }} securityContext: diff --git a/charts/must-gather/templates/tests/test.yaml b/charts/must-gather/templates/tests/test.yaml index 10f8632a..12ab532d 100644 --- a/charts/must-gather/templates/tests/test.yaml +++ b/charts/must-gather/templates/tests/test.yaml @@ -1,4 +1,17 @@ {{- if and .Values.test.enabled .Values.dataRetriever.enabled -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "rhdh-must-gather.fullname" . }}-test + labels: + {{- include "rhdh-must-gather.labels" . | nindent 4 }} + app.kubernetes.io/component: test + annotations: + helm.sh/hook: test + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded + helm.sh/hook-weight: "-2" +automountServiceAccountToken: true +--- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: @@ -18,7 +31,7 @@ rules: resources: ["pods"] verbs: ["get", "watch", "list"] - apiGroups: [""] - resources: ["pods/exec"] + resources: ["pods/exec"] # NOSONAR - exec is required to retrieve must-gather output from the data-retriever pod during helm test verbs: ["create"] --- apiVersion: rbac.authorization.k8s.io/v1 @@ -38,7 +51,7 @@ roleRef: name: {{ include "rhdh-must-gather.fullname" . }}-test subjects: - kind: ServiceAccount - name: {{ include "rhdh-must-gather.serviceAccountName" . }} + name: {{ include "rhdh-must-gather.fullname" . }}-test namespace: {{ .Release.Namespace }} --- apiVersion: v1 @@ -52,8 +65,7 @@ metadata: helm.sh/hook: test helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded spec: - serviceAccountName: {{ include "rhdh-must-gather.serviceAccountName" . }} - automountServiceAccountToken: true # NOSONAR - test pod runs kubectl commands; SA is bound via Role/RoleBinding above + serviceAccountName: {{ include "rhdh-must-gather.fullname" . }}-test restartPolicy: Never {{- with .Values.imagePullSecrets }} imagePullSecrets: @@ -61,7 +73,7 @@ spec: {{- end }} containers: - name: test - image: "{{ .Values.test.image.repository }}:{{ .Values.test.image.tag }}" + image: "{{ .Values.test.image.repository }}:{{ .Values.test.image.tag }}" # NOSONAR - tag is user-configurable via Helm values imagePullPolicy: {{ .Values.test.image.pullPolicy }} env: - name: NAMESPACE @@ -97,9 +109,11 @@ spec: requests: memory: "64Mi" cpu: "50m" + ephemeral-storage: "32Mi" limits: memory: "128Mi" cpu: "100m" + ephemeral-storage: "64Mi" {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 4 }} diff --git a/charts/must-gather/values.schema.json b/charts/must-gather/values.schema.json index 0bafb5e6..e9e0b617 100644 --- a/charts/must-gather/values.schema.json +++ b/charts/must-gather/values.schema.json @@ -802,6 +802,11 @@ "title": "CPU limit.", "type": "string" }, + "ephemeral-storage": { + "default": "64Mi", + "title": "Ephemeral storage limit.", + "type": "string" + }, "memory": { "default": "128Mi", "title": "Memory limit.", @@ -819,6 +824,11 @@ "title": "CPU request.", "type": "string" }, + "ephemeral-storage": { + "default": "32Mi", + "title": "Ephemeral storage request.", + "type": "string" + }, "memory": { "default": "64Mi", "title": "Memory request.", diff --git a/charts/must-gather/values.schema.tmpl.json b/charts/must-gather/values.schema.tmpl.json index e5bcc0d8..8e3ce013 100644 --- a/charts/must-gather/values.schema.tmpl.json +++ b/charts/must-gather/values.schema.tmpl.json @@ -349,6 +349,11 @@ "title": "CPU request.", "type": "string", "default": "50m" + }, + "ephemeral-storage": { + "title": "Ephemeral storage request.", + "type": "string", + "default": "32Mi" } } }, @@ -366,6 +371,11 @@ "title": "CPU limit.", "type": "string", "default": "100m" + }, + "ephemeral-storage": { + "title": "Ephemeral storage limit.", + "type": "string", + "default": "64Mi" } } } diff --git a/charts/must-gather/values.yaml b/charts/must-gather/values.yaml index 01ed6e2e..bacd4b4a 100644 --- a/charts/must-gather/values.yaml +++ b/charts/must-gather/values.yaml @@ -110,9 +110,11 @@ dataRetriever: requests: memory: "64Mi" cpu: "50m" + ephemeral-storage: "32Mi" limits: memory: "128Mi" cpu: "100m" + ephemeral-storage: "64Mi" # -- Helm test configuration test: From ab6fd71cc90e32455e4d23ce4b1d0e5c174b2b79 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Wed, 11 Mar 2026 23:51:21 +0100 Subject: [PATCH 09/34] fix log level value and validation --- charts/must-gather/README.md | 4 ++-- charts/must-gather/values.schema.json | 8 ++++---- charts/must-gather/values.schema.tmpl.json | 8 ++++---- charts/must-gather/values.yaml | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/charts/must-gather/README.md b/charts/must-gather/README.md index 6d4903d8..53fba50c 100644 --- a/charts/must-gather/README.md +++ b/charts/must-gather/README.md @@ -93,10 +93,10 @@ The command removes all the Kubernetes resources associated with the chart and d | dataRetriever.image | Image for the data retriever pod | object | `{"pullPolicy":"","repository":"registry.access.redhat.com/ubi9","tag":"latest"}` | | dataRetriever.resources | Resource configuration | object | `{"limits":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"},"requests":{"cpu":"50m","ephemeral-storage":"32Mi","memory":"64Mi"}}` | | fullnameOverride | | string | `""` | -| gather | Gather script configuration | object | `{"clusterInfo":false,"cmdTimeout":"30","extraArgs":[],"logLevel":"INFO","namespaces":"","since":"","sinceTime":"","withHeapDumps":false,"withSecrets":false,"withoutHelm":false,"withoutIngress":false,"withoutNamespaceInspect":false,"withoutOperator":false,"withoutOrchestrator":false,"withoutPlatform":false,"withoutRoute":false}` | +| gather | Gather script configuration | object | `{"clusterInfo":false,"cmdTimeout":"30","extraArgs":[],"logLevel":"info","namespaces":"","since":"","sinceTime":"","withHeapDumps":false,"withSecrets":false,"withoutHelm":false,"withoutIngress":false,"withoutNamespaceInspect":false,"withoutOperator":false,"withoutOrchestrator":false,"withoutPlatform":false,"withoutRoute":false}` | | gather.cmdTimeout | Command timeout for individual kubectl/helm commands (seconds) | string | `"30"` | | gather.extraArgs | Additional custom arguments to pass to the gather script | list | `[]` | -| gather.logLevel | Log level: INFO, DEBUG, TRACE | string | `"INFO"` | +| gather.logLevel | Log level: info, debug, trace | string | `"info"` | | gather.namespaces | Example: "rhdh-prod,rhdh-staging" | string | `""` | | gather.since | Relative time for log collection (e.g., "2h", "30m") | string | `""` | | gather.sinceTime | Absolute timestamp for log collection (RFC3339 format) | string | `""` | diff --git a/charts/must-gather/values.schema.json b/charts/must-gather/values.schema.json index e9e0b617..25bf6aa8 100644 --- a/charts/must-gather/values.schema.json +++ b/charts/must-gather/values.schema.json @@ -873,11 +873,11 @@ "type": "array" }, "logLevel": { - "default": "INFO", + "default": "info", "enum": [ - "INFO", - "DEBUG", - "TRACE" + "info", + "debug", + "trace" ], "title": "Log level for the gather script.", "type": "string" diff --git a/charts/must-gather/values.schema.tmpl.json b/charts/must-gather/values.schema.tmpl.json index 8e3ce013..890d897d 100644 --- a/charts/must-gather/values.schema.tmpl.json +++ b/charts/must-gather/values.schema.tmpl.json @@ -127,11 +127,11 @@ "logLevel": { "title": "Log level for the gather script.", "type": "string", - "default": "INFO", + "default": "info", "enum": [ - "INFO", - "DEBUG", - "TRACE" + "info", + "debug", + "trace" ] }, "cmdTimeout": { diff --git a/charts/must-gather/values.yaml b/charts/must-gather/values.yaml index bacd4b4a..55554d3a 100644 --- a/charts/must-gather/values.yaml +++ b/charts/must-gather/values.yaml @@ -45,8 +45,8 @@ job: # -- Gather script configuration gather: - # -- Log level: INFO, DEBUG, TRACE - logLevel: "INFO" + # -- Log level: info, debug, trace + logLevel: "info" # -- Command timeout for individual kubectl/helm commands (seconds) cmdTimeout: "30" # -- Relative time for log collection (e.g., "2h", "30m") From 81023d77cad68dc8c01a27265079d53ad0bbf177 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Thu, 12 Mar 2026 00:39:25 +0100 Subject: [PATCH 10/34] fix: fix RBAC for pods/exec to work properly when running against K8s --- charts/must-gather/templates/rbac.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/charts/must-gather/templates/rbac.yaml b/charts/must-gather/templates/rbac.yaml index 12f0753f..24777bf0 100644 --- a/charts/must-gather/templates/rbac.yaml +++ b/charts/must-gather/templates/rbac.yaml @@ -30,6 +30,9 @@ rules: - apiGroups: ["operators.coreos.com"] resources: ["subscriptions", "clusterserviceversions", "installplans", "catalogsources", "operatorgroups"] verbs: ["get", "list"] + - apiGroups: [""] + resources: ["pods/exec"] # NOSONAR - exec is required to collect data from the running containers + verbs: ["create"] # Orchestrator-related permissions - apiGroups: ["sonataflow.org"] resources: ["sonataflowplatforms", "sonataflows", "sonataflowclusterplatforms", "sonataflowbuilds"] From 2d282d7eb84469102de0edec8bb45089059a38b8 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Thu, 12 Mar 2026 12:59:15 +0100 Subject: [PATCH 11/34] ensure a new Job is created each time the Helm release is upgraded --- charts/must-gather/README.md | 8 +++--- charts/must-gather/README.md.gotmpl | 4 ++- charts/must-gather/templates/NOTES.txt | 17 ++++++++----- charts/must-gather/templates/_helpers.tpl | 25 +++++++++++++++++++ .../templates/data-retriever-pod.yaml | 2 +- charts/must-gather/templates/job.yaml | 4 +-- charts/must-gather/templates/tests/test.yaml | 4 +-- charts/must-gather/values.schema.json | 2 +- charts/must-gather/values.yaml | 3 +-- 9 files changed, 50 insertions(+), 19 deletions(-) diff --git a/charts/must-gather/README.md b/charts/must-gather/README.md index 53fba50c..8566bcb6 100644 --- a/charts/must-gather/README.md +++ b/charts/must-gather/README.md @@ -25,11 +25,13 @@ Kubernetes: `>= 1.27.0-0` ## TL;DR ```console -helm install my-rhdh-must-gather rhdh-must-gather \ +helm upgrade --install my-rhdh-must-gather rhdh-must-gather \ --repo https://redhat-developer.github.io/rhdh-chart \ --version 0.1.0 ``` +Running the command again will automatically replace the previous job and start a new gather. + Then follow the instructions that will be printed to retrieve the gathered data. > **Tip**: List all releases using `helm list` @@ -104,10 +106,10 @@ The command removes all the Kubernetes resources associated with the chart and d | gather.withoutOperator | Exclusion options (set to true to skip collection) | bool | `false` | | image | Container image configuration | object | `{"pullPolicy":"","repository":"quay.io/rhdh-community/rhdh-must-gather","tag":"latest"}` | | imagePullSecrets | Secrets for pulling images from a private registry | list | `[]` | -| job | Job configuration | object | `{"activeDeadlineSeconds":3600,"backoffLimit":3,"ttlSecondsAfterFinished":""}` | +| job | Job configuration | object | `{"activeDeadlineSeconds":3600,"backoffLimit":3,"ttlSecondsAfterFinished":600}` | | job.activeDeadlineSeconds | Job timeout in seconds (default: 1 hour) | int | `3600` | | job.backoffLimit | Number of retries before marking job as failed | int | `3` | -| job.ttlSecondsAfterFinished | Set to a positive value to enable automatic cleanup | string | `""` | +| job.ttlSecondsAfterFinished | TTL for automatic cleanup after job finishes (seconds) | int | `600` | | nameOverride | Override the chart name | string | `""` | | nodeSelector | Node selector for pod scheduling | object | `{}` | | persistence | Persistent volume configuration for storing gathered data | object | `{"accessMode":"ReadWriteOnce","size":"1Gi","storageClass":""}` | diff --git a/charts/must-gather/README.md.gotmpl b/charts/must-gather/README.md.gotmpl index 088439d1..025ef74d 100644 --- a/charts/must-gather/README.md.gotmpl +++ b/charts/must-gather/README.md.gotmpl @@ -18,11 +18,13 @@ ## TL;DR ```console -helm install my-rhdh-must-gather rhdh-must-gather \ +helm upgrade --install my-rhdh-must-gather rhdh-must-gather \ --repo https://redhat-developer.github.io/rhdh-chart \ --version {{ template "chart.version" . }} ``` +Running the command again will automatically replace the previous job and start a new gather. + Then follow the instructions that will be printed to retrieve the gathered data. > **Tip**: List all releases using `helm list` diff --git a/charts/must-gather/templates/NOTES.txt b/charts/must-gather/templates/NOTES.txt index e466a3f6..78952f85 100644 --- a/charts/must-gather/templates/NOTES.txt +++ b/charts/must-gather/templates/NOTES.txt @@ -1,17 +1,21 @@ -RHDH Must-Gather has been deployed! +------------------------------------------------------------------- +RHDH Must-Gather job is now running. This might take a few minutes. -To retrieve the gathered diagnostic data: +To check the job logs: + kubectl -n {{ .Release.Namespace }} logs -f job/{{ include "rhdh-must-gather.jobName" . }} + +To retrieve the gathered diagnostic data once the job completes: 1. Wait for the job to complete: - kubectl -n {{ .Release.Namespace }} wait --for=condition=complete job/{{ include "rhdh-must-gather.fullname" . }} --timeout={{ .Values.job.activeDeadlineSeconds }}s + kubectl -n {{ .Release.Namespace }} wait --for=condition=complete job/{{ include "rhdh-must-gather.jobName" . }} --timeout={{ .Values.job.activeDeadlineSeconds }}s {{- if .Values.dataRetriever.enabled }} 2. Wait for the data retriever pod to be ready: - kubectl -n {{ .Release.Namespace }} wait --for=condition=ready pod/{{ include "rhdh-must-gather.fullname" . }}-data-retriever --timeout=60s + kubectl -n {{ .Release.Namespace }} wait --for=condition=ready pod/{{ include "rhdh-must-gather.dataRetrieverName" . }} --timeout=60s 3. Download the gathered data: - kubectl -n {{ .Release.Namespace }} exec {{ include "rhdh-must-gather.fullname" . }}-data-retriever -- tar czf - -C /data . > rhdh-must-gather-output.tar.gz + kubectl -n {{ .Release.Namespace }} exec {{ include "rhdh-must-gather.dataRetrieverName" . }} -- tar czf - -C /data . > rhdh-must-gather-output.tar.gz {{- else }} @@ -22,7 +26,7 @@ To retrieve the gathered diagnostic data: {{- end }} -4. Clean up the must-gather resources: +To clean up the must-gather resources: helm uninstall {{ .Release.Name }} -n {{ .Release.Namespace }} Configuration used: @@ -40,3 +44,4 @@ Configuration used: {{- end }} For more information, visit: https://github.com/redhat-developer/rhdh-must-gather +------------------------------------------------------------------- diff --git a/charts/must-gather/templates/_helpers.tpl b/charts/must-gather/templates/_helpers.tpl index bb199ec8..a2782a3e 100644 --- a/charts/must-gather/templates/_helpers.tpl +++ b/charts/must-gather/templates/_helpers.tpl @@ -60,3 +60,28 @@ Create the name of the service account to use {{- default "default" .Values.serviceAccount.name }} {{- end }} {{- end }} + +{{/* +Unique run ID based on the current timestamp (YYYYMMDDHHMMSS). +Appended to Job and data-retriever names so each helm install/upgrade +creates new resources, working around Kubernetes Job immutability. +*/}} +{{- define "rhdh-must-gather.runId" -}} +{{- now | date "20060102150405" }} +{{- end }} + +{{/* +Job name with unique run ID suffix. +Base name is truncated to 48 chars to stay within the 63-char DNS limit. +*/}} +{{- define "rhdh-must-gather.jobName" -}} +{{- printf "%s-%s" (include "rhdh-must-gather.fullname" . | trunc 48 | trimSuffix "-") (include "rhdh-must-gather.runId" .) }} +{{- end }} + +{{/* +Data retriever pod name with unique run ID suffix. +Base name is truncated to 32 chars to stay within the 63-char DNS limit. +*/}} +{{- define "rhdh-must-gather.dataRetrieverName" -}} +{{- printf "%s-data-retriever-%s" (include "rhdh-must-gather.fullname" . | trunc 32 | trimSuffix "-") (include "rhdh-must-gather.runId" .) }} +{{- end }} diff --git a/charts/must-gather/templates/data-retriever-pod.yaml b/charts/must-gather/templates/data-retriever-pod.yaml index c112adcb..084a0c6a 100644 --- a/charts/must-gather/templates/data-retriever-pod.yaml +++ b/charts/must-gather/templates/data-retriever-pod.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: Pod metadata: - name: {{ include "rhdh-must-gather.fullname" . }}-data-retriever + name: {{ include "rhdh-must-gather.dataRetrieverName" . }} labels: {{- include "rhdh-must-gather.labels" . | nindent 4 }} app.kubernetes.io/component: data-retriever diff --git a/charts/must-gather/templates/job.yaml b/charts/must-gather/templates/job.yaml index e7610493..4854dc9c 100644 --- a/charts/must-gather/templates/job.yaml +++ b/charts/must-gather/templates/job.yaml @@ -1,16 +1,14 @@ apiVersion: batch/v1 kind: Job metadata: - name: {{ include "rhdh-must-gather.fullname" . }} + name: {{ include "rhdh-must-gather.jobName" . }} labels: {{- include "rhdh-must-gather.labels" . | nindent 4 }} app.kubernetes.io/component: gather spec: activeDeadlineSeconds: {{ .Values.job.activeDeadlineSeconds }} backoffLimit: {{ .Values.job.backoffLimit }} - {{- if .Values.job.ttlSecondsAfterFinished }} ttlSecondsAfterFinished: {{ .Values.job.ttlSecondsAfterFinished }} - {{- end }} template: metadata: labels: diff --git a/charts/must-gather/templates/tests/test.yaml b/charts/must-gather/templates/tests/test.yaml index 12ab532d..565cf6b5 100644 --- a/charts/must-gather/templates/tests/test.yaml +++ b/charts/must-gather/templates/tests/test.yaml @@ -81,9 +81,9 @@ spec: fieldRef: fieldPath: metadata.namespace - name: JOB_NAME - value: {{ include "rhdh-must-gather.fullname" . }} + value: {{ include "rhdh-must-gather.jobName" . }} - name: DATA_RETRIEVER_POD - value: {{ include "rhdh-must-gather.fullname" . }}-data-retriever + value: {{ include "rhdh-must-gather.dataRetrieverName" . }} - name: JOB_TIMEOUT value: "{{ .Values.job.activeDeadlineSeconds }}" command: ["/bin/sh", "-c"] diff --git a/charts/must-gather/values.schema.json b/charts/must-gather/values.schema.json index 25bf6aa8..4fc8313d 100644 --- a/charts/must-gather/values.schema.json +++ b/charts/must-gather/values.schema.json @@ -1001,7 +1001,7 @@ "type": "integer" }, "ttlSecondsAfterFinished": { - "default": "", + "default": 600, "title": "TTL for automatic cleanup after job finishes (seconds). Set to a positive value to enable automatic cleanup.", "type": [ "integer", diff --git a/charts/must-gather/values.yaml b/charts/must-gather/values.yaml index 55554d3a..44f1e37e 100644 --- a/charts/must-gather/values.yaml +++ b/charts/must-gather/values.yaml @@ -40,8 +40,7 @@ job: # -- Number of retries before marking job as failed backoffLimit: 3 # -- TTL for automatic cleanup after job finishes (seconds) - # -- Set to a positive value to enable automatic cleanup - ttlSecondsAfterFinished: "" + ttlSecondsAfterFinished: 600 # -- Gather script configuration gather: From 7359a134887702c32cfbe127d68c42a43459f64e Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Thu, 12 Mar 2026 13:01:00 +0100 Subject: [PATCH 12/34] fix field in CI values --- charts/must-gather/ci/with-secrets-and-cluster-info-values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/must-gather/ci/with-secrets-and-cluster-info-values.yaml b/charts/must-gather/ci/with-secrets-and-cluster-info-values.yaml index f52888cd..0ae47bb7 100644 --- a/charts/must-gather/ci/with-secrets-and-cluster-info-values.yaml +++ b/charts/must-gather/ci/with-secrets-and-cluster-info-values.yaml @@ -2,4 +2,4 @@ gather: withSecrets: true clusterInfo: true - logLevel: "DEBUG" + logLevel: "debug" From bde908d119d9844ed912b6a532474b3e6ab65a99 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Thu, 12 Mar 2026 17:28:19 +0100 Subject: [PATCH 13/34] handle logLevel parameter correctly --- charts/must-gather/README.md | 2 +- charts/must-gather/ci/with-minimal-collection-values.yaml | 1 + .../ci/with-secrets-and-cluster-info-values.yaml | 2 +- charts/must-gather/templates/NOTES.txt | 2 +- charts/must-gather/templates/job.yaml | 2 +- charts/must-gather/values.schema.json | 6 +++++- charts/must-gather/values.schema.tmpl.json | 6 +++++- charts/must-gather/values.yaml | 2 +- 8 files changed, 16 insertions(+), 7 deletions(-) diff --git a/charts/must-gather/README.md b/charts/must-gather/README.md index 8566bcb6..b698ec25 100644 --- a/charts/must-gather/README.md +++ b/charts/must-gather/README.md @@ -98,7 +98,7 @@ The command removes all the Kubernetes resources associated with the chart and d | gather | Gather script configuration | object | `{"clusterInfo":false,"cmdTimeout":"30","extraArgs":[],"logLevel":"info","namespaces":"","since":"","sinceTime":"","withHeapDumps":false,"withSecrets":false,"withoutHelm":false,"withoutIngress":false,"withoutNamespaceInspect":false,"withoutOperator":false,"withoutOrchestrator":false,"withoutPlatform":false,"withoutRoute":false}` | | gather.cmdTimeout | Command timeout for individual kubectl/helm commands (seconds) | string | `"30"` | | gather.extraArgs | Additional custom arguments to pass to the gather script | list | `[]` | -| gather.logLevel | Log level: info, debug, trace | string | `"info"` | +| gather.logLevel | Log level: info, INFO, debug, DEBUG, trace, TRACE | string | `"info"` | | gather.namespaces | Example: "rhdh-prod,rhdh-staging" | string | `""` | | gather.since | Relative time for log collection (e.g., "2h", "30m") | string | `""` | | gather.sinceTime | Absolute timestamp for log collection (RFC3339 format) | string | `""` | diff --git a/charts/must-gather/ci/with-minimal-collection-values.yaml b/charts/must-gather/ci/with-minimal-collection-values.yaml index 9c1f242f..e7cf75e5 100644 --- a/charts/must-gather/ci/with-minimal-collection-values.yaml +++ b/charts/must-gather/ci/with-minimal-collection-values.yaml @@ -5,3 +5,4 @@ gather: withoutHelm: true withoutRoute: true withoutIngress: true + logLevel: "TRACE" diff --git a/charts/must-gather/ci/with-secrets-and-cluster-info-values.yaml b/charts/must-gather/ci/with-secrets-and-cluster-info-values.yaml index 0ae47bb7..f52888cd 100644 --- a/charts/must-gather/ci/with-secrets-and-cluster-info-values.yaml +++ b/charts/must-gather/ci/with-secrets-and-cluster-info-values.yaml @@ -2,4 +2,4 @@ gather: withSecrets: true clusterInfo: true - logLevel: "debug" + logLevel: "DEBUG" diff --git a/charts/must-gather/templates/NOTES.txt b/charts/must-gather/templates/NOTES.txt index 78952f85..e8cfe8af 100644 --- a/charts/must-gather/templates/NOTES.txt +++ b/charts/must-gather/templates/NOTES.txt @@ -30,7 +30,7 @@ To clean up the must-gather resources: helm uninstall {{ .Release.Name }} -n {{ .Release.Namespace }} Configuration used: - - Log level: {{ .Values.gather.logLevel }} + - Log level: {{ .Values.gather.logLevel | lower }} - Job timeout: {{ .Values.job.activeDeadlineSeconds }}s - Storage size: {{ .Values.persistence.size }} {{- if .Values.gather.namespaces }} diff --git a/charts/must-gather/templates/job.yaml b/charts/must-gather/templates/job.yaml index 4854dc9c..bb6b9558 100644 --- a/charts/must-gather/templates/job.yaml +++ b/charts/must-gather/templates/job.yaml @@ -49,7 +49,7 @@ spec: - name: BASE_COLLECTION_PATH value: "/must-gather" - name: LOG_LEVEL - value: {{ .Values.gather.logLevel | quote }} + value: {{ .Values.gather.logLevel | lower | quote }} - name: CMD_TIMEOUT value: {{ .Values.gather.cmdTimeout | quote }} {{- if .Values.gather.since }} diff --git a/charts/must-gather/values.schema.json b/charts/must-gather/values.schema.json index 4fc8313d..379bd996 100644 --- a/charts/must-gather/values.schema.json +++ b/charts/must-gather/values.schema.json @@ -876,8 +876,12 @@ "default": "info", "enum": [ "info", + "INFO", "debug", - "trace" + "DEBUG", + "trace", + "TRACE", + "" ], "title": "Log level for the gather script.", "type": "string" diff --git a/charts/must-gather/values.schema.tmpl.json b/charts/must-gather/values.schema.tmpl.json index 890d897d..6ea71083 100644 --- a/charts/must-gather/values.schema.tmpl.json +++ b/charts/must-gather/values.schema.tmpl.json @@ -130,8 +130,12 @@ "default": "info", "enum": [ "info", + "INFO", "debug", - "trace" + "DEBUG", + "trace", + "TRACE", + "" ] }, "cmdTimeout": { diff --git a/charts/must-gather/values.yaml b/charts/must-gather/values.yaml index 44f1e37e..db80ebcb 100644 --- a/charts/must-gather/values.yaml +++ b/charts/must-gather/values.yaml @@ -44,7 +44,7 @@ job: # -- Gather script configuration gather: - # -- Log level: info, debug, trace + # -- Log level: info, INFO, debug, DEBUG, trace, TRACE logLevel: "info" # -- Command timeout for individual kubectl/helm commands (seconds) cmdTimeout: "30" From a7a75ec8fa1770b8ed42ae9dd6ebd68cf2046c64 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Thu, 12 Mar 2026 17:51:53 +0100 Subject: [PATCH 14/34] make gather.namespaces an array instead to simplify config --- charts/must-gather/README.md | 4 ++-- charts/must-gather/ci/with-namespace-scoped-values.yaml | 4 +++- charts/must-gather/templates/NOTES.txt | 2 +- charts/must-gather/templates/job.yaml | 2 +- charts/must-gather/values.schema.json | 9 ++++++--- charts/must-gather/values.schema.tmpl.json | 9 ++++++--- charts/must-gather/values.yaml | 6 +++--- 7 files changed, 22 insertions(+), 14 deletions(-) diff --git a/charts/must-gather/README.md b/charts/must-gather/README.md index b698ec25..4a174c97 100644 --- a/charts/must-gather/README.md +++ b/charts/must-gather/README.md @@ -95,11 +95,11 @@ The command removes all the Kubernetes resources associated with the chart and d | dataRetriever.image | Image for the data retriever pod | object | `{"pullPolicy":"","repository":"registry.access.redhat.com/ubi9","tag":"latest"}` | | dataRetriever.resources | Resource configuration | object | `{"limits":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"},"requests":{"cpu":"50m","ephemeral-storage":"32Mi","memory":"64Mi"}}` | | fullnameOverride | | string | `""` | -| gather | Gather script configuration | object | `{"clusterInfo":false,"cmdTimeout":"30","extraArgs":[],"logLevel":"info","namespaces":"","since":"","sinceTime":"","withHeapDumps":false,"withSecrets":false,"withoutHelm":false,"withoutIngress":false,"withoutNamespaceInspect":false,"withoutOperator":false,"withoutOrchestrator":false,"withoutPlatform":false,"withoutRoute":false}` | +| gather | Gather script configuration | object | `{"clusterInfo":false,"cmdTimeout":"30","extraArgs":[],"logLevel":"info","namespaces":[],"since":"","sinceTime":"","withHeapDumps":false,"withSecrets":false,"withoutHelm":false,"withoutIngress":false,"withoutNamespaceInspect":false,"withoutOperator":false,"withoutOrchestrator":false,"withoutPlatform":false,"withoutRoute":false}` | | gather.cmdTimeout | Command timeout for individual kubectl/helm commands (seconds) | string | `"30"` | | gather.extraArgs | Additional custom arguments to pass to the gather script | list | `[]` | | gather.logLevel | Log level: info, INFO, debug, DEBUG, trace, TRACE | string | `"info"` | -| gather.namespaces | Example: "rhdh-prod,rhdh-staging" | string | `""` | +| gather.namespaces | Example: ["rhdh-prod", "rhdh-staging"] | list | `[]` | | gather.since | Relative time for log collection (e.g., "2h", "30m") | string | `""` | | gather.sinceTime | Absolute timestamp for log collection (RFC3339 format) | string | `""` | | gather.withSecrets | Optional collection features (disabled by default) | bool | `false` | diff --git a/charts/must-gather/ci/with-namespace-scoped-values.yaml b/charts/must-gather/ci/with-namespace-scoped-values.yaml index 99db13cb..781f5fe5 100644 --- a/charts/must-gather/ci/with-namespace-scoped-values.yaml +++ b/charts/must-gather/ci/with-namespace-scoped-values.yaml @@ -1,4 +1,6 @@ # Scope collection to specific namespaces gather: - namespaces: "rhdh-prod,rhdh-staging" + namespaces: + - "rhdh-prod" + - "rhdh-staging" since: "2h" diff --git a/charts/must-gather/templates/NOTES.txt b/charts/must-gather/templates/NOTES.txt index e8cfe8af..36a7defe 100644 --- a/charts/must-gather/templates/NOTES.txt +++ b/charts/must-gather/templates/NOTES.txt @@ -34,7 +34,7 @@ Configuration used: - Job timeout: {{ .Values.job.activeDeadlineSeconds }}s - Storage size: {{ .Values.persistence.size }} {{- if .Values.gather.namespaces }} - - Target namespaces: {{ .Values.gather.namespaces }} + - Target namespaces: {{ .Values.gather.namespaces | join ", " }} {{- end }} {{- if .Values.gather.withSecrets }} - Secrets collection: enabled (sanitized) diff --git a/charts/must-gather/templates/job.yaml b/charts/must-gather/templates/job.yaml index bb6b9558..2f16eb18 100644 --- a/charts/must-gather/templates/job.yaml +++ b/charts/must-gather/templates/job.yaml @@ -94,7 +94,7 @@ spec: {{- end }} {{- if .Values.gather.namespaces }} - "--namespaces" - - {{ .Values.gather.namespaces | quote }} + - {{ .Values.gather.namespaces | join "," | quote }} {{- end }} {{- with .Values.gather.extraArgs }} {{- toYaml . | nindent 12 }} diff --git a/charts/must-gather/values.schema.json b/charts/must-gather/values.schema.json index 379bd996..53d25c25 100644 --- a/charts/must-gather/values.schema.json +++ b/charts/must-gather/values.schema.json @@ -887,9 +887,12 @@ "type": "string" }, "namespaces": { - "default": "", - "title": "Limit collection to specific namespaces (comma-separated). Example: 'rhdh-prod,rhdh-staging'.", - "type": "string" + "default": [], + "items": { + "type": "string" + }, + "title": "Limit collection to specific namespaces. Example: ['rhdh-prod', 'rhdh-staging'].", + "type": "array" }, "since": { "default": "", diff --git a/charts/must-gather/values.schema.tmpl.json b/charts/must-gather/values.schema.tmpl.json index 6ea71083..fa575460 100644 --- a/charts/must-gather/values.schema.tmpl.json +++ b/charts/must-gather/values.schema.tmpl.json @@ -204,9 +204,12 @@ "default": false }, "namespaces": { - "title": "Limit collection to specific namespaces (comma-separated). Example: 'rhdh-prod,rhdh-staging'.", - "type": "string", - "default": "" + "title": "Limit collection to specific namespaces. Example: ['rhdh-prod', 'rhdh-staging'].", + "type": "array", + "items": { + "type": "string" + }, + "default": [] }, "extraArgs": { "title": "Additional custom arguments to pass to the gather script.", diff --git a/charts/must-gather/values.yaml b/charts/must-gather/values.yaml index db80ebcb..29a7fc01 100644 --- a/charts/must-gather/values.yaml +++ b/charts/must-gather/values.yaml @@ -67,9 +67,9 @@ gather: withoutIngress: false withoutNamespaceInspect: false - # -- Limit collection to specific namespaces (comma-separated) - # -- Example: "rhdh-prod,rhdh-staging" - namespaces: "" + # -- Limit collection to specific namespaces + # -- Example: ["rhdh-prod", "rhdh-staging"] + namespaces: [] # -- Additional custom arguments to pass to the gather script extraArgs: [] From b30421a8670ec54f09dc787438a3cb6b419487df Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Thu, 12 Mar 2026 17:59:47 +0100 Subject: [PATCH 15/34] set podSecurityContext and container security context to improve security posture --- .gitignore | 2 ++ charts/must-gather/README.md | 8 +++--- .../templates/data-retriever-pod.yaml | 12 +++++++-- charts/must-gather/templates/tests/test.yaml | 8 ++++++ charts/must-gather/values.schema.json | 10 +++---- charts/must-gather/values.schema.tmpl.json | 25 +++++++++++++----- charts/must-gather/values.yaml | 26 ++++++++++--------- 7 files changed, 61 insertions(+), 30 deletions(-) diff --git a/.gitignore b/.gitignore index 8d0f9547..cb931b3c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ .LSOverride .idea +*.tar.gz + # helm chart dependencies charts/*/charts/ **/charts/*.tgz diff --git a/charts/must-gather/README.md b/charts/must-gather/README.md index 4a174c97..8b2c1112 100644 --- a/charts/must-gather/README.md +++ b/charts/must-gather/README.md @@ -90,9 +90,9 @@ The command removes all the Kubernetes resources associated with the chart and d | Key | Description | Type | Default | |-----|-------------|------|---------| | affinity | Affinity rules for pod scheduling | object | `{}` | -| dataRetriever | This pod allows you to retrieve the gathered data after the job completes | object | `{"enabled":true,"image":{"pullPolicy":"","repository":"registry.access.redhat.com/ubi9","tag":"latest"},"resources":{"limits":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"},"requests":{"cpu":"50m","ephemeral-storage":"32Mi","memory":"64Mi"}}}` | +| dataRetriever | This pod allows you to retrieve the gathered data after the job completes | object | `{"enabled":true,"image":{"pullPolicy":"","repository":"","tag":""},"resources":{"limits":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"},"requests":{"cpu":"50m","ephemeral-storage":"32Mi","memory":"64Mi"}}}` | | dataRetriever.enabled | Enable the data retriever pod | bool | `true` | -| dataRetriever.image | Image for the data retriever pod | object | `{"pullPolicy":"","repository":"registry.access.redhat.com/ubi9","tag":"latest"}` | +| dataRetriever.image | Image for the data retriever pod (defaults to the main must-gather image) | object | `{"pullPolicy":"","repository":"","tag":""}` | | dataRetriever.resources | Resource configuration | object | `{"limits":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"},"requests":{"cpu":"50m","ephemeral-storage":"32Mi","memory":"64Mi"}}` | | fullnameOverride | | string | `""` | | gather | Gather script configuration | object | `{"clusterInfo":false,"cmdTimeout":"30","extraArgs":[],"logLevel":"info","namespaces":[],"since":"","sinceTime":"","withHeapDumps":false,"withSecrets":false,"withoutHelm":false,"withoutIngress":false,"withoutNamespaceInspect":false,"withoutOperator":false,"withoutOrchestrator":false,"withoutPlatform":false,"withoutRoute":false}` | @@ -118,11 +118,11 @@ The command removes all the Kubernetes resources associated with the chart and d | persistence.storageClass | Storage class (empty = use cluster default) | string | `""` | | podAnnotations | Pod annotations | object | `{}` | | podLabels | Pod labels | object | `{}` | -| podSecurityContext | Pod security context | object | `{}` | +| podSecurityContext | On OCP, the SCC may override fsGroup with a value from the namespace's allowed range. | object | `{"fsGroup":1001,"runAsNonRoot":true,"seccompProfile":{"type":"RuntimeDefault"}}` | | rbac | RBAC configuration | object | `{"create":true}` | | rbac.create | Create ClusterRole and ClusterRoleBinding for cluster-wide read access | bool | `true` | | resources | Resource requests and limits for the gather job | object | `{"limits":{"cpu":"500m","ephemeral-storage":"128Mi","memory":"512Mi"},"requests":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"}}` | -| securityContext | Container security context | object | `{}` | +| securityContext | Container security context | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]}}` | | serviceAccount | Service account configuration | object | `{"annotations":{},"automount":true,"create":true,"name":""}` | | serviceAccount.annotations | Annotations to add to the service account | object | `{}` | | serviceAccount.automount | Automatically mount a ServiceAccount's API credentials | bool | `true` | diff --git a/charts/must-gather/templates/data-retriever-pod.yaml b/charts/must-gather/templates/data-retriever-pod.yaml index 084a0c6a..6e18f4eb 100644 --- a/charts/must-gather/templates/data-retriever-pod.yaml +++ b/charts/must-gather/templates/data-retriever-pod.yaml @@ -13,6 +13,10 @@ spec: imagePullSecrets: {{- toYaml . | nindent 4 }} {{- end }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 4 }} + {{- end }} affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: @@ -23,8 +27,12 @@ spec: topologyKey: kubernetes.io/hostname containers: - name: data-retriever - image: "{{ .Values.dataRetriever.image.repository }}:{{ .Values.dataRetriever.image.tag }}" # NOSONAR - tag is user-configurable via Helm values - imagePullPolicy: {{ .Values.dataRetriever.image.pullPolicy }} + image: "{{ .Values.dataRetriever.image.repository | default .Values.image.repository }}:{{ .Values.dataRetriever.image.tag | default .Values.image.tag | default .Chart.AppVersion }}" # NOSONAR + imagePullPolicy: {{ .Values.dataRetriever.image.pullPolicy | default .Values.image.pullPolicy }} + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} command: - sleep - infinity diff --git a/charts/must-gather/templates/tests/test.yaml b/charts/must-gather/templates/tests/test.yaml index 565cf6b5..1de6ad0c 100644 --- a/charts/must-gather/templates/tests/test.yaml +++ b/charts/must-gather/templates/tests/test.yaml @@ -71,10 +71,18 @@ spec: imagePullSecrets: {{- toYaml . | nindent 4 }} {{- end }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 4 }} + {{- end }} containers: - name: test image: "{{ .Values.test.image.repository }}:{{ .Values.test.image.tag }}" # NOSONAR - tag is user-configurable via Helm values imagePullPolicy: {{ .Values.test.image.pullPolicy }} + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} env: - name: NAMESPACE valueFrom: diff --git a/charts/must-gather/values.schema.json b/charts/must-gather/values.schema.json index 53d25c25..c48ac4a5 100644 --- a/charts/must-gather/values.schema.json +++ b/charts/must-gather/values.schema.json @@ -778,17 +778,17 @@ "type": "string" }, "repository": { - "default": "registry.access.redhat.com/ubi9", - "title": "Image repository.", + "default": "", + "title": "Image repository. Empty defaults to the main must-gather image.", "type": "string" }, "tag": { - "default": "latest", - "title": "Image tag.", + "default": "", + "title": "Image tag. Empty defaults to the main must-gather image tag.", "type": "string" } }, - "title": "Image for the data retriever pod.", + "title": "Image for the data retriever pod. Defaults to the main must-gather image if not set.", "type": "object" }, "resources": { diff --git a/charts/must-gather/values.schema.tmpl.json b/charts/must-gather/values.schema.tmpl.json index fa575460..fe8f90dd 100644 --- a/charts/must-gather/values.schema.tmpl.json +++ b/charts/must-gather/values.schema.tmpl.json @@ -310,19 +310,19 @@ "default": true }, "image": { - "title": "Image for the data retriever pod.", + "title": "Image for the data retriever pod. Defaults to the main must-gather image if not set.", "type": "object", "additionalProperties": false, "properties": { "repository": { - "title": "Image repository.", + "title": "Image repository. Empty defaults to the main must-gather image.", "type": "string", - "default": "registry.access.redhat.com/ubi9" + "default": "" }, "tag": { - "title": "Image tag.", + "title": "Image tag. Empty defaults to the main must-gather image tag.", "type": "string", - "default": "latest" + "default": "" }, "pullPolicy": { "title": "Image pull policy.", @@ -444,13 +444,24 @@ "title": "Security settings for a Pod.", "description": "The security settings that you specify for a Pod apply to all Containers in the Pod. Ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod", "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.4/_definitions.json#/definitions/io.k8s.api.core.v1.SecurityContext", - "default": {} + "default": { + "runAsNonRoot": true, + "fsGroup": 1001, + "seccompProfile": { + "type": "RuntimeDefault" + } + } }, "securityContext": { "title": "Security settings for a Container.", "description": "Ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container", "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.4/_definitions.json#/definitions/io.k8s.api.core.v1.SecurityContext", - "default": {} + "default": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": ["ALL"] + } + } }, "nodeSelector": { "title": "Node labels for pod assignment", diff --git a/charts/must-gather/values.yaml b/charts/must-gather/values.yaml index 29a7fc01..8cc39ae2 100644 --- a/charts/must-gather/values.yaml +++ b/charts/must-gather/values.yaml @@ -99,10 +99,10 @@ persistence: dataRetriever: # -- Enable the data retriever pod enabled: true - # -- Image for the data retriever pod + # -- Image for the data retriever pod (defaults to the main must-gather image) image: - repository: registry.access.redhat.com/ubi9 - tag: "latest" + repository: "" + tag: "" pullPolicy: "" # -- Resource configuration resources: @@ -132,17 +132,19 @@ podAnnotations: {} podLabels: {} # -- Pod security context -podSecurityContext: {} - # -- fsGroup: 2000 +# -- On OCP, the SCC may override fsGroup with a value from the namespace's allowed range. +podSecurityContext: + runAsNonRoot: true + fsGroup: 1001 + seccompProfile: + type: RuntimeDefault # -- Container security context -securityContext: {} - # -- capabilities: - # -- drop: - # -- - ALL - # -- readOnlyRootFilesystem: true - # -- runAsNonRoot: true - # -- runAsUser: 1000 +securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL # -- Node selector for pod scheduling nodeSelector: {} From 44c21b7aa7b62d20eb74c29e3f211027a53b5278 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Thu, 12 Mar 2026 18:47:49 +0100 Subject: [PATCH 16/34] better handle image refs in the values file and support digests --- charts/must-gather/README.md | 12 ++++--- charts/must-gather/templates/_helpers.tpl | 24 ++++++++++++++ .../templates/data-retriever-pod.yaml | 9 ++++-- charts/must-gather/templates/job.yaml | 2 +- charts/must-gather/templates/tests/test.yaml | 2 +- charts/must-gather/values.schema.json | 32 ++++++++++++++++++- charts/must-gather/values.schema.tmpl.json | 32 ++++++++++++++++++- charts/must-gather/values.yaml | 11 +++++-- 8 files changed, 111 insertions(+), 13 deletions(-) diff --git a/charts/must-gather/README.md b/charts/must-gather/README.md index 8b2c1112..2c6efbf0 100644 --- a/charts/must-gather/README.md +++ b/charts/must-gather/README.md @@ -90,9 +90,9 @@ The command removes all the Kubernetes resources associated with the chart and d | Key | Description | Type | Default | |-----|-------------|------|---------| | affinity | Affinity rules for pod scheduling | object | `{}` | -| dataRetriever | This pod allows you to retrieve the gathered data after the job completes | object | `{"enabled":true,"image":{"pullPolicy":"","repository":"","tag":""},"resources":{"limits":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"},"requests":{"cpu":"50m","ephemeral-storage":"32Mi","memory":"64Mi"}}}` | +| dataRetriever | This pod allows you to retrieve the gathered data after the job completes | object | `{"enabled":true,"image":{"digest":"","pullPolicy":"","registry":"","repository":"","tag":""},"resources":{"limits":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"},"requests":{"cpu":"50m","ephemeral-storage":"32Mi","memory":"64Mi"}}}` | | dataRetriever.enabled | Enable the data retriever pod | bool | `true` | -| dataRetriever.image | Image for the data retriever pod (defaults to the main must-gather image) | object | `{"pullPolicy":"","repository":"","tag":""}` | +| dataRetriever.image | Image for the data retriever pod (defaults to the main must-gather image) | object | `{"digest":"","pullPolicy":"","registry":"","repository":"","tag":""}` | | dataRetriever.resources | Resource configuration | object | `{"limits":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"},"requests":{"cpu":"50m","ephemeral-storage":"32Mi","memory":"64Mi"}}` | | fullnameOverride | | string | `""` | | gather | Gather script configuration | object | `{"clusterInfo":false,"cmdTimeout":"30","extraArgs":[],"logLevel":"info","namespaces":[],"since":"","sinceTime":"","withHeapDumps":false,"withSecrets":false,"withoutHelm":false,"withoutIngress":false,"withoutNamespaceInspect":false,"withoutOperator":false,"withoutOrchestrator":false,"withoutPlatform":false,"withoutRoute":false}` | @@ -104,7 +104,9 @@ The command removes all the Kubernetes resources associated with the chart and d | gather.sinceTime | Absolute timestamp for log collection (RFC3339 format) | string | `""` | | gather.withSecrets | Optional collection features (disabled by default) | bool | `false` | | gather.withoutOperator | Exclusion options (set to true to skip collection) | bool | `false` | -| image | Container image configuration | object | `{"pullPolicy":"","repository":"quay.io/rhdh-community/rhdh-must-gather","tag":"latest"}` | +| image | Container image configuration | object | `{"digest":"","pullPolicy":"","registry":"quay.io","repository":"rhdh-community/rhdh-must-gather","tag":"latest"}` | +| image.digest | Image digest (e.g., sha256:abc123...). Can be used with or without tag. | string | `""` | +| image.tag | Overrides the image tag whose default is the chart appVersion. | string | `"latest"` | | imagePullSecrets | Secrets for pulling images from a private registry | list | `[]` | | job | Job configuration | object | `{"activeDeadlineSeconds":3600,"backoffLimit":3,"ttlSecondsAfterFinished":600}` | | job.activeDeadlineSeconds | Job timeout in seconds (default: 1 hour) | int | `3600` | @@ -128,8 +130,8 @@ The command removes all the Kubernetes resources associated with the chart and d | serviceAccount.automount | Automatically mount a ServiceAccount's API credentials | bool | `true` | | serviceAccount.create | Specifies whether a service account should be created | bool | `true` | | serviceAccount.name | If not set and create is true, a name is generated using the fullname template | string | `""` | -| test | Helm test configuration | object | `{"enabled":true,"image":{"pullPolicy":"","repository":"bitnami/kubectl","tag":"latest"}}` | +| test | Helm test configuration | object | `{"enabled":true,"image":{"digest":"","pullPolicy":"","registry":"docker.io","repository":"bitnami/kubectl","tag":"latest"}}` | | test.enabled | Enable the Helm test | bool | `true` | -| test.image | Image for the test pod | object | `{"pullPolicy":"","repository":"bitnami/kubectl","tag":"latest"}` | +| test.image | Image for the test pod | object | `{"digest":"","pullPolicy":"","registry":"docker.io","repository":"bitnami/kubectl","tag":"latest"}` | | tolerations | Tolerations for pod scheduling | list | `[]` | diff --git a/charts/must-gather/templates/_helpers.tpl b/charts/must-gather/templates/_helpers.tpl index a2782a3e..5885c77b 100644 --- a/charts/must-gather/templates/_helpers.tpl +++ b/charts/must-gather/templates/_helpers.tpl @@ -78,6 +78,30 @@ Base name is truncated to 48 chars to stay within the 63-char DNS limit. {{- printf "%s-%s" (include "rhdh-must-gather.fullname" . | trunc 48 | trimSuffix "-") (include "rhdh-must-gather.runId" .) }} {{- end }} +{{/* +Build a full image reference from registry, repository, and tag. +Usage: {{ include "rhdh-must-gather.image" (dict "image" .Values.image "defaultTag" .Chart.AppVersion) }} +*/}} +{{- define "rhdh-must-gather.image" -}} +{{- $registry := .image.registry | default "" -}} +{{- $repository := .image.repository -}} +{{- $tag := .image.tag | default .defaultTag | default "" -}} +{{- $digest := .image.digest | default "" -}} +{{- $ref := "" -}} +{{- if and $tag $digest -}} +{{- $ref = printf ":%s@%s" $tag $digest -}} +{{- else if $digest -}} +{{- $ref = printf "@%s" $digest -}} +{{- else if $tag -}} +{{- $ref = printf ":%s" $tag -}} +{{- end -}} +{{- if $registry -}} +{{- printf "%s/%s%s" $registry $repository $ref -}} +{{- else -}} +{{- printf "%s%s" $repository $ref -}} +{{- end -}} +{{- end -}} + {{/* Data retriever pod name with unique run ID suffix. Base name is truncated to 32 chars to stay within the 63-char DNS limit. diff --git a/charts/must-gather/templates/data-retriever-pod.yaml b/charts/must-gather/templates/data-retriever-pod.yaml index 6e18f4eb..2fade6c3 100644 --- a/charts/must-gather/templates/data-retriever-pod.yaml +++ b/charts/must-gather/templates/data-retriever-pod.yaml @@ -27,8 +27,13 @@ spec: topologyKey: kubernetes.io/hostname containers: - name: data-retriever - image: "{{ .Values.dataRetriever.image.repository | default .Values.image.repository }}:{{ .Values.dataRetriever.image.tag | default .Values.image.tag | default .Chart.AppVersion }}" # NOSONAR - imagePullPolicy: {{ .Values.dataRetriever.image.pullPolicy | default .Values.image.pullPolicy }} + {{- $drImage := deepCopy .Values.image }} + {{- if .Values.dataRetriever.image.registry }}{{ $_ := set $drImage "registry" .Values.dataRetriever.image.registry }}{{- end }} + {{- if .Values.dataRetriever.image.repository }}{{ $_ := set $drImage "repository" .Values.dataRetriever.image.repository }}{{- end }} + {{- if .Values.dataRetriever.image.tag }}{{ $_ := set $drImage "tag" .Values.dataRetriever.image.tag }}{{- end }} + {{- if .Values.dataRetriever.image.digest }}{{ $_ := set $drImage "digest" .Values.dataRetriever.image.digest }}{{- end }} + image: {{ include "rhdh-must-gather.image" (dict "image" $drImage "defaultTag" .Chart.AppVersion) | quote }} # NOSONAR + imagePullPolicy: {{ .Values.dataRetriever.image.pullPolicy | default .Values.image.pullPolicy | default "IfNotPresent" }} {{- with .Values.securityContext }} securityContext: {{- toYaml . | nindent 8 }} diff --git a/charts/must-gather/templates/job.yaml b/charts/must-gather/templates/job.yaml index 2f16eb18..8a312e30 100644 --- a/charts/must-gather/templates/job.yaml +++ b/charts/must-gather/templates/job.yaml @@ -35,7 +35,7 @@ spec: {{- end }} containers: - name: gather - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" # NOSONAR - tag is user-configurable via Helm values; defaults to Chart.AppVersion + image: {{ include "rhdh-must-gather.image" (dict "image" .Values.image "defaultTag" .Chart.AppVersion) | quote }} # NOSONAR imagePullPolicy: {{ .Values.image.pullPolicy }} {{- with .Values.securityContext }} securityContext: diff --git a/charts/must-gather/templates/tests/test.yaml b/charts/must-gather/templates/tests/test.yaml index 1de6ad0c..a2113063 100644 --- a/charts/must-gather/templates/tests/test.yaml +++ b/charts/must-gather/templates/tests/test.yaml @@ -77,7 +77,7 @@ spec: {{- end }} containers: - name: test - image: "{{ .Values.test.image.repository }}:{{ .Values.test.image.tag }}" # NOSONAR - tag is user-configurable via Helm values + image: {{ include "rhdh-must-gather.image" (dict "image" .Values.test.image "defaultTag" "latest") | quote }} # NOSONAR imagePullPolicy: {{ .Values.test.image.pullPolicy }} {{- with .Values.securityContext }} securityContext: diff --git a/charts/must-gather/values.schema.json b/charts/must-gather/values.schema.json index c48ac4a5..e55de6e6 100644 --- a/charts/must-gather/values.schema.json +++ b/charts/must-gather/values.schema.json @@ -766,6 +766,11 @@ "image": { "additionalProperties": false, "properties": { + "digest": { + "default": "", + "title": "Image digest. Empty defaults to the main must-gather image digest.", + "type": "string" + }, "pullPolicy": { "default": "", "enum": [ @@ -777,6 +782,11 @@ "title": "Image pull policy.", "type": "string" }, + "registry": { + "default": "", + "title": "Image registry. Empty defaults to the main must-gather image registry.", + "type": "string" + }, "repository": { "default": "", "title": "Image repository. Empty defaults to the main must-gather image.", @@ -956,6 +966,11 @@ "image": { "additionalProperties": false, "properties": { + "digest": { + "default": "", + "title": "Image digest (e.g., sha256:abc123...). Can be used with or without tag.", + "type": "string" + }, "pullPolicy": { "default": "", "enum": [ @@ -967,8 +982,13 @@ "title": "Image pull policy.", "type": "string" }, + "registry": { + "default": "quay.io", + "title": "Image registry.", + "type": "string" + }, "repository": { - "default": "quay.io/rhdh-community/rhdh-must-gather", + "default": "rhdh-community/rhdh-must-gather", "title": "Image repository.", "type": "string" }, @@ -1471,6 +1491,11 @@ "image": { "additionalProperties": false, "properties": { + "digest": { + "default": "", + "title": "Image digest (e.g., sha256:abc123...). Can be used with or without tag.", + "type": "string" + }, "pullPolicy": { "default": "", "enum": [ @@ -1482,6 +1507,11 @@ "title": "Image pull policy.", "type": "string" }, + "registry": { + "default": "docker.io", + "title": "Image registry.", + "type": "string" + }, "repository": { "default": "bitnami/kubectl", "title": "Image repository.", diff --git a/charts/must-gather/values.schema.tmpl.json b/charts/must-gather/values.schema.tmpl.json index fe8f90dd..b05fa4e2 100644 --- a/charts/must-gather/values.schema.tmpl.json +++ b/charts/must-gather/values.schema.tmpl.json @@ -9,10 +9,15 @@ "type": "object", "additionalProperties": false, "properties": { + "registry": { + "title": "Image registry.", + "type": "string", + "default": "quay.io" + }, "repository": { "title": "Image repository.", "type": "string", - "default": "quay.io/rhdh-community/rhdh-must-gather" + "default": "rhdh-community/rhdh-must-gather" }, "pullPolicy": { "title": "Image pull policy.", @@ -29,6 +34,11 @@ "title": "Overrides the image tag whose default is the chart appVersion.", "type": "string", "default": "latest" + }, + "digest": { + "title": "Image digest (e.g., sha256:abc123...). Can be used with or without tag.", + "type": "string", + "default": "" } } }, @@ -314,6 +324,11 @@ "type": "object", "additionalProperties": false, "properties": { + "registry": { + "title": "Image registry. Empty defaults to the main must-gather image registry.", + "type": "string", + "default": "" + }, "repository": { "title": "Image repository. Empty defaults to the main must-gather image.", "type": "string", @@ -324,6 +339,11 @@ "type": "string", "default": "" }, + "digest": { + "title": "Image digest. Empty defaults to the main must-gather image digest.", + "type": "string", + "default": "" + }, "pullPolicy": { "title": "Image pull policy.", "type": "string", @@ -405,6 +425,11 @@ "type": "object", "additionalProperties": false, "properties": { + "registry": { + "title": "Image registry.", + "type": "string", + "default": "docker.io" + }, "repository": { "title": "Image repository.", "type": "string", @@ -415,6 +440,11 @@ "type": "string", "default": "latest" }, + "digest": { + "title": "Image digest (e.g., sha256:abc123...). Can be used with or without tag.", + "type": "string", + "default": "" + }, "pullPolicy": { "title": "Image pull policy.", "type": "string", diff --git a/charts/must-gather/values.yaml b/charts/must-gather/values.yaml index 8cc39ae2..6fbe40a7 100644 --- a/charts/must-gather/values.yaml +++ b/charts/must-gather/values.yaml @@ -4,10 +4,13 @@ # -- Container image configuration image: - repository: quay.io/rhdh-community/rhdh-must-gather + registry: quay.io + repository: rhdh-community/rhdh-must-gather pullPolicy: "" - # -- -- Overrides the image tag whose default is the chart appVersion. + # -- Overrides the image tag whose default is the chart appVersion. tag: "latest" + # -- Image digest (e.g., sha256:abc123...). Can be used with or without tag. + digest: "" # -- Secrets for pulling images from a private registry imagePullSecrets: [] @@ -101,8 +104,10 @@ dataRetriever: enabled: true # -- Image for the data retriever pod (defaults to the main must-gather image) image: + registry: "" repository: "" tag: "" + digest: "" pullPolicy: "" # -- Resource configuration resources: @@ -121,8 +126,10 @@ test: enabled: true # -- Image for the test pod image: + registry: docker.io repository: bitnami/kubectl tag: "latest" + digest: "" pullPolicy: "" # -- Pod annotations From eba8205bb1155a2696f0b73c99b4483d836925fd Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Thu, 12 Mar 2026 22:25:10 +0100 Subject: [PATCH 17/34] Apply suggestions from code review Co-authored-by: rhdh-qodo-merge[bot] <232573409+rhdh-qodo-merge[bot]@users.noreply.github.com> --- charts/must-gather/templates/pvc.yaml | 2 +- charts/must-gather/templates/tests/test.yaml | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/charts/must-gather/templates/pvc.yaml b/charts/must-gather/templates/pvc.yaml index 0504a3c8..66bf6449 100644 --- a/charts/must-gather/templates/pvc.yaml +++ b/charts/must-gather/templates/pvc.yaml @@ -12,4 +12,4 @@ spec: {{- end }} resources: requests: - storage: {{ .Values.persistence.size }} + storage: {{ .Values.persistence.size | quote }} diff --git a/charts/must-gather/templates/tests/test.yaml b/charts/must-gather/templates/tests/test.yaml index a2113063..70feaf87 100644 --- a/charts/must-gather/templates/tests/test.yaml +++ b/charts/must-gather/templates/tests/test.yaml @@ -108,8 +108,9 @@ spec: kubectl -n "$NAMESPACE" exec "$DATA_RETRIEVER_POD" -- tar czf - -C /data . > /tmp/rhdh-must-gather-output.tar.gz size=$(stat -c%s /tmp/rhdh-must-gather-output.tar.gz 2>/dev/null || stat -f%z /tmp/rhdh-must-gather-output.tar.gz 2>/dev/null) - if [ "$size" -le 0 ] 2>/dev/null; then - echo "FAIL: retrieved archive is empty" + file_count=$(tar -tzf /tmp/rhdh-must-gather-output.tar.gz | wc -l) + if [ "$size" -le 0 ] 2>/dev/null || [ "$file_count" -eq 0 ]; then + echo "FAIL: retrieved archive is empty or contains no files" exit 1 fi echo "PASS: gathered data retrieved successfully (${size} bytes)" From d5056a69f861adb662e305ab5d8fd428deca0927 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Fri, 13 Mar 2026 14:13:21 +0100 Subject: [PATCH 18/34] chore: conditionally set image pull policy --- charts/must-gather/templates/data-retriever-pod.yaml | 4 +++- charts/must-gather/templates/job.yaml | 4 +++- charts/must-gather/templates/tests/test.yaml | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/charts/must-gather/templates/data-retriever-pod.yaml b/charts/must-gather/templates/data-retriever-pod.yaml index 2fade6c3..702fac68 100644 --- a/charts/must-gather/templates/data-retriever-pod.yaml +++ b/charts/must-gather/templates/data-retriever-pod.yaml @@ -33,7 +33,9 @@ spec: {{- if .Values.dataRetriever.image.tag }}{{ $_ := set $drImage "tag" .Values.dataRetriever.image.tag }}{{- end }} {{- if .Values.dataRetriever.image.digest }}{{ $_ := set $drImage "digest" .Values.dataRetriever.image.digest }}{{- end }} image: {{ include "rhdh-must-gather.image" (dict "image" $drImage "defaultTag" .Chart.AppVersion) | quote }} # NOSONAR - imagePullPolicy: {{ .Values.dataRetriever.image.pullPolicy | default .Values.image.pullPolicy | default "IfNotPresent" }} + {{- with (.Values.dataRetriever.image.pullPolicy | default .Values.image.pullPolicy) }} + imagePullPolicy: {{ . }} + {{- end }} {{- with .Values.securityContext }} securityContext: {{- toYaml . | nindent 8 }} diff --git a/charts/must-gather/templates/job.yaml b/charts/must-gather/templates/job.yaml index 8a312e30..d7096384 100644 --- a/charts/must-gather/templates/job.yaml +++ b/charts/must-gather/templates/job.yaml @@ -36,7 +36,9 @@ spec: containers: - name: gather image: {{ include "rhdh-must-gather.image" (dict "image" .Values.image "defaultTag" .Chart.AppVersion) | quote }} # NOSONAR - imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- with .Values.image.pullPolicy }} + imagePullPolicy: {{ . }} + {{- end }} {{- with .Values.securityContext }} securityContext: {{- toYaml . | nindent 12 }} diff --git a/charts/must-gather/templates/tests/test.yaml b/charts/must-gather/templates/tests/test.yaml index 70feaf87..6e1393f5 100644 --- a/charts/must-gather/templates/tests/test.yaml +++ b/charts/must-gather/templates/tests/test.yaml @@ -78,7 +78,9 @@ spec: containers: - name: test image: {{ include "rhdh-must-gather.image" (dict "image" .Values.test.image "defaultTag" "latest") | quote }} # NOSONAR - imagePullPolicy: {{ .Values.test.image.pullPolicy }} + {{- with .Values.test.image.pullPolicy }} + imagePullPolicy: {{ . }} + {{- end }} {{- with .Values.securityContext }} securityContext: {{- toYaml . | nindent 8 }} From c43edf6432fb5be646002af25a9ddfdf879d8924 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Fri, 13 Mar 2026 14:24:57 +0100 Subject: [PATCH 19/34] docs: add note about using the Chart on OCP --- charts/must-gather/README.md | 21 ++++++++++++++++++++- charts/must-gather/README.md.gotmpl | 21 ++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/charts/must-gather/README.md b/charts/must-gather/README.md index 2c6efbf0..e5d048c2 100644 --- a/charts/must-gather/README.md +++ b/charts/must-gather/README.md @@ -34,7 +34,26 @@ Running the command again will automatically replace the previous job and start Then follow the instructions that will be printed to retrieve the gathered data. -> **Tip**: List all releases using `helm list` +## Running on OpenShift + +This chart is optimized for running out of the box on Kubernetes. + +For OpenShift, we recommend using the `oc adm must-gather` command, like so: + +```sh +oc adm must-gather --image=quay.io/rhdh-community/rhdh-must-gather +``` + +See the [must-gather tool README](https://github.com/redhat-developer/rhdh-must-gather#for-openshift-clusters) for more details. + +But if you still want to use this chart on OpenShift, you will need to unset the `podSecurityContext.fsGroup` value (or set it to a value aligned with your Security Context Constraints). + +```sh +helm upgrade --install my-rhdh-must-gather rhdh-must-gather \ + --repo https://redhat-developer.github.io/rhdh-chart \ + --version 0.1.0 \ + --set podSecurityContext.fsGroup=null +``` ## Testing a Release diff --git a/charts/must-gather/README.md.gotmpl b/charts/must-gather/README.md.gotmpl index 025ef74d..6877f212 100644 --- a/charts/must-gather/README.md.gotmpl +++ b/charts/must-gather/README.md.gotmpl @@ -27,7 +27,26 @@ Running the command again will automatically replace the previous job and start Then follow the instructions that will be printed to retrieve the gathered data. -> **Tip**: List all releases using `helm list` +## Running on OpenShift + +This chart is optimized for running out of the box on Kubernetes. + +For OpenShift, we recommend using the `oc adm must-gather` command, like so: + +```sh +oc adm must-gather --image=quay.io/rhdh-community/rhdh-must-gather +``` + +See the [must-gather tool README](https://github.com/redhat-developer/rhdh-must-gather#for-openshift-clusters) for more details. + +But if you still want to use this chart on OpenShift, you will need to unset the `podSecurityContext.fsGroup` value (or set it to a value aligned with your Security Context Constraints). + +```sh +helm upgrade --install my-rhdh-must-gather rhdh-must-gather \ + --repo https://redhat-developer.github.io/rhdh-chart \ + --version {{ template "chart.version" . }} \ + --set podSecurityContext.fsGroup=null +``` ## Testing a Release From 99d5c69ad7d776bce82d488fca67aa08797292d6 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Fri, 13 Mar 2026 15:11:49 +0100 Subject: [PATCH 20/34] Simplify the whole gather logic by switching to a single Pod The previous approach used to create issues with the PVC --- charts/must-gather/README.md | 19 ++-- charts/must-gather/README.md.gotmpl | 2 +- .../with-data-retriever-disabled-values.yaml | 3 - .../ci/with-namespace-rbac-values.yaml | 6 + charts/must-gather/templates/NOTES.txt | 31 ++--- charts/must-gather/templates/_helpers.tpl | 23 ---- charts/must-gather/templates/clusterrbac.yaml | 63 +++++++++++ .../templates/data-retriever-pod.yaml | 66 ----------- .../templates/{job.yaml => deployment.yaml} | 76 ++++++++++--- charts/must-gather/templates/pvc.yaml | 15 --- charts/must-gather/templates/rbac.yaml | 25 ++-- charts/must-gather/templates/tests/test.yaml | 28 ++--- charts/must-gather/values.schema.json | 107 ++++++------------ charts/must-gather/values.schema.tmpl.json | 91 ++++----------- charts/must-gather/values.yaml | 36 +++--- 15 files changed, 235 insertions(+), 356 deletions(-) delete mode 100644 charts/must-gather/ci/with-data-retriever-disabled-values.yaml create mode 100644 charts/must-gather/ci/with-namespace-rbac-values.yaml create mode 100644 charts/must-gather/templates/clusterrbac.yaml delete mode 100644 charts/must-gather/templates/data-retriever-pod.yaml rename charts/must-gather/templates/{job.yaml => deployment.yaml} (59%) delete mode 100644 charts/must-gather/templates/pvc.yaml diff --git a/charts/must-gather/README.md b/charts/must-gather/README.md index e5d048c2..d2ab8a82 100644 --- a/charts/must-gather/README.md +++ b/charts/must-gather/README.md @@ -30,7 +30,7 @@ helm upgrade --install my-rhdh-must-gather rhdh-must-gather \ --version 0.1.0 ``` -Running the command again will automatically replace the previous job and start a new gather. +Running the command again will automatically replace the previous pod and start a new gather. Then follow the instructions that will be printed to retrieve the gathered data. @@ -109,10 +109,8 @@ The command removes all the Kubernetes resources associated with the chart and d | Key | Description | Type | Default | |-----|-------------|------|---------| | affinity | Affinity rules for pod scheduling | object | `{}` | -| dataRetriever | This pod allows you to retrieve the gathered data after the job completes | object | `{"enabled":true,"image":{"digest":"","pullPolicy":"","registry":"","repository":"","tag":""},"resources":{"limits":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"},"requests":{"cpu":"50m","ephemeral-storage":"32Mi","memory":"64Mi"}}}` | -| dataRetriever.enabled | Enable the data retriever pod | bool | `true` | -| dataRetriever.image | Image for the data retriever pod (defaults to the main must-gather image) | object | `{"digest":"","pullPolicy":"","registry":"","repository":"","tag":""}` | -| dataRetriever.resources | Resource configuration | object | `{"limits":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"},"requests":{"cpu":"50m","ephemeral-storage":"32Mi","memory":"64Mi"}}` | +| dataHolder | Runs alongside the gather container and stays alive so you can exec in and retrieve the output. | object | `{"resources":{"limits":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"},"requests":{"cpu":"50m","ephemeral-storage":"32Mi","memory":"64Mi"}}}` | +| dataHolder.resources | Resource requests and limits for the data-holder container | object | `{"limits":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"},"requests":{"cpu":"50m","ephemeral-storage":"32Mi","memory":"64Mi"}}` | | fullnameOverride | | string | `""` | | gather | Gather script configuration | object | `{"clusterInfo":false,"cmdTimeout":"30","extraArgs":[],"logLevel":"info","namespaces":[],"since":"","sinceTime":"","withHeapDumps":false,"withSecrets":false,"withoutHelm":false,"withoutIngress":false,"withoutNamespaceInspect":false,"withoutOperator":false,"withoutOrchestrator":false,"withoutPlatform":false,"withoutRoute":false}` | | gather.cmdTimeout | Command timeout for individual kubectl/helm commands (seconds) | string | `"30"` | @@ -127,10 +125,6 @@ The command removes all the Kubernetes resources associated with the chart and d | image.digest | Image digest (e.g., sha256:abc123...). Can be used with or without tag. | string | `""` | | image.tag | Overrides the image tag whose default is the chart appVersion. | string | `"latest"` | | imagePullSecrets | Secrets for pulling images from a private registry | list | `[]` | -| job | Job configuration | object | `{"activeDeadlineSeconds":3600,"backoffLimit":3,"ttlSecondsAfterFinished":600}` | -| job.activeDeadlineSeconds | Job timeout in seconds (default: 1 hour) | int | `3600` | -| job.backoffLimit | Number of retries before marking job as failed | int | `3` | -| job.ttlSecondsAfterFinished | TTL for automatic cleanup after job finishes (seconds) | int | `600` | | nameOverride | Override the chart name | string | `""` | | nodeSelector | Node selector for pod scheduling | object | `{}` | | persistence | Persistent volume configuration for storing gathered data | object | `{"accessMode":"ReadWriteOnce","size":"1Gi","storageClass":""}` | @@ -140,15 +134,16 @@ The command removes all the Kubernetes resources associated with the chart and d | podAnnotations | Pod annotations | object | `{}` | | podLabels | Pod labels | object | `{}` | | podSecurityContext | On OCP, the SCC may override fsGroup with a value from the namespace's allowed range. | object | `{"fsGroup":1001,"runAsNonRoot":true,"seccompProfile":{"type":"RuntimeDefault"}}` | -| rbac | RBAC configuration | object | `{"create":true}` | -| rbac.create | Create ClusterRole and ClusterRoleBinding for cluster-wide read access | bool | `true` | -| resources | Resource requests and limits for the gather job | object | `{"limits":{"cpu":"500m","ephemeral-storage":"128Mi","memory":"512Mi"},"requests":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"}}` | +| rbac | RBAC configuration | object | `{"create":true,"scope":"cluster"}` | +| rbac.create | Create RBAC resources (Role/ClusterRole and bindings) | bool | `true` | +| resources | Resource requests and limits for the gather container | object | `{"limits":{"cpu":"500m","ephemeral-storage":"128Mi","memory":"512Mi"},"requests":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"}}` | | securityContext | Container security context | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]}}` | | serviceAccount | Service account configuration | object | `{"annotations":{},"automount":true,"create":true,"name":""}` | | serviceAccount.annotations | Annotations to add to the service account | object | `{}` | | serviceAccount.automount | Automatically mount a ServiceAccount's API credentials | bool | `true` | | serviceAccount.create | Specifies whether a service account should be created | bool | `true` | | serviceAccount.name | If not set and create is true, a name is generated using the fullname template | string | `""` | +| strategy | Deployment strategy | object | `{"type":"Recreate"}` | | test | Helm test configuration | object | `{"enabled":true,"image":{"digest":"","pullPolicy":"","registry":"docker.io","repository":"bitnami/kubectl","tag":"latest"}}` | | test.enabled | Enable the Helm test | bool | `true` | | test.image | Image for the test pod | object | `{"digest":"","pullPolicy":"","registry":"docker.io","repository":"bitnami/kubectl","tag":"latest"}` | diff --git a/charts/must-gather/README.md.gotmpl b/charts/must-gather/README.md.gotmpl index 6877f212..06d6dae9 100644 --- a/charts/must-gather/README.md.gotmpl +++ b/charts/must-gather/README.md.gotmpl @@ -23,7 +23,7 @@ helm upgrade --install my-rhdh-must-gather rhdh-must-gather \ --version {{ template "chart.version" . }} ``` -Running the command again will automatically replace the previous job and start a new gather. +Running the command again will automatically replace the previous pod and start a new gather. Then follow the instructions that will be printed to retrieve the gathered data. diff --git a/charts/must-gather/ci/with-data-retriever-disabled-values.yaml b/charts/must-gather/ci/with-data-retriever-disabled-values.yaml deleted file mode 100644 index 5d88433a..00000000 --- a/charts/must-gather/ci/with-data-retriever-disabled-values.yaml +++ /dev/null @@ -1,3 +0,0 @@ -# Data retriever disabled — users retrieve data directly from the PVC -dataRetriever: - enabled: false diff --git a/charts/must-gather/ci/with-namespace-rbac-values.yaml b/charts/must-gather/ci/with-namespace-rbac-values.yaml new file mode 100644 index 00000000..6b222d80 --- /dev/null +++ b/charts/must-gather/ci/with-namespace-rbac-values.yaml @@ -0,0 +1,6 @@ +# Namespace-scoped RBAC — creates a Role instead of a ClusterRole +rbac: + scope: namespace +gather: + namespaces: + - "kube-system" diff --git a/charts/must-gather/templates/NOTES.txt b/charts/must-gather/templates/NOTES.txt index 36a7defe..d6245cd9 100644 --- a/charts/must-gather/templates/NOTES.txt +++ b/charts/must-gather/templates/NOTES.txt @@ -1,37 +1,22 @@ ------------------------------------------------------------------- -RHDH Must-Gather job is now running. This might take a few minutes. +RHDH Must-Gather is now running. This might take a few minutes. -To check the job logs: - kubectl -n {{ .Release.Namespace }} logs -f job/{{ include "rhdh-must-gather.jobName" . }} +To check the gather logs: + kubectl -n {{ .Release.Namespace }} logs -l app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=gather -c gather -f -To retrieve the gathered diagnostic data once the job completes: +To retrieve the gathered diagnostic data once the gather completes: -1. Wait for the job to complete: - kubectl -n {{ .Release.Namespace }} wait --for=condition=complete job/{{ include "rhdh-must-gather.jobName" . }} --timeout={{ .Values.job.activeDeadlineSeconds }}s +1. Wait for the pod to be ready (gather init container completed): + kubectl -n {{ .Release.Namespace }} wait --for=condition=ready pod -l app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=gather --timeout=3600s -{{- if .Values.dataRetriever.enabled }} - -2. Wait for the data retriever pod to be ready: - kubectl -n {{ .Release.Namespace }} wait --for=condition=ready pod/{{ include "rhdh-must-gather.dataRetrieverName" . }} --timeout=60s - -3. Download the gathered data: - kubectl -n {{ .Release.Namespace }} exec {{ include "rhdh-must-gather.dataRetrieverName" . }} -- tar czf - -C /data . > rhdh-must-gather-output.tar.gz - -{{- else }} - -2. Find the completed job pod: - kubectl -n {{ .Release.Namespace }} get pods -l app.kubernetes.io/name={{ include "rhdh-must-gather.name" . }},app.kubernetes.io/component=gather - -3. Copy the data from the PVC (you may need to create a temporary pod to access it) - -{{- end }} +2. Download the gathered data from the data-holder container: + kubectl -n {{ .Release.Namespace }} exec deploy/{{ include "rhdh-must-gather.fullname" . }} -c data-holder -- tar czf - -C /must-gather . > rhdh-must-gather-output.tar.gz To clean up the must-gather resources: helm uninstall {{ .Release.Name }} -n {{ .Release.Namespace }} Configuration used: - Log level: {{ .Values.gather.logLevel | lower }} - - Job timeout: {{ .Values.job.activeDeadlineSeconds }}s - Storage size: {{ .Values.persistence.size }} {{- if .Values.gather.namespaces }} - Target namespaces: {{ .Values.gather.namespaces | join ", " }} diff --git a/charts/must-gather/templates/_helpers.tpl b/charts/must-gather/templates/_helpers.tpl index 5885c77b..0a196576 100644 --- a/charts/must-gather/templates/_helpers.tpl +++ b/charts/must-gather/templates/_helpers.tpl @@ -61,22 +61,6 @@ Create the name of the service account to use {{- end }} {{- end }} -{{/* -Unique run ID based on the current timestamp (YYYYMMDDHHMMSS). -Appended to Job and data-retriever names so each helm install/upgrade -creates new resources, working around Kubernetes Job immutability. -*/}} -{{- define "rhdh-must-gather.runId" -}} -{{- now | date "20060102150405" }} -{{- end }} - -{{/* -Job name with unique run ID suffix. -Base name is truncated to 48 chars to stay within the 63-char DNS limit. -*/}} -{{- define "rhdh-must-gather.jobName" -}} -{{- printf "%s-%s" (include "rhdh-must-gather.fullname" . | trunc 48 | trimSuffix "-") (include "rhdh-must-gather.runId" .) }} -{{- end }} {{/* Build a full image reference from registry, repository, and tag. @@ -102,10 +86,3 @@ Usage: {{ include "rhdh-must-gather.image" (dict "image" .Values.image "defaultT {{- end -}} {{- end -}} -{{/* -Data retriever pod name with unique run ID suffix. -Base name is truncated to 32 chars to stay within the 63-char DNS limit. -*/}} -{{- define "rhdh-must-gather.dataRetrieverName" -}} -{{- printf "%s-data-retriever-%s" (include "rhdh-must-gather.fullname" . | trunc 32 | trimSuffix "-") (include "rhdh-must-gather.runId" .) }} -{{- end }} diff --git a/charts/must-gather/templates/clusterrbac.yaml b/charts/must-gather/templates/clusterrbac.yaml new file mode 100644 index 00000000..5031e4b6 --- /dev/null +++ b/charts/must-gather/templates/clusterrbac.yaml @@ -0,0 +1,63 @@ +{{- if and .Values.rbac.create (eq (.Values.rbac.scope | default "cluster") "cluster") -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "rhdh-must-gather.fullname" . }} + labels: + {{- include "rhdh-must-gather.labels" . | nindent 4 }} +rules: + - apiGroups: [""] + resources: ["pods", "pods/log", "services", "endpoints", "configmaps", "secrets", "events", "persistentvolumeclaims", "serviceaccounts", "replicationcontrollers", "resourcequotas", "limitranges"] + verbs: ["get", "list"] + - apiGroups: [""] + resources: ["namespaces", "nodes"] + verbs: ["get", "list"] + - apiGroups: ["apps"] + resources: ["deployments", "replicasets", "statefulsets", "daemonsets"] + verbs: ["get", "list"] + - apiGroups: ["networking.k8s.io"] + resources: ["ingresses", "networkpolicies", "ingressclasses"] + verbs: ["get", "list"] + - apiGroups: ["rbac.authorization.k8s.io"] + resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"] + verbs: ["get", "list"] + - apiGroups: ["storage.k8s.io"] + resources: ["storageclasses", "volumeattachments"] + verbs: ["get", "list"] + - apiGroups: ["apiextensions.k8s.io"] + resources: ["customresourcedefinitions"] + verbs: ["get", "list"] + - apiGroups: ["rhdh.redhat.com"] + resources: ["backstages"] + verbs: ["get", "list"] + - apiGroups: ["operators.coreos.com"] + resources: ["subscriptions", "clusterserviceversions", "installplans", "catalogsources", "operatorgroups"] + verbs: ["get", "list"] + - apiGroups: [""] + resources: ["pods/exec"] # NOSONAR - exec is required to collect data from the running containers + verbs: ["create"] + - apiGroups: ["sonataflow.org"] + resources: ["sonataflowplatforms", "sonataflows", "sonataflowbuilds", "sonataflowclusterplatforms"] + verbs: ["get", "list"] + - apiGroups: ["operator.knative.dev"] + resources: ["knativeservings", "knativeeventings"] + verbs: ["get", "list"] + - apiGroups: ["operator.serverless.openshift.io"] + resources: ["knativekafkas"] + verbs: ["get", "list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "rhdh-must-gather.fullname" . }} + labels: + {{- include "rhdh-must-gather.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "rhdh-must-gather.fullname" . }} +subjects: + - kind: ServiceAccount + name: {{ include "rhdh-must-gather.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/must-gather/templates/data-retriever-pod.yaml b/charts/must-gather/templates/data-retriever-pod.yaml deleted file mode 100644 index 702fac68..00000000 --- a/charts/must-gather/templates/data-retriever-pod.yaml +++ /dev/null @@ -1,66 +0,0 @@ -{{- if .Values.dataRetriever.enabled -}} -apiVersion: v1 -kind: Pod -metadata: - name: {{ include "rhdh-must-gather.dataRetrieverName" . }} - labels: - {{- include "rhdh-must-gather.labels" . | nindent 4 }} - app.kubernetes.io/component: data-retriever -spec: - automountServiceAccountToken: false - restartPolicy: Never - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 4 }} - {{- end }} - {{- with .Values.podSecurityContext }} - securityContext: - {{- toYaml . | nindent 4 }} - {{- end }} - affinity: - podAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - labelSelector: - matchLabels: - {{- include "rhdh-must-gather.selectorLabels" . | nindent 14 }} - app.kubernetes.io/component: gather - topologyKey: kubernetes.io/hostname - containers: - - name: data-retriever - {{- $drImage := deepCopy .Values.image }} - {{- if .Values.dataRetriever.image.registry }}{{ $_ := set $drImage "registry" .Values.dataRetriever.image.registry }}{{- end }} - {{- if .Values.dataRetriever.image.repository }}{{ $_ := set $drImage "repository" .Values.dataRetriever.image.repository }}{{- end }} - {{- if .Values.dataRetriever.image.tag }}{{ $_ := set $drImage "tag" .Values.dataRetriever.image.tag }}{{- end }} - {{- if .Values.dataRetriever.image.digest }}{{ $_ := set $drImage "digest" .Values.dataRetriever.image.digest }}{{- end }} - image: {{ include "rhdh-must-gather.image" (dict "image" $drImage "defaultTag" .Chart.AppVersion) | quote }} # NOSONAR - {{- with (.Values.dataRetriever.image.pullPolicy | default .Values.image.pullPolicy) }} - imagePullPolicy: {{ . }} - {{- end }} - {{- with .Values.securityContext }} - securityContext: - {{- toYaml . | nindent 8 }} - {{- end }} - command: - - sleep - - infinity - volumeMounts: - - name: must-gather-output - mountPath: /data - readOnly: true - {{- with .Values.dataRetriever.resources }} - resources: - {{- toYaml . | nindent 8 }} - {{- end }} - volumes: - - name: must-gather-output - persistentVolumeClaim: - claimName: {{ include "rhdh-must-gather.fullname" . }}-pvc - {{- with .Values.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 4 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: - {{- toYaml . | nindent 4 }} - {{- end }} -{{- end }} diff --git a/charts/must-gather/templates/job.yaml b/charts/must-gather/templates/deployment.yaml similarity index 59% rename from charts/must-gather/templates/job.yaml rename to charts/must-gather/templates/deployment.yaml index d7096384..73ef1373 100644 --- a/charts/must-gather/templates/job.yaml +++ b/charts/must-gather/templates/deployment.yaml @@ -1,14 +1,20 @@ -apiVersion: batch/v1 -kind: Job +apiVersion: apps/v1 +kind: Deployment metadata: - name: {{ include "rhdh-must-gather.jobName" . }} + name: {{ include "rhdh-must-gather.fullname" . }} labels: {{- include "rhdh-must-gather.labels" . | nindent 4 }} app.kubernetes.io/component: gather spec: - activeDeadlineSeconds: {{ .Values.job.activeDeadlineSeconds }} - backoffLimit: {{ .Values.job.backoffLimit }} - ttlSecondsAfterFinished: {{ .Values.job.ttlSecondsAfterFinished }} + replicas: 1 + {{- with .Values.strategy }} + strategy: + {{- toYaml . | nindent 4 }} + {{- end }} + selector: + matchLabels: + {{- include "rhdh-must-gather.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: gather template: metadata: labels: @@ -17,14 +23,14 @@ spec: {{- with .Values.podLabels }} {{- toYaml . | nindent 8 }} {{- end }} - {{- with .Values.podAnnotations }} annotations: + rhdh-must-gather/run-timestamp: {{ now | date "2006-01-02T15:04:05Z" | quote }} + {{- with .Values.podAnnotations }} {{- toYaml . | nindent 8 }} - {{- end }} + {{- end }} spec: serviceAccountName: {{ include "rhdh-must-gather.serviceAccountName" . }} # NOSONAR - SA is bound via ClusterRole/ClusterRoleBinding in rbac.yaml automountServiceAccountToken: true - restartPolicy: Never {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} @@ -33,7 +39,7 @@ spec: securityContext: {{- toYaml . | nindent 8 }} {{- end }} - containers: + initContainers: - name: gather image: {{ include "rhdh-must-gather.image" (dict "image" .Values.image "defaultTag" .Chart.AppVersion) | quote }} # NOSONAR {{- with .Values.image.pullPolicy }} @@ -62,7 +68,12 @@ spec: - name: MUST_GATHER_SINCE_TIME value: {{ .Values.gather.sinceTime | quote }} {{- end }} - {{- if or .Values.gather.withSecrets .Values.gather.withHeapDumps .Values.gather.clusterInfo .Values.gather.withoutOperator .Values.gather.withoutOrchestrator .Values.gather.withoutHelm .Values.gather.withoutPlatform .Values.gather.withoutRoute .Values.gather.withoutIngress .Values.gather.withoutNamespaceInspect .Values.gather.namespaces .Values.gather.extraArgs }} + {{- $nsScope := ne (.Values.rbac.scope | default "cluster") "cluster" }} + {{- $effectiveNamespaces := .Values.gather.namespaces | default list }} + {{- if $nsScope }} + {{- $effectiveNamespaces = list .Release.Namespace }} + {{- end }} + {{- if or $nsScope .Values.gather.withSecrets .Values.gather.withHeapDumps .Values.gather.clusterInfo .Values.gather.withoutOperator .Values.gather.withoutOrchestrator .Values.gather.withoutHelm .Values.gather.withoutPlatform .Values.gather.withoutRoute .Values.gather.withoutIngress .Values.gather.withoutNamespaceInspect $effectiveNamespaces .Values.gather.extraArgs }} args: {{- if .Values.gather.withSecrets }} - "--with-secrets" @@ -82,7 +93,7 @@ spec: {{- if .Values.gather.withoutHelm }} - "--without-helm" {{- end }} - {{- if .Values.gather.withoutPlatform }} + {{- if or .Values.gather.withoutPlatform $nsScope }} - "--without-platform" {{- end }} {{- if .Values.gather.withoutRoute }} @@ -94,9 +105,9 @@ spec: {{- if .Values.gather.withoutNamespaceInspect }} - "--without-namespace-inspect" {{- end }} - {{- if .Values.gather.namespaces }} + {{- if $effectiveNamespaces }} - "--namespaces" - - {{ .Values.gather.namespaces | join "," | quote }} + - {{ $effectiveNamespaces | join "," | quote }} {{- end }} {{- with .Values.gather.extraArgs }} {{- toYaml . | nindent 12 }} @@ -109,10 +120,43 @@ spec: resources: {{- toYaml . | nindent 12 }} {{- end }} + containers: + - name: data-holder + image: {{ include "rhdh-must-gather.image" (dict "image" .Values.image "defaultTag" .Chart.AppVersion) | quote }} # NOSONAR + {{- with .Values.image.pullPolicy }} + imagePullPolicy: {{ . }} + {{- end }} + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + command: + - sleep + - infinity + volumeMounts: + - name: output + mountPath: /must-gather + readOnly: true + {{- with .Values.dataHolder.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} volumes: - name: output - persistentVolumeClaim: - claimName: {{ include "rhdh-must-gather.fullname" . }}-pvc + ephemeral: + volumeClaimTemplate: + metadata: + labels: + {{- include "rhdh-must-gather.labels" . | nindent 18 }} + spec: + accessModes: + - {{ .Values.persistence.accessMode }} + {{- if .Values.persistence.storageClass }} + storageClassName: {{ .Values.persistence.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/charts/must-gather/templates/pvc.yaml b/charts/must-gather/templates/pvc.yaml deleted file mode 100644 index 66bf6449..00000000 --- a/charts/must-gather/templates/pvc.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: {{ include "rhdh-must-gather.fullname" . }}-pvc - labels: - {{- include "rhdh-must-gather.labels" . | nindent 4 }} -spec: - accessModes: - - {{ .Values.persistence.accessMode }} - {{- if .Values.persistence.storageClass }} - storageClassName: {{ .Values.persistence.storageClass | quote }} - {{- end }} - resources: - requests: - storage: {{ .Values.persistence.size | quote }} diff --git a/charts/must-gather/templates/rbac.yaml b/charts/must-gather/templates/rbac.yaml index 24777bf0..e358a700 100644 --- a/charts/must-gather/templates/rbac.yaml +++ b/charts/must-gather/templates/rbac.yaml @@ -1,28 +1,23 @@ -{{- if .Values.rbac.create -}} +{{- if and .Values.rbac.create (ne (.Values.rbac.scope | default "cluster") "cluster") -}} apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole +kind: Role metadata: name: {{ include "rhdh-must-gather.fullname" . }} + namespace: {{ .Release.Namespace }} labels: {{- include "rhdh-must-gather.labels" . | nindent 4 }} rules: - apiGroups: [""] - resources: ["pods", "pods/log", "services", "endpoints", "configmaps", "secrets", "events", "persistentvolumeclaims", "serviceaccounts", "namespaces", "nodes", "replicationcontrollers", "resourcequotas", "limitranges"] + resources: ["pods", "pods/log", "services", "endpoints", "configmaps", "secrets", "events", "persistentvolumeclaims", "serviceaccounts", "replicationcontrollers", "resourcequotas", "limitranges"] verbs: ["get", "list"] - apiGroups: ["apps"] resources: ["deployments", "replicasets", "statefulsets", "daemonsets"] verbs: ["get", "list"] - apiGroups: ["networking.k8s.io"] - resources: ["ingresses", "networkpolicies", "ingressclasses"] + resources: ["ingresses", "networkpolicies"] verbs: ["get", "list"] - apiGroups: ["rbac.authorization.k8s.io"] - resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"] - verbs: ["get", "list"] - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses", "volumeattachments"] - verbs: ["get", "list"] - - apiGroups: ["apiextensions.k8s.io"] - resources: ["customresourcedefinitions"] + resources: ["roles", "rolebindings"] verbs: ["get", "list"] - apiGroups: ["rhdh.redhat.com"] resources: ["backstages"] @@ -33,9 +28,8 @@ rules: - apiGroups: [""] resources: ["pods/exec"] # NOSONAR - exec is required to collect data from the running containers verbs: ["create"] - # Orchestrator-related permissions - apiGroups: ["sonataflow.org"] - resources: ["sonataflowplatforms", "sonataflows", "sonataflowclusterplatforms", "sonataflowbuilds"] + resources: ["sonataflowplatforms", "sonataflows", "sonataflowbuilds"] verbs: ["get", "list"] - apiGroups: ["operator.knative.dev"] resources: ["knativeservings", "knativeeventings"] @@ -45,14 +39,15 @@ rules: verbs: ["get", "list"] --- apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding +kind: RoleBinding metadata: name: {{ include "rhdh-must-gather.fullname" . }} + namespace: {{ .Release.Namespace }} labels: {{- include "rhdh-must-gather.labels" . | nindent 4 }} roleRef: apiGroup: rbac.authorization.k8s.io - kind: ClusterRole + kind: Role name: {{ include "rhdh-must-gather.fullname" . }} subjects: - kind: ServiceAccount diff --git a/charts/must-gather/templates/tests/test.yaml b/charts/must-gather/templates/tests/test.yaml index 6e1393f5..bd82aca6 100644 --- a/charts/must-gather/templates/tests/test.yaml +++ b/charts/must-gather/templates/tests/test.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.test.enabled .Values.dataRetriever.enabled -}} +{{- if .Values.test.enabled -}} apiVersion: v1 kind: ServiceAccount metadata: @@ -24,14 +24,11 @@ metadata: helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded helm.sh/hook-weight: "-1" rules: - - apiGroups: ["batch"] - resources: ["jobs"] - verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["pods"] verbs: ["get", "watch", "list"] - apiGroups: [""] - resources: ["pods/exec"] # NOSONAR - exec is required to retrieve must-gather output from the data-retriever pod during helm test + resources: ["pods/exec"] # NOSONAR - exec is required to retrieve must-gather output from the data-holder container during helm test verbs: ["create"] --- apiVersion: rbac.authorization.k8s.io/v1 @@ -90,24 +87,19 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - - name: JOB_NAME - value: {{ include "rhdh-must-gather.jobName" . }} - - name: DATA_RETRIEVER_POD - value: {{ include "rhdh-must-gather.dataRetrieverName" . }} - - name: JOB_TIMEOUT - value: "{{ .Values.job.activeDeadlineSeconds }}" + - name: DEPLOY_NAME + value: {{ include "rhdh-must-gather.fullname" . }} + - name: POD_SELECTOR + value: "app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=gather" command: ["/bin/sh", "-c"] args: - | set -e - echo "Step 1: Waiting for the gather job to complete..." - kubectl -n "$NAMESPACE" wait --for=condition=complete "job/$JOB_NAME" --timeout="${JOB_TIMEOUT}s" + echo "Step 1: Waiting for the gather pod to be ready (init container completed)..." + kubectl -n "$NAMESPACE" wait --for=condition=ready pod -l "$POD_SELECTOR" --timeout=3600s - echo "Step 2: Waiting for the data retriever pod to be ready..." - kubectl -n "$NAMESPACE" wait --for=condition=ready "pod/$DATA_RETRIEVER_POD" --timeout=60s - - echo "Step 3: Retrieving gathered data from the data retriever pod..." - kubectl -n "$NAMESPACE" exec "$DATA_RETRIEVER_POD" -- tar czf - -C /data . > /tmp/rhdh-must-gather-output.tar.gz + echo "Step 2: Retrieving gathered data from the data-holder container..." + kubectl -n "$NAMESPACE" exec "deploy/$DEPLOY_NAME" -c data-holder -- tar czf - -C /must-gather . > /tmp/rhdh-must-gather-output.tar.gz size=$(stat -c%s /tmp/rhdh-must-gather-output.tar.gz 2>/dev/null || stat -f%z /tmp/rhdh-must-gather-output.tar.gz 2>/dev/null) file_count=$(tar -tzf /tmp/rhdh-must-gather-output.tar.gz | wc -l) diff --git a/charts/must-gather/values.schema.json b/charts/must-gather/values.schema.json index e55de6e6..d77e581b 100644 --- a/charts/must-gather/values.schema.json +++ b/charts/must-gather/values.schema.json @@ -755,52 +755,9 @@ "title": "Affinity for pod assignment", "type": "object" }, - "dataRetriever": { + "dataHolder": { "additionalProperties": false, "properties": { - "enabled": { - "default": true, - "title": "Enable the data retriever pod.", - "type": "boolean" - }, - "image": { - "additionalProperties": false, - "properties": { - "digest": { - "default": "", - "title": "Image digest. Empty defaults to the main must-gather image digest.", - "type": "string" - }, - "pullPolicy": { - "default": "", - "enum": [ - "Always", - "IfNotPresent", - "Never", - "" - ], - "title": "Image pull policy.", - "type": "string" - }, - "registry": { - "default": "", - "title": "Image registry. Empty defaults to the main must-gather image registry.", - "type": "string" - }, - "repository": { - "default": "", - "title": "Image repository. Empty defaults to the main must-gather image.", - "type": "string" - }, - "tag": { - "default": "", - "title": "Image tag. Empty defaults to the main must-gather image tag.", - "type": "string" - } - }, - "title": "Image for the data retriever pod. Defaults to the main must-gather image if not set.", - "type": "object" - }, "resources": { "additionalProperties": false, "properties": { @@ -849,11 +806,11 @@ "type": "object" } }, - "title": "Resource configuration for the data retriever pod.", + "title": "Resource requests and limits for the data holder container.", "type": "object" } }, - "title": "Data retriever pod configuration. This pod allows you to retrieve the gathered data after the job completes.", + "title": "Data holder container configuration. Stays alive after the gather init container completes so you can exec in and retrieve the output.", "type": "object" }, "fullnameOverride": { @@ -1014,31 +971,6 @@ "title": "Secrets for pulling images from a private registry.", "type": "array" }, - "job": { - "additionalProperties": false, - "properties": { - "activeDeadlineSeconds": { - "default": 3600, - "title": "Job timeout in seconds.", - "type": "integer" - }, - "backoffLimit": { - "default": 3, - "title": "Number of retries before marking job as failed.", - "type": "integer" - }, - "ttlSecondsAfterFinished": { - "default": 600, - "title": "TTL for automatic cleanup after job finishes (seconds). Set to a positive value to enable automatic cleanup.", - "type": [ - "integer", - "string" - ] - } - }, - "title": "Job configuration.", - "type": "object" - }, "nameOverride": { "default": "", "title": "Override the chart name.", @@ -1245,8 +1177,18 @@ "properties": { "create": { "default": true, - "title": "Create ClusterRole and ClusterRoleBinding for cluster-wide read access.", + "title": "Create RBAC resources (Role/ClusterRole and bindings).", "type": "boolean" + }, + "scope": { + "default": "cluster", + "description": "'cluster' creates a ClusterRole with cluster-wide read access. 'namespace' creates a namespace-scoped Role; the release namespace is always included in --namespaces automatically, alongside any extra namespaces in gather.namespaces.", + "enum": [ + "cluster", + "namespace" + ], + "title": "Scope of the RBAC role.", + "type": "string" } }, "title": "RBAC configuration.", @@ -1300,7 +1242,7 @@ "type": "object" } }, - "title": "Resource requests and limits for the gather job.", + "title": "Resource requests and limits for the gather container.", "type": "object" }, "securityContext": { @@ -1480,6 +1422,25 @@ "title": "Service account configuration.", "type": "object" }, + "strategy": { + "default": { + "type": "Recreate" + }, + "description": "Ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy", + "properties": { + "type": { + "default": "Recreate", + "enum": [ + "Recreate", + "RollingUpdate" + ], + "title": "Strategy type.", + "type": "string" + } + }, + "title": "Deployment strategy.", + "type": "object" + }, "test": { "additionalProperties": false, "properties": { diff --git a/charts/must-gather/values.schema.tmpl.json b/charts/must-gather/values.schema.tmpl.json index b05fa4e2..444a1645 100644 --- a/charts/must-gather/values.schema.tmpl.json +++ b/charts/must-gather/values.schema.tmpl.json @@ -98,35 +98,33 @@ "additionalProperties": false, "properties": { "create": { - "title": "Create ClusterRole and ClusterRoleBinding for cluster-wide read access.", + "title": "Create RBAC resources (Role/ClusterRole and bindings).", "type": "boolean", "default": true + }, + "scope": { + "title": "Scope of the RBAC role.", + "description": "'cluster' creates a ClusterRole with cluster-wide read access. 'namespace' creates a namespace-scoped Role; the release namespace is always included in --namespaces automatically, alongside any extra namespaces in gather.namespaces.", + "type": "string", + "default": "cluster", + "enum": ["cluster", "namespace"] } } }, - "job": { - "title": "Job configuration.", + "strategy": { + "title": "Deployment strategy.", + "description": "Ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy", "type": "object", - "additionalProperties": false, "properties": { - "activeDeadlineSeconds": { - "title": "Job timeout in seconds.", - "type": "integer", - "default": 3600 - }, - "backoffLimit": { - "title": "Number of retries before marking job as failed.", - "type": "integer", - "default": 3 - }, - "ttlSecondsAfterFinished": { - "title": "TTL for automatic cleanup after job finishes (seconds). Set to a positive value to enable automatic cleanup.", - "type": [ - "integer", - "string" - ], - "default": "" + "type": { + "title": "Strategy type.", + "type": "string", + "default": "Recreate", + "enum": ["Recreate", "RollingUpdate"] } + }, + "default": { + "type": "Recreate" } }, "gather": { @@ -232,7 +230,7 @@ } }, "resources": { - "title": "Resource requests and limits for the gather job.", + "title": "Resource requests and limits for the gather container.", "type": "object", "additionalProperties": false, "properties": { @@ -309,56 +307,13 @@ } } }, - "dataRetriever": { - "title": "Data retriever pod configuration. This pod allows you to retrieve the gathered data after the job completes.", + "dataHolder": { + "title": "Data holder container configuration. Stays alive after the gather init container completes so you can exec in and retrieve the output.", "type": "object", "additionalProperties": false, "properties": { - "enabled": { - "title": "Enable the data retriever pod.", - "type": "boolean", - "default": true - }, - "image": { - "title": "Image for the data retriever pod. Defaults to the main must-gather image if not set.", - "type": "object", - "additionalProperties": false, - "properties": { - "registry": { - "title": "Image registry. Empty defaults to the main must-gather image registry.", - "type": "string", - "default": "" - }, - "repository": { - "title": "Image repository. Empty defaults to the main must-gather image.", - "type": "string", - "default": "" - }, - "tag": { - "title": "Image tag. Empty defaults to the main must-gather image tag.", - "type": "string", - "default": "" - }, - "digest": { - "title": "Image digest. Empty defaults to the main must-gather image digest.", - "type": "string", - "default": "" - }, - "pullPolicy": { - "title": "Image pull policy.", - "type": "string", - "default": "", - "enum": [ - "Always", - "IfNotPresent", - "Never", - "" - ] - } - } - }, "resources": { - "title": "Resource configuration for the data retriever pod.", + "title": "Resource requests and limits for the data holder container.", "type": "object", "additionalProperties": false, "properties": { diff --git a/charts/must-gather/values.yaml b/charts/must-gather/values.yaml index 6fbe40a7..ad0a3d8f 100644 --- a/charts/must-gather/values.yaml +++ b/charts/must-gather/values.yaml @@ -33,17 +33,16 @@ serviceAccount: # -- RBAC configuration rbac: - # -- Create ClusterRole and ClusterRoleBinding for cluster-wide read access + # -- Create RBAC resources (Role/ClusterRole and bindings) create: true + # -- Scope of the RBAC role: "cluster" creates a ClusterRole with cluster-wide read access; + # -- "namespace" creates a namespace-scoped Role (the release namespace is always included + # -- in --namespaces automatically, alongside any extra namespaces in gather.namespaces) + scope: cluster -# -- Job configuration -job: - # -- Job timeout in seconds (default: 1 hour) - activeDeadlineSeconds: 3600 - # -- Number of retries before marking job as failed - backoffLimit: 3 - # -- TTL for automatic cleanup after job finishes (seconds) - ttlSecondsAfterFinished: 600 +# -- Deployment strategy +strategy: + type: Recreate # -- Gather script configuration gather: @@ -77,7 +76,7 @@ gather: # -- Additional custom arguments to pass to the gather script extraArgs: [] -# -- Resource requests and limits for the gather job +# -- Resource requests and limits for the gather container resources: requests: memory: "128Mi" @@ -97,19 +96,10 @@ persistence: # -- Access mode accessMode: ReadWriteOnce -# -- Data retriever pod configuration -# -- This pod allows you to retrieve the gathered data after the job completes -dataRetriever: - # -- Enable the data retriever pod - enabled: true - # -- Image for the data retriever pod (defaults to the main must-gather image) - image: - registry: "" - repository: "" - tag: "" - digest: "" - pullPolicy: "" - # -- Resource configuration +# -- Data holder container configuration +# -- Runs alongside the gather container and stays alive so you can exec in and retrieve the output. +dataHolder: + # -- Resource requests and limits for the data-holder container resources: requests: memory: "64Mi" From 55f1db64436bd0b9ec3c547a872d8c5c0bb1158d Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Fri, 13 Mar 2026 16:27:02 +0100 Subject: [PATCH 21/34] fix: add missing permission to the ServiceAccount of the test pod --- charts/must-gather/templates/tests/test.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/charts/must-gather/templates/tests/test.yaml b/charts/must-gather/templates/tests/test.yaml index bd82aca6..8c31a2b1 100644 --- a/charts/must-gather/templates/tests/test.yaml +++ b/charts/must-gather/templates/tests/test.yaml @@ -30,6 +30,9 @@ rules: - apiGroups: [""] resources: ["pods/exec"] # NOSONAR - exec is required to retrieve must-gather output from the data-holder container during helm test verbs: ["create"] + - apiGroups: ["apps"] + resources: ["deployments"] + verbs: ["get"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding From f2495aeff027a579bc9e01c9c64c032d04b48640 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Fri, 13 Mar 2026 18:34:07 +0100 Subject: [PATCH 22/34] fix: avoid exposing SA token to containers that don't need API access automountServiceAccountToken: true mounts credentials into every container in the pod, including the data-holder which only runs "sleep infinity". Replace it with a projected volume carrying a bound (time-limited) service account token, mounted exclusively into the containers that actually call the Kubernetes API. Assisted-by: Cursor Made-with: Cursor --- charts/must-gather/README.md | 7 +++--- charts/must-gather/templates/_helpers.tpl | 4 --- charts/must-gather/templates/deployment.yaml | 22 +++++++++++++++- .../must-gather/templates/serviceaccount.yaml | 4 +-- charts/must-gather/templates/tests/test.yaml | 25 ++++++++++++++++++- charts/must-gather/values.schema.json | 18 ++++++------- charts/must-gather/values.schema.tmpl.json | 16 +++++------- charts/must-gather/values.yaml | 11 ++++---- 8 files changed, 67 insertions(+), 40 deletions(-) diff --git a/charts/must-gather/README.md b/charts/must-gather/README.md index d2ab8a82..92540f97 100644 --- a/charts/must-gather/README.md +++ b/charts/must-gather/README.md @@ -138,11 +138,10 @@ The command removes all the Kubernetes resources associated with the chart and d | rbac.create | Create RBAC resources (Role/ClusterRole and bindings) | bool | `true` | | resources | Resource requests and limits for the gather container | object | `{"limits":{"cpu":"500m","ephemeral-storage":"128Mi","memory":"512Mi"},"requests":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"}}` | | securityContext | Container security context | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]}}` | -| serviceAccount | Service account configuration | object | `{"annotations":{},"automount":true,"create":true,"name":""}` | +| serviceAccount | Service account configuration | object | `{"annotations":{},"name":"","tokenExpirationSeconds":3600}` | | serviceAccount.annotations | Annotations to add to the service account | object | `{}` | -| serviceAccount.automount | Automatically mount a ServiceAccount's API credentials | bool | `true` | -| serviceAccount.create | Specifies whether a service account should be created | bool | `true` | -| serviceAccount.name | If not set and create is true, a name is generated using the fullname template | string | `""` | +| serviceAccount.name | If not set, a name is generated using the fullname template. | string | `""` | +| serviceAccount.tokenExpirationSeconds | into the gather init container (minimum 600). | int | `3600` | | strategy | Deployment strategy | object | `{"type":"Recreate"}` | | test | Helm test configuration | object | `{"enabled":true,"image":{"digest":"","pullPolicy":"","registry":"docker.io","repository":"bitnami/kubectl","tag":"latest"}}` | | test.enabled | Enable the Helm test | bool | `true` | diff --git a/charts/must-gather/templates/_helpers.tpl b/charts/must-gather/templates/_helpers.tpl index 0a196576..14861300 100644 --- a/charts/must-gather/templates/_helpers.tpl +++ b/charts/must-gather/templates/_helpers.tpl @@ -54,11 +54,7 @@ app.kubernetes.io/instance: {{ .Release.Name }} Create the name of the service account to use */}} {{- define "rhdh-must-gather.serviceAccountName" -}} -{{- if .Values.serviceAccount.create }} {{- default (include "rhdh-must-gather.fullname" .) .Values.serviceAccount.name }} -{{- else }} -{{- default "default" .Values.serviceAccount.name }} -{{- end }} {{- end }} diff --git a/charts/must-gather/templates/deployment.yaml b/charts/must-gather/templates/deployment.yaml index 73ef1373..c1d5fff4 100644 --- a/charts/must-gather/templates/deployment.yaml +++ b/charts/must-gather/templates/deployment.yaml @@ -30,7 +30,7 @@ spec: {{- end }} spec: serviceAccountName: {{ include "rhdh-must-gather.serviceAccountName" . }} # NOSONAR - SA is bound via ClusterRole/ClusterRoleBinding in rbac.yaml - automountServiceAccountToken: true + automountServiceAccountToken: false {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} @@ -116,6 +116,9 @@ spec: volumeMounts: - name: output mountPath: /must-gather + - name: kube-api-access + mountPath: /var/run/secrets/kubernetes.io/serviceaccount + readOnly: true {{- with .Values.resources }} resources: {{- toYaml . | nindent 12 }} @@ -142,6 +145,23 @@ spec: {{- toYaml . | nindent 12 }} {{- end }} volumes: + - name: kube-api-access + projected: + defaultMode: 0444 + sources: + - serviceAccountToken: + expirationSeconds: {{ .Values.serviceAccount.tokenExpirationSeconds | default 3600 }} + path: token + - configMap: + name: kube-root-ca.crt + items: + - key: ca.crt + path: ca.crt + - downwardAPI: + items: + - path: namespace + fieldRef: + fieldPath: metadata.namespace - name: output ephemeral: volumeClaimTemplate: diff --git a/charts/must-gather/templates/serviceaccount.yaml b/charts/must-gather/templates/serviceaccount.yaml index 81c615af..20f91245 100644 --- a/charts/must-gather/templates/serviceaccount.yaml +++ b/charts/must-gather/templates/serviceaccount.yaml @@ -1,4 +1,3 @@ -{{- if .Values.serviceAccount.create -}} apiVersion: v1 kind: ServiceAccount metadata: @@ -9,5 +8,4 @@ metadata: annotations: {{- toYaml . | nindent 4 }} {{- end }} -automountServiceAccountToken: {{ .Values.serviceAccount.automount }} -{{- end }} +automountServiceAccountToken: false diff --git a/charts/must-gather/templates/tests/test.yaml b/charts/must-gather/templates/tests/test.yaml index 8c31a2b1..bd0b025a 100644 --- a/charts/must-gather/templates/tests/test.yaml +++ b/charts/must-gather/templates/tests/test.yaml @@ -10,7 +10,7 @@ metadata: helm.sh/hook: test helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded helm.sh/hook-weight: "-2" -automountServiceAccountToken: true +automountServiceAccountToken: false --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role @@ -66,6 +66,7 @@ metadata: helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded spec: serviceAccountName: {{ include "rhdh-must-gather.fullname" . }}-test + automountServiceAccountToken: false restartPolicy: Never {{- with .Values.imagePullSecrets }} imagePullSecrets: @@ -85,6 +86,10 @@ spec: securityContext: {{- toYaml . | nindent 8 }} {{- end }} + volumeMounts: + - name: kube-api-access + mountPath: /var/run/secrets/kubernetes.io/serviceaccount + readOnly: true env: - name: NAMESPACE valueFrom: @@ -120,6 +125,24 @@ spec: memory: "128Mi" cpu: "100m" ephemeral-storage: "64Mi" + volumes: + - name: kube-api-access + projected: + defaultMode: 0444 + sources: + - serviceAccountToken: + expirationSeconds: {{ .Values.serviceAccount.tokenExpirationSeconds | default 3600 }} + path: token + - configMap: + name: kube-root-ca.crt + items: + - key: ca.crt + path: ca.crt + - downwardAPI: + items: + - path: namespace + fieldRef: + fieldPath: metadata.namespace {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 4 }} diff --git a/charts/must-gather/values.schema.json b/charts/must-gather/values.schema.json index d77e581b..6c5624d0 100644 --- a/charts/must-gather/values.schema.json +++ b/charts/must-gather/values.schema.json @@ -1403,20 +1403,16 @@ "title": "Annotations to add to the service account.", "type": "object" }, - "automount": { - "default": true, - "title": "Automatically mount a ServiceAccount's API credentials.", - "type": "boolean" - }, - "create": { - "default": true, - "title": "Specifies whether a service account should be created.", - "type": "boolean" - }, "name": { "default": "", - "title": "The name of the service account to use. If not set and create is true, a name is generated using the fullname template.", + "title": "The name of the service account to use. If not set, a name is generated using the fullname template.", "type": "string" + }, + "tokenExpirationSeconds": { + "default": 3600, + "minimum": 600, + "title": "Expiration (in seconds) for the bound service account token projected into the gather init container.", + "type": "integer" } }, "title": "Service account configuration.", diff --git a/charts/must-gather/values.schema.tmpl.json b/charts/must-gather/values.schema.tmpl.json index 444a1645..4110493c 100644 --- a/charts/must-gather/values.schema.tmpl.json +++ b/charts/must-gather/values.schema.tmpl.json @@ -70,15 +70,11 @@ "type": "object", "additionalProperties": false, "properties": { - "create": { - "title": "Specifies whether a service account should be created.", - "type": "boolean", - "default": true - }, - "automount": { - "title": "Automatically mount a ServiceAccount's API credentials.", - "type": "boolean", - "default": true + "tokenExpirationSeconds": { + "title": "Expiration (in seconds) for the bound service account token projected into the gather init container.", + "type": "integer", + "default": 3600, + "minimum": 600 }, "annotations": { "title": "Annotations to add to the service account.", @@ -86,7 +82,7 @@ "default": {} }, "name": { - "title": "The name of the service account to use. If not set and create is true, a name is generated using the fullname template.", + "title": "The name of the service account to use. If not set, a name is generated using the fullname template.", "type": "string", "default": "" } diff --git a/charts/must-gather/values.yaml b/charts/must-gather/values.yaml index ad0a3d8f..c2437ab0 100644 --- a/charts/must-gather/values.yaml +++ b/charts/must-gather/values.yaml @@ -21,14 +21,13 @@ fullnameOverride: "" # -- Service account configuration serviceAccount: - # -- Specifies whether a service account should be created - create: true - # -- Automatically mount a ServiceAccount's API credentials - automount: true + # -- Expiration (in seconds) for the bound service account token projected + # -- into the gather init container (minimum 600). + tokenExpirationSeconds: 3600 # -- Annotations to add to the service account annotations: {} - # -- The name of the service account to use - # -- If not set and create is true, a name is generated using the fullname template + # -- The name of the service account to use. + # -- If not set, a name is generated using the fullname template. name: "" # -- RBAC configuration From 9ce4b1a7fa1775fb1c6ce1b3a36739282e224efc Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Fri, 13 Mar 2026 18:37:53 +0100 Subject: [PATCH 23/34] add missing permission to retrieve cluster version on OCP --- charts/must-gather/templates/clusterrbac.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/charts/must-gather/templates/clusterrbac.yaml b/charts/must-gather/templates/clusterrbac.yaml index 5031e4b6..a0e0966a 100644 --- a/charts/must-gather/templates/clusterrbac.yaml +++ b/charts/must-gather/templates/clusterrbac.yaml @@ -42,6 +42,9 @@ rules: - apiGroups: ["operator.knative.dev"] resources: ["knativeservings", "knativeeventings"] verbs: ["get", "list"] + - apiGroups: ["config.openshift.io"] + resources: ["clusterversions"] + verbs: ["get", "list"] - apiGroups: ["operator.serverless.openshift.io"] resources: ["knativekafkas"] verbs: ["get", "list"] From 213e5a4fb23e488cf3d926fe20f04a21df58fedb Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Fri, 13 Mar 2026 18:43:01 +0100 Subject: [PATCH 24/34] remove fsGroup as it works correctly on both K8s and OCP --- charts/must-gather/README.md | 15 +++------------ charts/must-gather/README.md.gotmpl | 13 ++----------- charts/must-gather/values.schema.tmpl.json | 1 - charts/must-gather/values.yaml | 2 -- 4 files changed, 5 insertions(+), 26 deletions(-) diff --git a/charts/must-gather/README.md b/charts/must-gather/README.md index 92540f97..4564352b 100644 --- a/charts/must-gather/README.md +++ b/charts/must-gather/README.md @@ -36,9 +36,9 @@ Then follow the instructions that will be printed to retrieve the gathered data. ## Running on OpenShift -This chart is optimized for running out of the box on Kubernetes. +This chart works on both Kubernetes and OpenShift. -For OpenShift, we recommend using the `oc adm must-gather` command, like so: +But for OpenShift, we recommend using the `oc adm must-gather` command, like so: ```sh oc adm must-gather --image=quay.io/rhdh-community/rhdh-must-gather @@ -46,15 +46,6 @@ oc adm must-gather --image=quay.io/rhdh-community/rhdh-must-gather See the [must-gather tool README](https://github.com/redhat-developer/rhdh-must-gather#for-openshift-clusters) for more details. -But if you still want to use this chart on OpenShift, you will need to unset the `podSecurityContext.fsGroup` value (or set it to a value aligned with your Security Context Constraints). - -```sh -helm upgrade --install my-rhdh-must-gather rhdh-must-gather \ - --repo https://redhat-developer.github.io/rhdh-chart \ - --version 0.1.0 \ - --set podSecurityContext.fsGroup=null -``` - ## Testing a Release Once a Helm Release has been deployed, you can test it using the [`helm test`](https://helm.sh/docs/helm/helm_test/) command: @@ -133,7 +124,7 @@ The command removes all the Kubernetes resources associated with the chart and d | persistence.storageClass | Storage class (empty = use cluster default) | string | `""` | | podAnnotations | Pod annotations | object | `{}` | | podLabels | Pod labels | object | `{}` | -| podSecurityContext | On OCP, the SCC may override fsGroup with a value from the namespace's allowed range. | object | `{"fsGroup":1001,"runAsNonRoot":true,"seccompProfile":{"type":"RuntimeDefault"}}` | +| podSecurityContext | Pod security context | object | `{"runAsNonRoot":true,"seccompProfile":{"type":"RuntimeDefault"}}` | | rbac | RBAC configuration | object | `{"create":true,"scope":"cluster"}` | | rbac.create | Create RBAC resources (Role/ClusterRole and bindings) | bool | `true` | | resources | Resource requests and limits for the gather container | object | `{"limits":{"cpu":"500m","ephemeral-storage":"128Mi","memory":"512Mi"},"requests":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"}}` | diff --git a/charts/must-gather/README.md.gotmpl b/charts/must-gather/README.md.gotmpl index 06d6dae9..dc9a7561 100644 --- a/charts/must-gather/README.md.gotmpl +++ b/charts/must-gather/README.md.gotmpl @@ -29,9 +29,9 @@ Then follow the instructions that will be printed to retrieve the gathered data. ## Running on OpenShift -This chart is optimized for running out of the box on Kubernetes. +This chart works on both Kubernetes and OpenShift. -For OpenShift, we recommend using the `oc adm must-gather` command, like so: +But for OpenShift, we recommend using the `oc adm must-gather` command, like so: ```sh oc adm must-gather --image=quay.io/rhdh-community/rhdh-must-gather @@ -39,15 +39,6 @@ oc adm must-gather --image=quay.io/rhdh-community/rhdh-must-gather See the [must-gather tool README](https://github.com/redhat-developer/rhdh-must-gather#for-openshift-clusters) for more details. -But if you still want to use this chart on OpenShift, you will need to unset the `podSecurityContext.fsGroup` value (or set it to a value aligned with your Security Context Constraints). - -```sh -helm upgrade --install my-rhdh-must-gather rhdh-must-gather \ - --repo https://redhat-developer.github.io/rhdh-chart \ - --version {{ template "chart.version" . }} \ - --set podSecurityContext.fsGroup=null -``` - ## Testing a Release Once a Helm Release has been deployed, you can test it using the [`helm test`](https://helm.sh/docs/helm/helm_test/) command: diff --git a/charts/must-gather/values.schema.tmpl.json b/charts/must-gather/values.schema.tmpl.json index 4110493c..83f27e0c 100644 --- a/charts/must-gather/values.schema.tmpl.json +++ b/charts/must-gather/values.schema.tmpl.json @@ -427,7 +427,6 @@ "$ref": "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.4/_definitions.json#/definitions/io.k8s.api.core.v1.SecurityContext", "default": { "runAsNonRoot": true, - "fsGroup": 1001, "seccompProfile": { "type": "RuntimeDefault" } diff --git a/charts/must-gather/values.yaml b/charts/must-gather/values.yaml index c2437ab0..ba377570 100644 --- a/charts/must-gather/values.yaml +++ b/charts/must-gather/values.yaml @@ -128,10 +128,8 @@ podAnnotations: {} podLabels: {} # -- Pod security context -# -- On OCP, the SCC may override fsGroup with a value from the namespace's allowed range. podSecurityContext: runAsNonRoot: true - fsGroup: 1001 seccompProfile: type: RuntimeDefault From 241223745ba5e896f8b2994dbecfad8fedb8aa9e Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Mon, 16 Mar 2026 10:52:32 +0100 Subject: [PATCH 25/34] grant secrets get/list permission only if withSecrets or Helm collection is enabled This is needed because 'helm list' uses a Secret storage backend by default, so it is needed to identify such Helm releases --- charts/must-gather/templates/clusterrbac.yaml | 7 ++++++- charts/must-gather/templates/rbac.yaml | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/charts/must-gather/templates/clusterrbac.yaml b/charts/must-gather/templates/clusterrbac.yaml index a0e0966a..8bb2a224 100644 --- a/charts/must-gather/templates/clusterrbac.yaml +++ b/charts/must-gather/templates/clusterrbac.yaml @@ -7,8 +7,13 @@ metadata: {{- include "rhdh-must-gather.labels" . | nindent 4 }} rules: - apiGroups: [""] - resources: ["pods", "pods/log", "services", "endpoints", "configmaps", "secrets", "events", "persistentvolumeclaims", "serviceaccounts", "replicationcontrollers", "resourcequotas", "limitranges"] + resources: ["pods", "pods/log", "services", "endpoints", "configmaps", "events", "persistentvolumeclaims", "serviceaccounts", "replicationcontrollers", "resourcequotas", "limitranges"] verbs: ["get", "list"] + {{- if or .Values.gather.withSecrets (not .Values.gather.withoutHelm) }} + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list"] + {{- end }} - apiGroups: [""] resources: ["namespaces", "nodes"] verbs: ["get", "list"] diff --git a/charts/must-gather/templates/rbac.yaml b/charts/must-gather/templates/rbac.yaml index e358a700..15bed519 100644 --- a/charts/must-gather/templates/rbac.yaml +++ b/charts/must-gather/templates/rbac.yaml @@ -8,8 +8,13 @@ metadata: {{- include "rhdh-must-gather.labels" . | nindent 4 }} rules: - apiGroups: [""] - resources: ["pods", "pods/log", "services", "endpoints", "configmaps", "secrets", "events", "persistentvolumeclaims", "serviceaccounts", "replicationcontrollers", "resourcequotas", "limitranges"] + resources: ["pods", "pods/log", "services", "endpoints", "configmaps", "events", "persistentvolumeclaims", "serviceaccounts", "replicationcontrollers", "resourcequotas", "limitranges"] verbs: ["get", "list"] + {{- if or .Values.gather.withSecrets (not .Values.gather.withoutHelm) }} + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list"] + {{- end }} - apiGroups: ["apps"] resources: ["deployments", "replicasets", "statefulsets", "daemonsets"] verbs: ["get", "list"] From 835c46a51bb11094fc958638469c7936a7e0d904 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Mon, 16 Mar 2026 10:57:59 +0100 Subject: [PATCH 26/34] avoid confusive double negative fields --- charts/must-gather/README.md | 4 +- .../ci/with-minimal-collection-values.yaml | 10 ++-- charts/must-gather/templates/clusterrbac.yaml | 2 +- charts/must-gather/templates/deployment.yaml | 16 +++---- charts/must-gather/templates/rbac.yaml | 2 +- charts/must-gather/values.schema.json | 46 +++++++++---------- charts/must-gather/values.schema.tmpl.json | 42 ++++++++--------- charts/must-gather/values.yaml | 16 +++---- 8 files changed, 69 insertions(+), 69 deletions(-) diff --git a/charts/must-gather/README.md b/charts/must-gather/README.md index 4564352b..bef94b00 100644 --- a/charts/must-gather/README.md +++ b/charts/must-gather/README.md @@ -103,15 +103,15 @@ The command removes all the Kubernetes resources associated with the chart and d | dataHolder | Runs alongside the gather container and stays alive so you can exec in and retrieve the output. | object | `{"resources":{"limits":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"},"requests":{"cpu":"50m","ephemeral-storage":"32Mi","memory":"64Mi"}}}` | | dataHolder.resources | Resource requests and limits for the data-holder container | object | `{"limits":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"},"requests":{"cpu":"50m","ephemeral-storage":"32Mi","memory":"64Mi"}}` | | fullnameOverride | | string | `""` | -| gather | Gather script configuration | object | `{"clusterInfo":false,"cmdTimeout":"30","extraArgs":[],"logLevel":"info","namespaces":[],"since":"","sinceTime":"","withHeapDumps":false,"withSecrets":false,"withoutHelm":false,"withoutIngress":false,"withoutNamespaceInspect":false,"withoutOperator":false,"withoutOrchestrator":false,"withoutPlatform":false,"withoutRoute":false}` | +| gather | Gather script configuration | object | `{"clusterInfo":false,"cmdTimeout":"30","extraArgs":[],"logLevel":"info","namespaces":[],"since":"","sinceTime":"","withHeapDumps":false,"withHelm":true,"withIngress":true,"withNamespaceInspect":true,"withOperator":true,"withOrchestrator":true,"withPlatform":true,"withRoute":true,"withSecrets":false}` | | gather.cmdTimeout | Command timeout for individual kubectl/helm commands (seconds) | string | `"30"` | | gather.extraArgs | Additional custom arguments to pass to the gather script | list | `[]` | | gather.logLevel | Log level: info, INFO, debug, DEBUG, trace, TRACE | string | `"info"` | | gather.namespaces | Example: ["rhdh-prod", "rhdh-staging"] | list | `[]` | | gather.since | Relative time for log collection (e.g., "2h", "30m") | string | `""` | | gather.sinceTime | Absolute timestamp for log collection (RFC3339 format) | string | `""` | +| gather.withOperator | Collection features (enabled by default; set to false to skip) | bool | `true` | | gather.withSecrets | Optional collection features (disabled by default) | bool | `false` | -| gather.withoutOperator | Exclusion options (set to true to skip collection) | bool | `false` | | image | Container image configuration | object | `{"digest":"","pullPolicy":"","registry":"quay.io","repository":"rhdh-community/rhdh-must-gather","tag":"latest"}` | | image.digest | Image digest (e.g., sha256:abc123...). Can be used with or without tag. | string | `""` | | image.tag | Overrides the image tag whose default is the chart appVersion. | string | `"latest"` | diff --git a/charts/must-gather/ci/with-minimal-collection-values.yaml b/charts/must-gather/ci/with-minimal-collection-values.yaml index e7cf75e5..19d25d0e 100644 --- a/charts/must-gather/ci/with-minimal-collection-values.yaml +++ b/charts/must-gather/ci/with-minimal-collection-values.yaml @@ -1,8 +1,8 @@ # Minimal collection — skip optional components to speed up gathering gather: - withoutOperator: true - withoutOrchestrator: true - withoutHelm: true - withoutRoute: true - withoutIngress: true + withOperator: false + withOrchestrator: false + withHelm: false + withRoute: false + withIngress: false logLevel: "TRACE" diff --git a/charts/must-gather/templates/clusterrbac.yaml b/charts/must-gather/templates/clusterrbac.yaml index 8bb2a224..7003b078 100644 --- a/charts/must-gather/templates/clusterrbac.yaml +++ b/charts/must-gather/templates/clusterrbac.yaml @@ -9,7 +9,7 @@ rules: - apiGroups: [""] resources: ["pods", "pods/log", "services", "endpoints", "configmaps", "events", "persistentvolumeclaims", "serviceaccounts", "replicationcontrollers", "resourcequotas", "limitranges"] verbs: ["get", "list"] - {{- if or .Values.gather.withSecrets (not .Values.gather.withoutHelm) }} + {{- if or .Values.gather.withSecrets .Values.gather.withHelm }} - apiGroups: [""] resources: ["secrets"] verbs: ["get", "list"] diff --git a/charts/must-gather/templates/deployment.yaml b/charts/must-gather/templates/deployment.yaml index c1d5fff4..5e0eee1d 100644 --- a/charts/must-gather/templates/deployment.yaml +++ b/charts/must-gather/templates/deployment.yaml @@ -73,7 +73,7 @@ spec: {{- if $nsScope }} {{- $effectiveNamespaces = list .Release.Namespace }} {{- end }} - {{- if or $nsScope .Values.gather.withSecrets .Values.gather.withHeapDumps .Values.gather.clusterInfo .Values.gather.withoutOperator .Values.gather.withoutOrchestrator .Values.gather.withoutHelm .Values.gather.withoutPlatform .Values.gather.withoutRoute .Values.gather.withoutIngress .Values.gather.withoutNamespaceInspect $effectiveNamespaces .Values.gather.extraArgs }} + {{- if or $nsScope .Values.gather.withSecrets .Values.gather.withHeapDumps .Values.gather.clusterInfo (not .Values.gather.withOperator) (not .Values.gather.withOrchestrator) (not .Values.gather.withHelm) (not .Values.gather.withPlatform) (not .Values.gather.withRoute) (not .Values.gather.withIngress) (not .Values.gather.withNamespaceInspect) $effectiveNamespaces .Values.gather.extraArgs }} args: {{- if .Values.gather.withSecrets }} - "--with-secrets" @@ -84,25 +84,25 @@ spec: {{- if .Values.gather.clusterInfo }} - "--cluster-info" {{- end }} - {{- if .Values.gather.withoutOperator }} + {{- if not .Values.gather.withOperator }} - "--without-operator" {{- end }} - {{- if .Values.gather.withoutOrchestrator }} + {{- if not .Values.gather.withOrchestrator }} - "--without-orchestrator" {{- end }} - {{- if .Values.gather.withoutHelm }} + {{- if not .Values.gather.withHelm }} - "--without-helm" {{- end }} - {{- if or .Values.gather.withoutPlatform $nsScope }} + {{- if or (not .Values.gather.withPlatform) $nsScope }} - "--without-platform" {{- end }} - {{- if .Values.gather.withoutRoute }} + {{- if not .Values.gather.withRoute }} - "--without-route" {{- end }} - {{- if .Values.gather.withoutIngress }} + {{- if not .Values.gather.withIngress }} - "--without-ingress" {{- end }} - {{- if .Values.gather.withoutNamespaceInspect }} + {{- if not .Values.gather.withNamespaceInspect }} - "--without-namespace-inspect" {{- end }} {{- if $effectiveNamespaces }} diff --git a/charts/must-gather/templates/rbac.yaml b/charts/must-gather/templates/rbac.yaml index 15bed519..b5f377d0 100644 --- a/charts/must-gather/templates/rbac.yaml +++ b/charts/must-gather/templates/rbac.yaml @@ -10,7 +10,7 @@ rules: - apiGroups: [""] resources: ["pods", "pods/log", "services", "endpoints", "configmaps", "events", "persistentvolumeclaims", "serviceaccounts", "replicationcontrollers", "resourcequotas", "limitranges"] verbs: ["get", "list"] - {{- if or .Values.gather.withSecrets (not .Values.gather.withoutHelm) }} + {{- if or .Values.gather.withSecrets .Values.gather.withHelm }} - apiGroups: [""] resources: ["secrets"] verbs: ["get", "list"] diff --git a/charts/must-gather/values.schema.json b/charts/must-gather/values.schema.json index 6c5624d0..b1f7e845 100644 --- a/charts/must-gather/values.schema.json +++ b/charts/must-gather/values.schema.json @@ -876,44 +876,44 @@ "title": "Enable collection of heap dumps (disabled by default).", "type": "boolean" }, - "withSecrets": { - "default": false, - "title": "Enable collection of Secrets (disabled by default).", + "withHelm": { + "default": true, + "title": "Enable collection of Helm release information.", "type": "boolean" }, - "withoutHelm": { - "default": false, - "title": "Skip collection of Helm release information.", + "withIngress": { + "default": true, + "title": "Enable collection of Ingress resources.", "type": "boolean" }, - "withoutIngress": { - "default": false, - "title": "Skip collection of Ingress resources.", + "withNamespaceInspect": { + "default": true, + "title": "Enable namespace-level inspection.", "type": "boolean" }, - "withoutNamespaceInspect": { - "default": false, - "title": "Skip namespace-level inspection.", + "withOperator": { + "default": true, + "title": "Enable collection of RHDH Operator resources.", "type": "boolean" }, - "withoutOperator": { - "default": false, - "title": "Skip collection of RHDH Operator resources.", + "withOrchestrator": { + "default": true, + "title": "Enable collection of Orchestrator resources.", "type": "boolean" }, - "withoutOrchestrator": { - "default": false, - "title": "Skip collection of Orchestrator resources.", + "withPlatform": { + "default": true, + "title": "Enable collection of platform-level resources.", "type": "boolean" }, - "withoutPlatform": { - "default": false, - "title": "Skip collection of platform-level resources.", + "withRoute": { + "default": true, + "title": "Enable collection of OpenShift Route resources.", "type": "boolean" }, - "withoutRoute": { + "withSecrets": { "default": false, - "title": "Skip collection of OpenShift Route resources.", + "title": "Enable collection of Secrets (disabled by default).", "type": "boolean" } }, diff --git a/charts/must-gather/values.schema.tmpl.json b/charts/must-gather/values.schema.tmpl.json index 83f27e0c..eb5db8bf 100644 --- a/charts/must-gather/values.schema.tmpl.json +++ b/charts/must-gather/values.schema.tmpl.json @@ -172,40 +172,40 @@ "type": "boolean", "default": false }, - "withoutOperator": { - "title": "Skip collection of RHDH Operator resources.", + "withOperator": { + "title": "Enable collection of RHDH Operator resources.", "type": "boolean", - "default": false + "default": true }, - "withoutOrchestrator": { - "title": "Skip collection of Orchestrator resources.", + "withOrchestrator": { + "title": "Enable collection of Orchestrator resources.", "type": "boolean", - "default": false + "default": true }, - "withoutHelm": { - "title": "Skip collection of Helm release information.", + "withHelm": { + "title": "Enable collection of Helm release information.", "type": "boolean", - "default": false + "default": true }, - "withoutPlatform": { - "title": "Skip collection of platform-level resources.", + "withPlatform": { + "title": "Enable collection of platform-level resources.", "type": "boolean", - "default": false + "default": true }, - "withoutRoute": { - "title": "Skip collection of OpenShift Route resources.", + "withRoute": { + "title": "Enable collection of OpenShift Route resources.", "type": "boolean", - "default": false + "default": true }, - "withoutIngress": { - "title": "Skip collection of Ingress resources.", + "withIngress": { + "title": "Enable collection of Ingress resources.", "type": "boolean", - "default": false + "default": true }, - "withoutNamespaceInspect": { - "title": "Skip namespace-level inspection.", + "withNamespaceInspect": { + "title": "Enable namespace-level inspection.", "type": "boolean", - "default": false + "default": true }, "namespaces": { "title": "Limit collection to specific namespaces. Example: ['rhdh-prod', 'rhdh-staging'].", diff --git a/charts/must-gather/values.yaml b/charts/must-gather/values.yaml index ba377570..0ea0f13e 100644 --- a/charts/must-gather/values.yaml +++ b/charts/must-gather/values.yaml @@ -59,14 +59,14 @@ gather: withHeapDumps: false clusterInfo: false - # -- Exclusion options (set to true to skip collection) - withoutOperator: false - withoutOrchestrator: false - withoutHelm: false - withoutPlatform: false - withoutRoute: false - withoutIngress: false - withoutNamespaceInspect: false + # -- Collection features (enabled by default; set to false to skip) + withOperator: true + withOrchestrator: true + withHelm: true + withPlatform: true + withRoute: true + withIngress: true + withNamespaceInspect: true # -- Limit collection to specific namespaces # -- Example: ["rhdh-prod", "rhdh-staging"] From 5a09172f98b50bde7eeda499c4722d8c972c3c57 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Mon, 16 Mar 2026 11:02:49 +0100 Subject: [PATCH 27/34] clarify how the rbac scope affects the handling of gather.namespaces --- charts/must-gather/ci/with-namespace-rbac-values.yaml | 5 ++++- charts/must-gather/templates/deployment.yaml | 1 + charts/must-gather/values.schema.json | 4 ++-- charts/must-gather/values.schema.tmpl.json | 4 ++-- charts/must-gather/values.yaml | 7 ++++--- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/charts/must-gather/ci/with-namespace-rbac-values.yaml b/charts/must-gather/ci/with-namespace-rbac-values.yaml index 6b222d80..3c109a97 100644 --- a/charts/must-gather/ci/with-namespace-rbac-values.yaml +++ b/charts/must-gather/ci/with-namespace-rbac-values.yaml @@ -1,6 +1,9 @@ -# Namespace-scoped RBAC — creates a Role instead of a ClusterRole +# Namespace-scoped RBAC — creates a Role instead of a ClusterRole. +# Collection is restricted to the release namespace only; +# gather.namespaces is ignored in this mode. rbac: scope: namespace gather: + # NOTE: this field will be ignored when rbac.scope is "namespace". namespaces: - "kube-system" diff --git a/charts/must-gather/templates/deployment.yaml b/charts/must-gather/templates/deployment.yaml index 5e0eee1d..3a137f14 100644 --- a/charts/must-gather/templates/deployment.yaml +++ b/charts/must-gather/templates/deployment.yaml @@ -71,6 +71,7 @@ spec: {{- $nsScope := ne (.Values.rbac.scope | default "cluster") "cluster" }} {{- $effectiveNamespaces := .Values.gather.namespaces | default list }} {{- if $nsScope }} + {{- /* Namespace-scoped RBAC: override to release namespace only (gather.namespaces is ignored) */ -}} {{- $effectiveNamespaces = list .Release.Namespace }} {{- end }} {{- if or $nsScope .Values.gather.withSecrets .Values.gather.withHeapDumps .Values.gather.clusterInfo (not .Values.gather.withOperator) (not .Values.gather.withOrchestrator) (not .Values.gather.withHelm) (not .Values.gather.withPlatform) (not .Values.gather.withRoute) (not .Values.gather.withIngress) (not .Values.gather.withNamespaceInspect) $effectiveNamespaces .Values.gather.extraArgs }} diff --git a/charts/must-gather/values.schema.json b/charts/must-gather/values.schema.json index b1f7e845..3e26f732 100644 --- a/charts/must-gather/values.schema.json +++ b/charts/must-gather/values.schema.json @@ -858,7 +858,7 @@ "items": { "type": "string" }, - "title": "Limit collection to specific namespaces. Example: ['rhdh-prod', 'rhdh-staging'].", + "title": "Limit collection to specific namespaces (only effective when rbac.scope is 'cluster'). Ignored when rbac.scope is 'namespace'. Example: ['rhdh-prod', 'rhdh-staging'].", "type": "array" }, "since": { @@ -1182,7 +1182,7 @@ }, "scope": { "default": "cluster", - "description": "'cluster' creates a ClusterRole with cluster-wide read access. 'namespace' creates a namespace-scoped Role; the release namespace is always included in --namespaces automatically, alongside any extra namespaces in gather.namespaces.", + "description": "'cluster' creates a ClusterRole with cluster-wide read access. 'namespace' creates a namespace-scoped Role limited to the release namespace only (gather.namespaces is ignored; collection is restricted to the release namespace).", "enum": [ "cluster", "namespace" diff --git a/charts/must-gather/values.schema.tmpl.json b/charts/must-gather/values.schema.tmpl.json index eb5db8bf..b38d3bec 100644 --- a/charts/must-gather/values.schema.tmpl.json +++ b/charts/must-gather/values.schema.tmpl.json @@ -100,7 +100,7 @@ }, "scope": { "title": "Scope of the RBAC role.", - "description": "'cluster' creates a ClusterRole with cluster-wide read access. 'namespace' creates a namespace-scoped Role; the release namespace is always included in --namespaces automatically, alongside any extra namespaces in gather.namespaces.", + "description": "'cluster' creates a ClusterRole with cluster-wide read access. 'namespace' creates a namespace-scoped Role limited to the release namespace only (gather.namespaces is ignored; collection is restricted to the release namespace).", "type": "string", "default": "cluster", "enum": ["cluster", "namespace"] @@ -208,7 +208,7 @@ "default": true }, "namespaces": { - "title": "Limit collection to specific namespaces. Example: ['rhdh-prod', 'rhdh-staging'].", + "title": "Limit collection to specific namespaces (only effective when rbac.scope is 'cluster'). Ignored when rbac.scope is 'namespace'. Example: ['rhdh-prod', 'rhdh-staging'].", "type": "array", "items": { "type": "string" diff --git a/charts/must-gather/values.yaml b/charts/must-gather/values.yaml index 0ea0f13e..9dc78cb6 100644 --- a/charts/must-gather/values.yaml +++ b/charts/must-gather/values.yaml @@ -35,8 +35,8 @@ rbac: # -- Create RBAC resources (Role/ClusterRole and bindings) create: true # -- Scope of the RBAC role: "cluster" creates a ClusterRole with cluster-wide read access; - # -- "namespace" creates a namespace-scoped Role (the release namespace is always included - # -- in --namespaces automatically, alongside any extra namespaces in gather.namespaces) + # -- "namespace" creates a namespace-scoped Role limited to the release namespace only + # -- (gather.namespaces is ignored; collection is restricted to the release namespace) scope: cluster # -- Deployment strategy @@ -68,7 +68,8 @@ gather: withIngress: true withNamespaceInspect: true - # -- Limit collection to specific namespaces + # -- Limit collection to specific namespaces (only effective when rbac.scope is "cluster"). + # -- Ignored when rbac.scope is "namespace" (collection is restricted to the release namespace). # -- Example: ["rhdh-prod", "rhdh-staging"] namespaces: [] From a0f3cda6a6c668fbb0184df6365d692c6f809289 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Mon, 16 Mar 2026 11:04:26 +0100 Subject: [PATCH 28/34] fix examples for setting test.image --- charts/must-gather/README.md | 4 +++- charts/must-gather/README.md.gotmpl | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/charts/must-gather/README.md b/charts/must-gather/README.md index bef94b00..db974d2e 100644 --- a/charts/must-gather/README.md +++ b/charts/must-gather/README.md @@ -80,7 +80,9 @@ helm install \ ```sh helm install \ - --set test.image= + --set test.image.registry=docker.io \ + --set test.image.repository=bitnami/kubectl \ + --set test.image.tag=1.30.0 ``` diff --git a/charts/must-gather/README.md.gotmpl b/charts/must-gather/README.md.gotmpl index dc9a7561..571a7db7 100644 --- a/charts/must-gather/README.md.gotmpl +++ b/charts/must-gather/README.md.gotmpl @@ -73,7 +73,9 @@ helm install \ ```sh helm install \ - --set test.image= + --set test.image.registry=docker.io \ + --set test.image.repository=bitnami/kubectl \ + --set test.image.tag=1.30.0 ``` From 2944ff32e569783cee3fe323c16c11b514f919be Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Mon, 16 Mar 2026 11:19:23 +0100 Subject: [PATCH 29/34] simplify by removing '| default ...' if the field already has a default values enforced in values.yaml and the JSON schema file --- charts/must-gather/templates/_helpers.tpl | 4 ++-- charts/must-gather/templates/clusterrbac.yaml | 2 +- charts/must-gather/templates/deployment.yaml | 6 +++--- charts/must-gather/templates/rbac.yaml | 2 +- charts/must-gather/templates/tests/test.yaml | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/charts/must-gather/templates/_helpers.tpl b/charts/must-gather/templates/_helpers.tpl index 14861300..e45085ab 100644 --- a/charts/must-gather/templates/_helpers.tpl +++ b/charts/must-gather/templates/_helpers.tpl @@ -63,10 +63,10 @@ Build a full image reference from registry, repository, and tag. Usage: {{ include "rhdh-must-gather.image" (dict "image" .Values.image "defaultTag" .Chart.AppVersion) }} */}} {{- define "rhdh-must-gather.image" -}} -{{- $registry := .image.registry | default "" -}} +{{- $registry := .image.registry -}} {{- $repository := .image.repository -}} {{- $tag := .image.tag | default .defaultTag | default "" -}} -{{- $digest := .image.digest | default "" -}} +{{- $digest := .image.digest -}} {{- $ref := "" -}} {{- if and $tag $digest -}} {{- $ref = printf ":%s@%s" $tag $digest -}} diff --git a/charts/must-gather/templates/clusterrbac.yaml b/charts/must-gather/templates/clusterrbac.yaml index 7003b078..c950e0ca 100644 --- a/charts/must-gather/templates/clusterrbac.yaml +++ b/charts/must-gather/templates/clusterrbac.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.rbac.create (eq (.Values.rbac.scope | default "cluster") "cluster") -}} +{{- if and .Values.rbac.create (eq .Values.rbac.scope "cluster") -}} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: diff --git a/charts/must-gather/templates/deployment.yaml b/charts/must-gather/templates/deployment.yaml index 3a137f14..27ad1e64 100644 --- a/charts/must-gather/templates/deployment.yaml +++ b/charts/must-gather/templates/deployment.yaml @@ -68,8 +68,8 @@ spec: - name: MUST_GATHER_SINCE_TIME value: {{ .Values.gather.sinceTime | quote }} {{- end }} - {{- $nsScope := ne (.Values.rbac.scope | default "cluster") "cluster" }} - {{- $effectiveNamespaces := .Values.gather.namespaces | default list }} + {{- $nsScope := ne .Values.rbac.scope "cluster" }} + {{- $effectiveNamespaces := .Values.gather.namespaces }} {{- if $nsScope }} {{- /* Namespace-scoped RBAC: override to release namespace only (gather.namespaces is ignored) */ -}} {{- $effectiveNamespaces = list .Release.Namespace }} @@ -151,7 +151,7 @@ spec: defaultMode: 0444 sources: - serviceAccountToken: - expirationSeconds: {{ .Values.serviceAccount.tokenExpirationSeconds | default 3600 }} + expirationSeconds: {{ .Values.serviceAccount.tokenExpirationSeconds }} path: token - configMap: name: kube-root-ca.crt diff --git a/charts/must-gather/templates/rbac.yaml b/charts/must-gather/templates/rbac.yaml index b5f377d0..6990e650 100644 --- a/charts/must-gather/templates/rbac.yaml +++ b/charts/must-gather/templates/rbac.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.rbac.create (ne (.Values.rbac.scope | default "cluster") "cluster") -}} +{{- if and .Values.rbac.create (ne .Values.rbac.scope "cluster") -}} apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: diff --git a/charts/must-gather/templates/tests/test.yaml b/charts/must-gather/templates/tests/test.yaml index bd0b025a..ba8a662a 100644 --- a/charts/must-gather/templates/tests/test.yaml +++ b/charts/must-gather/templates/tests/test.yaml @@ -131,7 +131,7 @@ spec: defaultMode: 0444 sources: - serviceAccountToken: - expirationSeconds: {{ .Values.serviceAccount.tokenExpirationSeconds | default 3600 }} + expirationSeconds: {{ .Values.serviceAccount.tokenExpirationSeconds }} path: token - configMap: name: kube-root-ca.crt From bafe158f3d7f6dc9dea47e577afe9e8b48457c4f Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Tue, 17 Mar 2026 23:47:16 +0100 Subject: [PATCH 30/34] fix missing RBAC permissions --- charts/must-gather/templates/clusterrbac.yaml | 8 ++++++++ charts/must-gather/templates/rbac.yaml | 3 +++ 2 files changed, 11 insertions(+) diff --git a/charts/must-gather/templates/clusterrbac.yaml b/charts/must-gather/templates/clusterrbac.yaml index c950e0ca..60168afa 100644 --- a/charts/must-gather/templates/clusterrbac.yaml +++ b/charts/must-gather/templates/clusterrbac.yaml @@ -20,9 +20,17 @@ rules: - apiGroups: ["apps"] resources: ["deployments", "replicasets", "statefulsets", "daemonsets"] verbs: ["get", "list"] + # Platform detection (gather_platform): clusterversions, infrastructures + - apiGroups: ["config.openshift.io"] + resources: ["clusterversions", "infrastructures"] + verbs: ["get", "list"] - apiGroups: ["networking.k8s.io"] resources: ["ingresses", "networkpolicies", "ingressclasses"] verbs: ["get", "list"] + # Route collection (gather_route) + - apiGroups: ["route.openshift.io"] + resources: ["routes"] + verbs: ["get", "list"] - apiGroups: ["rbac.authorization.k8s.io"] resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"] verbs: ["get", "list"] diff --git a/charts/must-gather/templates/rbac.yaml b/charts/must-gather/templates/rbac.yaml index 6990e650..c6dc9216 100644 --- a/charts/must-gather/templates/rbac.yaml +++ b/charts/must-gather/templates/rbac.yaml @@ -21,6 +21,9 @@ rules: - apiGroups: ["networking.k8s.io"] resources: ["ingresses", "networkpolicies"] verbs: ["get", "list"] + - apiGroups: ["route.openshift.io"] + resources: ["routes"] + verbs: ["get", "list"] - apiGroups: ["rbac.authorization.k8s.io"] resources: ["roles", "rolebindings"] verbs: ["get", "list"] From b77f552663ca7f45ada97ba88c7c991d68eb015d Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Wed, 18 Mar 2026 23:30:12 +0100 Subject: [PATCH 31/34] allow users to selectively disable RBAC rules they cannot grant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kubernetes requires the installing user to already hold any permission they grant via a Role or ClusterRole. When deploying with namespace- scoped RBAC on a cluster where CRDs like backstages or sonataflows are not installed, the role creation fails because those permissions cannot be escalated. Rather than a single opaque toggle, expose per-API-group booleans under rbac.rules so users can precisely disable only the rules they cannot grant, while keeping the corresponding gather.with* collection flags enabled — the gather script already handles missing permissions gracefully at runtime. Also removes a duplicate config.openshift.io/clusterversions rule from the ClusterRole template. Assisted-by: Cursor Made-with: Cursor --- charts/must-gather/README.md | 10 ++++- charts/must-gather/templates/clusterrbac.yaml | 19 +++++--- charts/must-gather/templates/rbac.yaml | 12 ++++++ charts/must-gather/values.schema.json | 43 +++++++++++++++++++ charts/must-gather/values.schema.tmpl.json | 43 +++++++++++++++++++ charts/must-gather/values.yaml | 20 +++++++++ 6 files changed, 141 insertions(+), 6 deletions(-) diff --git a/charts/must-gather/README.md b/charts/must-gather/README.md index db974d2e..766d6600 100644 --- a/charts/must-gather/README.md +++ b/charts/must-gather/README.md @@ -127,8 +127,16 @@ The command removes all the Kubernetes resources associated with the chart and d | podAnnotations | Pod annotations | object | `{}` | | podLabels | Pod labels | object | `{}` | | podSecurityContext | Pod security context | object | `{"runAsNonRoot":true,"seccompProfile":{"type":"RuntimeDefault"}}` | -| rbac | RBAC configuration | object | `{"create":true,"scope":"cluster"}` | +| rbac | RBAC configuration | object | `{"create":true,"rules":{"backstages":true,"ingresses":true,"knative":true,"olm":true,"platform":true,"routes":true,"sonataflow":true},"scope":"cluster"}` | | rbac.create | Create RBAC resources (Role/ClusterRole and bindings) | bool | `true` | +| rbac.rules | a rule here does not require disabling the corresponding gather.with* flag. | object | `{"backstages":true,"ingresses":true,"knative":true,"olm":true,"platform":true,"routes":true,"sonataflow":true}` | +| rbac.rules.backstages | rhdh.redhat.com — Backstage custom resources | bool | `true` | +| rbac.rules.ingresses | networking.k8s.io — Ingresses, NetworkPolicies | bool | `true` | +| rbac.rules.knative | operator.knative.dev, operator.serverless.openshift.io — Knative/Serverless | bool | `true` | +| rbac.rules.olm | operators.coreos.com — OLM resources (subscriptions, CSVs, etc.) | bool | `true` | +| rbac.rules.platform | config.openshift.io — ClusterVersions, Infrastructures (cluster scope only) | bool | `true` | +| rbac.rules.routes | route.openshift.io — OpenShift Routes | bool | `true` | +| rbac.rules.sonataflow | sonataflow.org — SonataFlow custom resources | bool | `true` | | resources | Resource requests and limits for the gather container | object | `{"limits":{"cpu":"500m","ephemeral-storage":"128Mi","memory":"512Mi"},"requests":{"cpu":"100m","ephemeral-storage":"64Mi","memory":"128Mi"}}` | | securityContext | Container security context | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]}}` | | serviceAccount | Service account configuration | object | `{"annotations":{},"name":"","tokenExpirationSeconds":3600}` | diff --git a/charts/must-gather/templates/clusterrbac.yaml b/charts/must-gather/templates/clusterrbac.yaml index 60168afa..7dd582ed 100644 --- a/charts/must-gather/templates/clusterrbac.yaml +++ b/charts/must-gather/templates/clusterrbac.yaml @@ -20,17 +20,21 @@ rules: - apiGroups: ["apps"] resources: ["deployments", "replicasets", "statefulsets", "daemonsets"] verbs: ["get", "list"] - # Platform detection (gather_platform): clusterversions, infrastructures + {{- if .Values.rbac.rules.platform }} - apiGroups: ["config.openshift.io"] resources: ["clusterversions", "infrastructures"] verbs: ["get", "list"] + {{- end }} + {{- if .Values.rbac.rules.ingresses }} - apiGroups: ["networking.k8s.io"] resources: ["ingresses", "networkpolicies", "ingressclasses"] verbs: ["get", "list"] - # Route collection (gather_route) + {{- end }} + {{- if .Values.rbac.rules.routes }} - apiGroups: ["route.openshift.io"] resources: ["routes"] verbs: ["get", "list"] + {{- end }} - apiGroups: ["rbac.authorization.k8s.io"] resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"] verbs: ["get", "list"] @@ -40,27 +44,32 @@ rules: - apiGroups: ["apiextensions.k8s.io"] resources: ["customresourcedefinitions"] verbs: ["get", "list"] + {{- if .Values.rbac.rules.backstages }} - apiGroups: ["rhdh.redhat.com"] resources: ["backstages"] verbs: ["get", "list"] + {{- end }} + {{- if .Values.rbac.rules.olm }} - apiGroups: ["operators.coreos.com"] resources: ["subscriptions", "clusterserviceversions", "installplans", "catalogsources", "operatorgroups"] verbs: ["get", "list"] + {{- end }} - apiGroups: [""] resources: ["pods/exec"] # NOSONAR - exec is required to collect data from the running containers verbs: ["create"] + {{- if .Values.rbac.rules.sonataflow }} - apiGroups: ["sonataflow.org"] resources: ["sonataflowplatforms", "sonataflows", "sonataflowbuilds", "sonataflowclusterplatforms"] verbs: ["get", "list"] + {{- end }} + {{- if .Values.rbac.rules.knative }} - apiGroups: ["operator.knative.dev"] resources: ["knativeservings", "knativeeventings"] verbs: ["get", "list"] - - apiGroups: ["config.openshift.io"] - resources: ["clusterversions"] - verbs: ["get", "list"] - apiGroups: ["operator.serverless.openshift.io"] resources: ["knativekafkas"] verbs: ["get", "list"] + {{- end }} --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/charts/must-gather/templates/rbac.yaml b/charts/must-gather/templates/rbac.yaml index c6dc9216..57cbd665 100644 --- a/charts/must-gather/templates/rbac.yaml +++ b/charts/must-gather/templates/rbac.yaml @@ -18,33 +18,45 @@ rules: - apiGroups: ["apps"] resources: ["deployments", "replicasets", "statefulsets", "daemonsets"] verbs: ["get", "list"] + {{- if .Values.rbac.rules.ingresses }} - apiGroups: ["networking.k8s.io"] resources: ["ingresses", "networkpolicies"] verbs: ["get", "list"] + {{- end }} + {{- if .Values.rbac.rules.routes }} - apiGroups: ["route.openshift.io"] resources: ["routes"] verbs: ["get", "list"] + {{- end }} - apiGroups: ["rbac.authorization.k8s.io"] resources: ["roles", "rolebindings"] verbs: ["get", "list"] + {{- if .Values.rbac.rules.backstages }} - apiGroups: ["rhdh.redhat.com"] resources: ["backstages"] verbs: ["get", "list"] + {{- end }} + {{- if .Values.rbac.rules.olm }} - apiGroups: ["operators.coreos.com"] resources: ["subscriptions", "clusterserviceversions", "installplans", "catalogsources", "operatorgroups"] verbs: ["get", "list"] + {{- end }} - apiGroups: [""] resources: ["pods/exec"] # NOSONAR - exec is required to collect data from the running containers verbs: ["create"] + {{- if .Values.rbac.rules.sonataflow }} - apiGroups: ["sonataflow.org"] resources: ["sonataflowplatforms", "sonataflows", "sonataflowbuilds"] verbs: ["get", "list"] + {{- end }} + {{- if .Values.rbac.rules.knative }} - apiGroups: ["operator.knative.dev"] resources: ["knativeservings", "knativeeventings"] verbs: ["get", "list"] - apiGroups: ["operator.serverless.openshift.io"] resources: ["knativekafkas"] verbs: ["get", "list"] + {{- end }} --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/charts/must-gather/values.schema.json b/charts/must-gather/values.schema.json index 3e26f732..b6696abb 100644 --- a/charts/must-gather/values.schema.json +++ b/charts/must-gather/values.schema.json @@ -1180,6 +1180,49 @@ "title": "Create RBAC resources (Role/ClusterRole and bindings).", "type": "boolean" }, + "rules": { + "additionalProperties": false, + "description": "Disable a rule if the installing user cannot grant those permissions (Kubernetes requires you to already hold any permission you grant). The gather script handles missing permissions gracefully.", + "properties": { + "backstages": { + "default": true, + "title": "rhdh.redhat.com \u2014 Backstage custom resources.", + "type": "boolean" + }, + "ingresses": { + "default": true, + "title": "networking.k8s.io \u2014 Ingresses, NetworkPolicies.", + "type": "boolean" + }, + "knative": { + "default": true, + "title": "operator.knative.dev, operator.serverless.openshift.io \u2014 Knative/Serverless.", + "type": "boolean" + }, + "olm": { + "default": true, + "title": "operators.coreos.com \u2014 OLM resources (subscriptions, CSVs, etc.).", + "type": "boolean" + }, + "platform": { + "default": true, + "title": "config.openshift.io \u2014 ClusterVersions, Infrastructures (cluster scope only).", + "type": "boolean" + }, + "routes": { + "default": true, + "title": "route.openshift.io \u2014 OpenShift Routes.", + "type": "boolean" + }, + "sonataflow": { + "default": true, + "title": "sonataflow.org \u2014 SonataFlow custom resources.", + "type": "boolean" + } + }, + "title": "Toggle individual API-group rules in the Role/ClusterRole.", + "type": "object" + }, "scope": { "default": "cluster", "description": "'cluster' creates a ClusterRole with cluster-wide read access. 'namespace' creates a namespace-scoped Role limited to the release namespace only (gather.namespaces is ignored; collection is restricted to the release namespace).", diff --git a/charts/must-gather/values.schema.tmpl.json b/charts/must-gather/values.schema.tmpl.json index b38d3bec..f6305517 100644 --- a/charts/must-gather/values.schema.tmpl.json +++ b/charts/must-gather/values.schema.tmpl.json @@ -104,6 +104,49 @@ "type": "string", "default": "cluster", "enum": ["cluster", "namespace"] + }, + "rules": { + "title": "Toggle individual API-group rules in the Role/ClusterRole.", + "description": "Disable a rule if the installing user cannot grant those permissions (Kubernetes requires you to already hold any permission you grant). The gather script handles missing permissions gracefully.", + "type": "object", + "additionalProperties": false, + "properties": { + "backstages": { + "title": "rhdh.redhat.com — Backstage custom resources.", + "type": "boolean", + "default": true + }, + "olm": { + "title": "operators.coreos.com — OLM resources (subscriptions, CSVs, etc.).", + "type": "boolean", + "default": true + }, + "routes": { + "title": "route.openshift.io — OpenShift Routes.", + "type": "boolean", + "default": true + }, + "sonataflow": { + "title": "sonataflow.org — SonataFlow custom resources.", + "type": "boolean", + "default": true + }, + "knative": { + "title": "operator.knative.dev, operator.serverless.openshift.io — Knative/Serverless.", + "type": "boolean", + "default": true + }, + "ingresses": { + "title": "networking.k8s.io — Ingresses, NetworkPolicies.", + "type": "boolean", + "default": true + }, + "platform": { + "title": "config.openshift.io — ClusterVersions, Infrastructures (cluster scope only).", + "type": "boolean", + "default": true + } + } } } }, diff --git a/charts/must-gather/values.yaml b/charts/must-gather/values.yaml index 9dc78cb6..6549be92 100644 --- a/charts/must-gather/values.yaml +++ b/charts/must-gather/values.yaml @@ -38,6 +38,26 @@ rbac: # -- "namespace" creates a namespace-scoped Role limited to the release namespace only # -- (gather.namespaces is ignored; collection is restricted to the release namespace) scope: cluster + # -- Control which API-group rules are included in the Role/ClusterRole. + # -- Disable a rule if the installing user cannot grant those permissions + # -- (Kubernetes requires you to already hold any permission you grant). + # -- The gather script handles missing permissions gracefully, so disabling + # -- a rule here does not require disabling the corresponding gather.with* flag. + rules: + # -- rhdh.redhat.com — Backstage custom resources + backstages: true + # -- operators.coreos.com — OLM resources (subscriptions, CSVs, etc.) + olm: true + # -- route.openshift.io — OpenShift Routes + routes: true + # -- sonataflow.org — SonataFlow custom resources + sonataflow: true + # -- operator.knative.dev, operator.serverless.openshift.io — Knative/Serverless + knative: true + # -- networking.k8s.io — Ingresses, NetworkPolicies + ingresses: true + # -- config.openshift.io — ClusterVersions, Infrastructures (cluster scope only) + platform: true # -- Deployment strategy strategy: From 01757643cd00403a5d53ea17c2e0c3bd2c0fb1ea Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Wed, 18 Mar 2026 23:51:44 +0100 Subject: [PATCH 32/34] fix missing RBAC permissions --- charts/must-gather/templates/clusterrbac.yaml | 5 ++++- charts/must-gather/templates/rbac.yaml | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/charts/must-gather/templates/clusterrbac.yaml b/charts/must-gather/templates/clusterrbac.yaml index 7dd582ed..1f751381 100644 --- a/charts/must-gather/templates/clusterrbac.yaml +++ b/charts/must-gather/templates/clusterrbac.yaml @@ -7,7 +7,10 @@ metadata: {{- include "rhdh-must-gather.labels" . | nindent 4 }} rules: - apiGroups: [""] - resources: ["pods", "pods/log", "services", "endpoints", "configmaps", "events", "persistentvolumeclaims", "serviceaccounts", "replicationcontrollers", "resourcequotas", "limitranges"] + resources: ["pods"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["pods/log", "services", "endpoints", "configmaps", "events", "persistentvolumeclaims", "serviceaccounts", "replicationcontrollers", "resourcequotas", "limitranges"] verbs: ["get", "list"] {{- if or .Values.gather.withSecrets .Values.gather.withHelm }} - apiGroups: [""] diff --git a/charts/must-gather/templates/rbac.yaml b/charts/must-gather/templates/rbac.yaml index 57cbd665..0233fd64 100644 --- a/charts/must-gather/templates/rbac.yaml +++ b/charts/must-gather/templates/rbac.yaml @@ -8,7 +8,10 @@ metadata: {{- include "rhdh-must-gather.labels" . | nindent 4 }} rules: - apiGroups: [""] - resources: ["pods", "pods/log", "services", "endpoints", "configmaps", "events", "persistentvolumeclaims", "serviceaccounts", "replicationcontrollers", "resourcequotas", "limitranges"] + resources: ["pods"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["pods/log", "services", "endpoints", "configmaps", "events", "persistentvolumeclaims", "serviceaccounts", "replicationcontrollers", "resourcequotas", "limitranges"] verbs: ["get", "list"] {{- if or .Values.gather.withSecrets .Values.gather.withHelm }} - apiGroups: [""] From cf76eff7a175f9a45b897a6995847b5520402341 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Mon, 30 Mar 2026 00:10:15 +0200 Subject: [PATCH 33/34] add pods/portforward permission for heap dump collection --- charts/must-gather/templates/clusterrbac.yaml | 7 ++++++- charts/must-gather/templates/rbac.yaml | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/charts/must-gather/templates/clusterrbac.yaml b/charts/must-gather/templates/clusterrbac.yaml index 1f751381..7021000a 100644 --- a/charts/must-gather/templates/clusterrbac.yaml +++ b/charts/must-gather/templates/clusterrbac.yaml @@ -58,8 +58,13 @@ rules: verbs: ["get", "list"] {{- end }} - apiGroups: [""] - resources: ["pods/exec"] # NOSONAR - exec is required to collect data from the running containers + resources: ["pods/exec"] # NOSONAR - required for data collection verbs: ["create"] + {{- if .Values.gather.withHeapDumps }} + - apiGroups: [""] + resources: ["pods/portforward"] + verbs: ["create"] + {{- end }} {{- if .Values.rbac.rules.sonataflow }} - apiGroups: ["sonataflow.org"] resources: ["sonataflowplatforms", "sonataflows", "sonataflowbuilds", "sonataflowclusterplatforms"] diff --git a/charts/must-gather/templates/rbac.yaml b/charts/must-gather/templates/rbac.yaml index 0233fd64..46766d5e 100644 --- a/charts/must-gather/templates/rbac.yaml +++ b/charts/must-gather/templates/rbac.yaml @@ -45,8 +45,13 @@ rules: verbs: ["get", "list"] {{- end }} - apiGroups: [""] - resources: ["pods/exec"] # NOSONAR - exec is required to collect data from the running containers + resources: ["pods/exec"] # NOSONAR - required for data collection verbs: ["create"] + {{- if .Values.gather.withHeapDumps }} + - apiGroups: [""] + resources: ["pods/portforward"] + verbs: ["create"] + {{- end }} {{- if .Values.rbac.rules.sonataflow }} - apiGroups: ["sonataflow.org"] resources: ["sonataflowplatforms", "sonataflows", "sonataflowbuilds"] From 34a9336aec50b91d182b54a78a8f8430de443e71 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Mon, 30 Mar 2026 09:17:22 +0200 Subject: [PATCH 34/34] Update charts/must-gather/Chart.yaml --- charts/must-gather/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/must-gather/Chart.yaml b/charts/must-gather/Chart.yaml index ae1997e4..eeee1300 100644 --- a/charts/must-gather/Chart.yaml +++ b/charts/must-gather/Chart.yaml @@ -11,7 +11,7 @@ description: | type: application # Application version - matches the must-gather tool version -appVersion: "1.0.0" +appVersion: "0.2.0" keywords: - rhdh