This is an automated email from the ASF dual-hosted git repository.

hgruszecki pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git


The following commit(s) were added to refs/heads/master by this push:
     new 08be9a637 feat(helm): add helm-docs, yamllint, and helmfmt for chart 
quality (#3070)
08be9a637 is described below

commit 08be9a637dbf22af4978ff1e08e9a6acfb27c4d2
Author: Aviraj Khare <[email protected]>
AuthorDate: Tue Apr 14 13:47:22 2026 +0530

    feat(helm): add helm-docs, yamllint, and helmfmt for chart quality (#3070)
---
 .github/actions/utils/setup-helm-tools/action.yml |  51 +++++-
 .github/workflows/_test.yml                       |   3 +
 .pre-commit-config.yaml                           |  24 +++
 helm/charts/iggy/.yamllint.yml                    |  65 +++++++
 helm/charts/iggy/README.md                        | 198 +++++++++++++++-------
 helm/charts/iggy/{README.md => README.md.gotmpl}  | 144 +++++++++-------
 helm/charts/iggy/templates/_helpers.tpl           |  42 ++---
 helm/charts/iggy/templates/deployment.yaml        | 120 ++++++-------
 helm/charts/iggy/templates/hpa.yaml               |  22 +--
 helm/charts/iggy/templates/iggy-pvc.yaml          |   6 +-
 helm/charts/iggy/templates/ingress.yaml           |  80 ++++-----
 helm/charts/iggy/templates/service.yaml           |   6 +-
 helm/charts/iggy/templates/servicemonitor.yaml    |   6 +-
 helm/charts/iggy/values.yaml                      | 156 ++++++++---------
 scripts/ci/markdownlint.sh                        |   7 +-
 scripts/ci/setup-helm-tools.sh                    |  24 ++-
 scripts/ci/test-helm.sh                           |  46 +++++
 17 files changed, 659 insertions(+), 341 deletions(-)

diff --git a/.github/actions/utils/setup-helm-tools/action.yml 
b/.github/actions/utils/setup-helm-tools/action.yml
index 971796613..7878dd308 100644
--- a/.github/actions/utils/setup-helm-tools/action.yml
+++ b/.github/actions/utils/setup-helm-tools/action.yml
@@ -16,12 +16,24 @@
 # under the License.
 
 name: setup-helm-tools
-description: Install pinned Helm and optionally kind for Helm chart CI jobs
+description: Install pinned Helm and optionally kind, helm-docs, and yamllint 
for Helm chart CI jobs
 inputs:
   install-kind:
     description: "Whether to install kind in addition to Helm"
     required: false
     default: "false"
+  install-helm-docs:
+    description: "Whether to install helm-docs for README generation"
+    required: false
+    default: "false"
+  install-yamllint:
+    description: "Whether to install yamllint for YAML linting"
+    required: false
+    default: "false"
+  install-helmfmt:
+    description: "Whether to install helmfmt for Helm template formatting"
+    required: false
+    default: "false"
   helm-version:
     description: "Helm release version"
     required: false
@@ -38,6 +50,18 @@ inputs:
     description: "SHA256 checksum for the kind binary"
     required: false
     default: "eb244cbafcc157dff60cf68693c14c9a75c4e6e6fedaf9cd71c58117cb93e3fa"
+  helm-docs-version:
+    description: "helm-docs release version"
+    required: false
+    default: "v1.14.2"
+  helm-docs-checksum:
+    description: "SHA256 checksum for the helm-docs tarball"
+    required: false
+    default: "a8cf72ada34fad93285ba2a452b38bdc5bd52cc9a571236244ec31022928d6cc"
+  helmfmt-checksum:
+    description: "SHA256 checksum for the helmfmt Linux x86_64 tarball"
+    required: false
+    default: "17e6de3266b7bfeb50f4e425736d3362c3c7a01bf9756710b1d350f9ced78fb2"
 
 runs:
   using: "composite"
@@ -51,17 +75,40 @@ runs:
           exit 1
         fi
 
-    - name: Install Helm and optional kind
+    - name: Install Helm and optional kind/helm-docs
       shell: bash
       env:
         HELM_VERSION: ${{ inputs.helm-version }}
         HELM_CHECKSUM: ${{ inputs.helm-checksum }}
         KIND_VERSION: ${{ inputs.kind-version }}
         KIND_CHECKSUM: ${{ inputs.kind-checksum }}
+        HELM_DOCS_VERSION: ${{ inputs.helm-docs-version }}
+        HELM_DOCS_CHECKSUM: ${{ inputs.helm-docs-checksum }}
       run: |
         set -euo pipefail
         args=()
         if [[ "${{ inputs.install-kind }}" == "true" ]]; then
           args+=(--install-kind)
         fi
+        if [[ "${{ inputs.install-helm-docs }}" == "true" ]]; then
+          args+=(--install-helm-docs)
+        fi
         scripts/ci/setup-helm-tools.sh "${args[@]}"
+
+    - name: Install yamllint
+      if: inputs.install-yamllint == 'true'
+      shell: bash
+      run: pip install "yamllint==1.38.0"
+
+    - name: Install helmfmt
+      if: inputs.install-helmfmt == 'true'
+      shell: bash
+      env:
+        HELMFMT_CHECKSUM: ${{ inputs.helmfmt-checksum }}
+      run: |
+        set -euo pipefail
+        HELMFMT_VERSION="v0.5.0"
+        wget -qO /tmp/helmfmt.tar.gz 
"https://github.com/digitalstudium/helmfmt/releases/download/${HELMFMT_VERSION}/helmfmt_Linux_x86_64.tar.gz";
+        echo "${HELMFMT_CHECKSUM}  /tmp/helmfmt.tar.gz" | sha256sum -c -
+        tar -xzf /tmp/helmfmt.tar.gz -C /tmp helmfmt
+        sudo install -m 0755 /tmp/helmfmt /usr/local/bin/helmfmt
diff --git a/.github/workflows/_test.yml b/.github/workflows/_test.yml
index 75a7b7c1d..5feb0e5ae 100644
--- a/.github/workflows/_test.yml
+++ b/.github/workflows/_test.yml
@@ -198,6 +198,9 @@ jobs:
         uses: ./.github/actions/utils/setup-helm-tools
         with:
           install-kind: ${{ inputs.task == 'smoke' }}
+          install-helm-docs: ${{ inputs.task == 'validate' }}
+          install-yamllint: ${{ inputs.task == 'validate' }}
+          install-helmfmt: ${{ inputs.task == 'validate' }}
 
       - name: Setup Helm smoke cluster
         if: inputs.component == 'helm' && inputs.task == 'smoke'
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index d47136dc2..3a7d20f66 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -268,3 +268,27 @@ repos:
         files: ^(foreign|examples)/csharp/.*\.cs$
         pass_filenames: false
         stages: [pre-push]
+
+  # Helm chart formatting and linting
+  - repo: local
+    hooks:
+      - id: helmfmt
+        name: helmfmt
+        entry: helmfmt
+        language: system
+        files: ^helm/charts/.*/templates/.*\.(yaml|tpl)$
+        pass_filenames: true
+
+      - id: yamllint-helm
+        name: yamllint (helm)
+        entry: yamllint -c helm/charts/iggy/.yamllint.yml
+        language: system
+        files: ^helm/charts/iggy/(values\.yaml|Chart\.yaml)$
+        pass_filenames: true
+
+      - id: helm-docs
+        name: helm-docs
+        entry: bash -c 'cd helm/charts/iggy && helm-docs'
+        language: system
+        files: ^helm/charts/iggy/(values\.yaml|README\.md\.gotmpl)$
+        pass_filenames: false
diff --git a/helm/charts/iggy/.yamllint.yml b/helm/charts/iggy/.yamllint.yml
new file mode 100644
index 000000000..b2bdc2858
--- /dev/null
+++ b/helm/charts/iggy/.yamllint.yml
@@ -0,0 +1,65 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# yamllint configuration for Helm charts
+# Note: templates/ contains Go templating which yamllint cannot parse,
+# so we only lint values.yaml and Chart.yaml
+
+yaml-files:
+  - '*.yaml'
+  - '*.yml'
+
+ignore: |
+  templates/
+
+rules:
+  braces:
+    min-spaces-inside: 0
+    max-spaces-inside: 1
+  brackets:
+    min-spaces-inside: 0
+    max-spaces-inside: 0
+  colons:
+    max-spaces-before: 0
+    max-spaces-after: 1
+  commas:
+    max-spaces-before: 0
+    min-spaces-after: 1
+    max-spaces-after: 1
+  comments:
+    require-starting-space: true
+    min-spaces-from-content: 1
+  document-end: disable
+  document-start: disable
+  empty-lines:
+    max: 2
+    max-start: 0
+    max-end: 1
+  indentation:
+    spaces: 2
+    indent-sequences: true
+  key-duplicates: enable
+  line-length:
+    max: 200
+    allow-non-breakable-words: true
+    allow-non-breakable-inline-mappings: true
+  new-line-at-end-of-file: enable
+  new-lines:
+    type: unix
+  trailing-spaces: enable
+  truthy:
+    allowed-values: ['true', 'false', 'yes', 'no']
diff --git a/helm/charts/iggy/README.md b/helm/charts/iggy/README.md
index 9082cb968..4aba962b1 100644
--- a/helm/charts/iggy/README.md
+++ b/helm/charts/iggy/README.md
@@ -1,6 +1,8 @@
 # iggy
 
-A Helm chart for [Apache Iggy](https://github.com/apache/iggy) server and 
web-ui.
+A Helm chart for Apache Iggy server and web-ui
+
+![Version: 
0.5.0](https://img.shields.io/badge/Version-0.5.0-informational?style=flat-square)
 ![Type: 
application](https://img.shields.io/badge/Type-application-informational?style=flat-square)
 ![AppVersion: 
0.7.0](https://img.shields.io/badge/AppVersion-0.7.0-informational?style=flat-square)
 
 ## Prerequisites
 
@@ -80,61 +82,6 @@ command line with `--set server.serviceMonitor.enabled=true`.
 helm uninstall iggy
 ```
 
-## Configuration
-
-### Server Configuration
-
-| Key | Type | Default | Description |
-| --- | ---- | ------- | ----------- |
-| `server.enabled` | bool | `true` | Enable the Iggy server deployment |
-| `server.replicaCount` | int | `1` | Number of server replicas |
-| `server.image.repository` | string | `"apache/iggy"` | Server image 
repository |
-| `server.image.tag` | string | `"0.7.0"` | Server image tag |
-| `server.ports.http` | int | `3000` | HTTP API port |
-| `server.ports.tcp` | int | `8090` | TCP protocol port |
-| `server.ports.quic` | int | `8080` | QUIC protocol port |
-
-### Persistence Configuration
-
-| Key | Type | Default | Description |
-| --- | ---- | ------- | ----------- |
-| `server.persistence.enabled` | bool | `false` | Enable persistence using PVC 
|
-| `server.persistence.size` | string | `"8Gi"` | PVC storage size |
-| `server.persistence.storageClass` | string | `""` | Storage class (empty = 
default) |
-| `server.persistence.accessMode` | string | `"ReadWriteOnce"` | PVC access 
mode |
-| `server.persistence.existingClaim` | string | `""` | Use existing PVC |
-
-### Security Configuration
-
-| Key | Type | Default | Description |
-| --- | ---- | ------- | ----------- |
-| `server.users.root.username` | string | `"iggy"` | Root user username |
-| `server.users.root.password` | string | `"changeit"` | Root user password |
-| `server.users.root.createSecret` | bool | `true` | Create secret for root 
user |
-| `server.users.root.existingSecret.name` | string | `""` | Use existing 
secret |
-| `securityContext.capabilities.add` | list | `["IPC_LOCK"]` | Server 
container capabilities (required for io_uring) |
-| `podSecurityContext.seccompProfile.type` | string | `"Unconfined"` | Server 
pod seccomp profile (required for io_uring) |
-
-### Monitoring Configuration
-
-| Key | Type | Default | Description |
-| --- | ---- | ------- | ----------- |
-| `server.serviceMonitor.enabled` | bool | `false` | Enable ServiceMonitor for 
Prometheus Operator |
-| `server.serviceMonitor.interval` | string | `"30s"` | Scrape interval |
-| `server.serviceMonitor.path` | string | `"/metrics"` | Metrics endpoint path 
|
-
-### Web UI Configuration
-
-| Key | Type | Default | Description |
-| --- | ---- | ------- | ----------- |
-| `ui.enabled` | bool | `true` | Enable the Web UI deployment |
-| `ui.replicaCount` | int | `1` | Number of UI replicas |
-| `ui.image.repository` | string | `"apache/iggy-web-ui"` | UI image 
repository |
-| `ui.ports.http` | int | `3050` | UI HTTP port |
-| `ui.server.endpoint` | string | `""` | Iggy server endpoint (auto-detected 
if empty) |
-| `ui.securityContext` | object | `{}` | UI container security context |
-| `ui.podSecurityContext` | object | `{}` | UI pod security context |
-
 ## Testing
 
 The chart CI paths are also available locally from the repository root.
@@ -285,13 +232,150 @@ server:
   ingress:
     enabled: true
     className: "<your-ingress-class>"
+    annotations: {}
     hosts:
       - host: iggy.example.com
         paths:
           - path: /
             pathType: Prefix
+    tls: []
+
+ui:
+  ingress:
+    enabled: true
+    className: "<your-ingress-class>"
+    annotations: {}
+    hosts:
+      - host: iggy-ui.example.com
+        paths:
+          - path: /
+            pathType: Prefix
+    tls: []
+```
+
+The chart is controller-neutral and works with any Ingress controller (nginx, 
Traefik, HAProxy, Contour, etc.).
+
+## Development
+
+### Formatting and Linting
+
+This chart uses automated tools to maintain code quality:
+
+| Tool | Purpose | Files |
+|------|---------|-------|
+| [helm-docs](https://github.com/norwoodj/helm-docs) | Auto-generate this 
README | `values.yaml` → `README.md` |
+| [yamllint](https://github.com/adrienverge/yamllint) | YAML formatting | 
`values.yaml`, `Chart.yaml` |
+| [helmfmt](https://github.com/digitalstudium/helmfmt) | Helm template 
formatting | `templates/*.yaml`, `*.tpl` |
+
+### Local Development
+
+```bash
+# Install tools (macOS)
+brew install norwoodj/tap/helm-docs
+pip install "yamllint==1.38.0"
+go install github.com/digitalstudium/helmfmt@latest
+
+# Format templates
+helmfmt helm/charts/iggy/
+
+# Lint YAML files
+yamllint -c helm/charts/iggy/.yamllint.yml helm/charts/iggy/
+
+# Regenerate README after changing values.yaml
+cd helm/charts/iggy && helm-docs
+
+# Run all validations
+scripts/ci/test-helm.sh validate
+```
+
+### Pre-commit Hooks
+
+These tools are integrated with pre-commit. Install hooks with:
+
+```bash
+pre-commit install
 ```
 
 ## Values
 
-See [values.yaml](values.yaml) for the full list of configurable values.
+| Key | Type | Default | Description |
+|-----|------|---------|-------------|
+| additionalLabels | object | `{}` | Additional labels for all resources |
+| autoscaling.enabled | bool | `false` | Enable horizontal pod autoscaling |
+| autoscaling.maxReplicas | int | `100` | Maximum replicas for autoscaling |
+| autoscaling.minReplicas | int | `1` | Minimum replicas for autoscaling |
+| autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU 
utilization for autoscaling |
+| fullnameOverride | string | `""` | Override full release name |
+| imagePullSecrets | list | `[]` | Image pull secrets for private registries |
+| nameOverride | string | `""` | Override chart name |
+| podAnnotations | object | `{}` | Pod annotations |
+| podSecurityContext | object | `{"seccompProfile":{"type":"Unconfined"}}` | 
Pod security context (server uses io_uring, requires unconfined seccomp) |
+| resources | object | `{}` | Resource limits and requests for server |
+| securityContext | object | `{"capabilities":{"add":["IPC_LOCK"]}}` | 
Container security context (server requires IPC_LOCK for io_uring) |
+| server | object | 
`{"affinity":{},"enabled":true,"env":[{"name":"RUST_LOG","value":"info"},{"name":"IGGY_HTTP_ADDRESS","value":"0.0.0.0:3000"},{"name":"IGGY_TCP_ADDRESS","value":"0.0.0.0:8090"},{"name":"IGGY_QUIC_ADDRESS","value":"0.0.0.0:8080"},{"name":"IGGY_WEBSOCKET_ADDRESS","value":"0.0.0.0:8092"}],"image":{"pullPolicy":"Always","repository":"apache/iggy","tag":"0.7.0"},"ingress":{"annotations":{},"className":"","enabled":false,"hosts":[{"host":"chart-example.local","paths":[{"path
 [...]
+| server.affinity | object | `{}` | Affinity rules for server pods |
+| server.enabled | bool | `true` | Enable the Iggy server deployment |
+| server.env | list | 
`[{"name":"RUST_LOG","value":"info"},{"name":"IGGY_HTTP_ADDRESS","value":"0.0.0.0:3000"},{"name":"IGGY_TCP_ADDRESS","value":"0.0.0.0:8090"},{"name":"IGGY_QUIC_ADDRESS","value":"0.0.0.0:8080"},{"name":"IGGY_WEBSOCKET_ADDRESS","value":"0.0.0.0:8092"}]`
 | Environment variables for the server container |
+| server.image.pullPolicy | string | `"Always"` | Image pull policy |
+| server.image.repository | string | `"apache/iggy"` | Server image repository 
|
+| server.image.tag | string | `"0.7.0"` | Server image tag (overrides chart 
appVersion) |
+| server.ingress.annotations | object | `{}` | Ingress annotations 
(controller-specific) |
+| server.ingress.className | string | `""` | Ingress class name 
(controller-neutral) |
+| server.ingress.enabled | bool | `false` | Enable ingress for the server |
+| server.ingress.hosts | list | 
`[{"host":"chart-example.local","paths":[{"path":"/","pathType":"ImplementationSpecific"}]}]`
 | Ingress hosts configuration |
+| server.ingress.tls | list | `[]` | Ingress TLS configuration |
+| server.nodeSelector | object | `{}` | Node selector for server pods |
+| server.persistence.accessMode | string | `"ReadWriteOnce"` | PVC access mode 
|
+| server.persistence.annotations | object | `{}` | PVC annotations |
+| server.persistence.enabled | bool | `false` | Enable persistence using PVC |
+| server.persistence.existingClaim | string | `""` | Use existing PVC 
(requires persistence.enabled: true) |
+| server.persistence.size | string | `"8Gi"` | PVC storage size |
+| server.persistence.storageClass | string | `""` | Storage class for PVC 
(empty uses default provisioner) |
+| server.ports.http | int | `3000` | HTTP API port |
+| server.ports.quic | int | `8080` | QUIC protocol port |
+| server.ports.tcp | int | `8090` | TCP protocol port |
+| server.replicaCount | int | `1` | Number of server replicas |
+| server.service.port | int | `3000` | Service port for the server |
+| server.service.type | string | `"ClusterIP"` | Service type for the server |
+| server.serviceMonitor.additionalLabels | object | `{}` | Additional labels 
for the ServiceMonitor |
+| server.serviceMonitor.enabled | bool | `false` | Enable ServiceMonitor for 
Prometheus Operator |
+| server.serviceMonitor.honorLabels | bool | `false` | Honor labels from the 
target |
+| server.serviceMonitor.interval | string | `"30s"` | Scrape interval 
(fallback to Prometheus default) |
+| server.serviceMonitor.namespace | string | `""` | Namespace to deploy the 
ServiceMonitor |
+| server.serviceMonitor.path | string | `"/metrics"` | Path to scrape metrics 
from |
+| server.serviceMonitor.scrapeTimeout | string | `"10s"` | Timeout for scrape 
metrics request |
+| server.tolerations | list | `[]` | Tolerations for server pods |
+| server.users.root.createSecret | bool | `true` | Create a secret for the 
root user credentials |
+| server.users.root.existingSecret.name | string | `""` | Name of existing 
secret for root credentials |
+| server.users.root.existingSecret.passwordKey | string | `"password"` | Key 
in secret for password |
+| server.users.root.existingSecret.usernameKey | string | `"username"` | Key 
in secret for username |
+| server.users.root.password | string | `"changeit"` | Root password |
+| server.users.root.username | string | `"iggy"` | Root username |
+| serviceAccount.annotations | object | `{}` | Service account annotations |
+| serviceAccount.create | bool | `true` | Create a service account |
+| serviceAccount.name | string | `""` | Service account name (generated if not 
set) |
+| ui | object | 
`{"affinity":{},"enabled":true,"env":{},"image":{"pullPolicy":"Always","repository":"apache/iggy-web-ui","tag":"edge"},"ingress":{"annotations":{},"className":"","enabled":false,"hosts":[{"host":"chart-example.local","paths":[{"path":"/","pathType":"ImplementationSpecific"}]}],"tls":[]},"nodeSelector":{},"podSecurityContext":{},"ports":{"http":3050},"replicaCount":1,"resources":{},"securityContext":{},"server":{"endpoint":""},"service":{"port":3050,"type":"ClusterIP"},"to
 [...]
+| ui.affinity | object | `{}` | Affinity rules for UI pods |
+| ui.enabled | bool | `true` | Enable the web UI deployment |
+| ui.env | object | `{}` | Extra environment variables for UI container |
+| ui.image.pullPolicy | string | `"Always"` | UI image pull policy |
+| ui.image.repository | string | `"apache/iggy-web-ui"` | UI image repository |
+| ui.image.tag | string | `"edge"` | UI image tag (overrides chart appVersion) 
|
+| ui.ingress.annotations | object | `{}` | Ingress annotations 
(controller-specific) |
+| ui.ingress.className | string | `""` | Ingress class name 
(controller-neutral) |
+| ui.ingress.enabled | bool | `false` | Enable ingress for the UI |
+| ui.ingress.hosts | list | 
`[{"host":"chart-example.local","paths":[{"path":"/","pathType":"ImplementationSpecific"}]}]`
 | Ingress hosts configuration |
+| ui.ingress.tls | list | `[]` | Ingress TLS configuration |
+| ui.nodeSelector | object | `{}` | Node selector for UI pods |
+| ui.podSecurityContext | object | `{}` | Pod security context for UI pods |
+| ui.ports.http | int | `3050` | HTTP port for web UI |
+| ui.replicaCount | int | `1` | Number of UI replicas |
+| ui.resources | object | `{}` | Resource limits and requests for UI |
+| ui.securityContext | object | `{}` | Container security context for UI |
+| ui.server.endpoint | string | `""` | Iggy server endpoint (blank uses 
service URL) |
+| ui.service.port | int | `3050` | Service port for the UI |
+| ui.service.type | string | `"ClusterIP"` | Service type for the UI |
+| ui.tolerations | list | `[]` | Tolerations for UI pods |
+
+----------------------------------------------
+Autogenerated from chart metadata using [helm-docs 
v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2)
diff --git a/helm/charts/iggy/README.md b/helm/charts/iggy/README.md.gotmpl
similarity index 71%
copy from helm/charts/iggy/README.md
copy to helm/charts/iggy/README.md.gotmpl
index 9082cb968..a30365fe6 100644
--- a/helm/charts/iggy/README.md
+++ b/helm/charts/iggy/README.md.gotmpl
@@ -1,6 +1,26 @@
-# iggy
-
-A Helm chart for [Apache Iggy](https://github.com/apache/iggy) server and 
web-ui.
+{{- /*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/ -}}
+# {{ template "chart.name" . }}
+
+{{ template "chart.description" . }}
+
+{{ template "chart.versionBadge" . }}{{ template "chart.typeBadge" . }}{{ 
template "chart.appVersionBadge" . }}
 
 ## Prerequisites
 
@@ -80,61 +100,6 @@ command line with `--set 
server.serviceMonitor.enabled=true`.
 helm uninstall iggy
 ```
 
-## Configuration
-
-### Server Configuration
-
-| Key | Type | Default | Description |
-| --- | ---- | ------- | ----------- |
-| `server.enabled` | bool | `true` | Enable the Iggy server deployment |
-| `server.replicaCount` | int | `1` | Number of server replicas |
-| `server.image.repository` | string | `"apache/iggy"` | Server image 
repository |
-| `server.image.tag` | string | `"0.7.0"` | Server image tag |
-| `server.ports.http` | int | `3000` | HTTP API port |
-| `server.ports.tcp` | int | `8090` | TCP protocol port |
-| `server.ports.quic` | int | `8080` | QUIC protocol port |
-
-### Persistence Configuration
-
-| Key | Type | Default | Description |
-| --- | ---- | ------- | ----------- |
-| `server.persistence.enabled` | bool | `false` | Enable persistence using PVC 
|
-| `server.persistence.size` | string | `"8Gi"` | PVC storage size |
-| `server.persistence.storageClass` | string | `""` | Storage class (empty = 
default) |
-| `server.persistence.accessMode` | string | `"ReadWriteOnce"` | PVC access 
mode |
-| `server.persistence.existingClaim` | string | `""` | Use existing PVC |
-
-### Security Configuration
-
-| Key | Type | Default | Description |
-| --- | ---- | ------- | ----------- |
-| `server.users.root.username` | string | `"iggy"` | Root user username |
-| `server.users.root.password` | string | `"changeit"` | Root user password |
-| `server.users.root.createSecret` | bool | `true` | Create secret for root 
user |
-| `server.users.root.existingSecret.name` | string | `""` | Use existing 
secret |
-| `securityContext.capabilities.add` | list | `["IPC_LOCK"]` | Server 
container capabilities (required for io_uring) |
-| `podSecurityContext.seccompProfile.type` | string | `"Unconfined"` | Server 
pod seccomp profile (required for io_uring) |
-
-### Monitoring Configuration
-
-| Key | Type | Default | Description |
-| --- | ---- | ------- | ----------- |
-| `server.serviceMonitor.enabled` | bool | `false` | Enable ServiceMonitor for 
Prometheus Operator |
-| `server.serviceMonitor.interval` | string | `"30s"` | Scrape interval |
-| `server.serviceMonitor.path` | string | `"/metrics"` | Metrics endpoint path 
|
-
-### Web UI Configuration
-
-| Key | Type | Default | Description |
-| --- | ---- | ------- | ----------- |
-| `ui.enabled` | bool | `true` | Enable the Web UI deployment |
-| `ui.replicaCount` | int | `1` | Number of UI replicas |
-| `ui.image.repository` | string | `"apache/iggy-web-ui"` | UI image 
repository |
-| `ui.ports.http` | int | `3050` | UI HTTP port |
-| `ui.server.endpoint` | string | `""` | Iggy server endpoint (auto-detected 
if empty) |
-| `ui.securityContext` | object | `{}` | UI container security context |
-| `ui.podSecurityContext` | object | `{}` | UI pod security context |
-
 ## Testing
 
 The chart CI paths are also available locally from the repository root.
@@ -285,13 +250,74 @@ server:
   ingress:
     enabled: true
     className: "<your-ingress-class>"
+    annotations: {}
     hosts:
       - host: iggy.example.com
         paths:
           - path: /
             pathType: Prefix
+    tls: []
+
+ui:
+  ingress:
+    enabled: true
+    className: "<your-ingress-class>"
+    annotations: {}
+    hosts:
+      - host: iggy-ui.example.com
+        paths:
+          - path: /
+            pathType: Prefix
+    tls: []
+```
+
+The chart is controller-neutral and works with any Ingress controller (nginx, 
Traefik, HAProxy, Contour, etc.).
+
+{{ template "chart.requirementsSection" . }}
+
+## Development
+
+### Formatting and Linting
+
+This chart uses automated tools to maintain code quality:
+
+| Tool | Purpose | Files |
+|------|---------|-------|
+| [helm-docs](https://github.com/norwoodj/helm-docs) | Auto-generate this 
README | `values.yaml` → `README.md` |
+| [yamllint](https://github.com/adrienverge/yamllint) | YAML formatting | 
`values.yaml`, `Chart.yaml` |
+| [helmfmt](https://github.com/digitalstudium/helmfmt) | Helm template 
formatting | `templates/*.yaml`, `*.tpl` |
+
+### Local Development
+
+```bash
+# Install tools (macOS)
+brew install norwoodj/tap/helm-docs
+pip install "yamllint==1.38.0"
+go install github.com/digitalstudium/helmfmt@latest
+
+# Format templates
+helmfmt helm/charts/iggy/
+
+# Lint YAML files
+yamllint -c helm/charts/iggy/.yamllint.yml helm/charts/iggy/
+
+# Regenerate README after changing values.yaml
+cd helm/charts/iggy && helm-docs
+
+# Run all validations
+scripts/ci/test-helm.sh validate
+```
+
+### Pre-commit Hooks
+
+These tools are integrated with pre-commit. Install hooks with:
+
+```bash
+pre-commit install
 ```
 
 ## Values
 
-See [values.yaml](values.yaml) for the full list of configurable values.
+{{ template "chart.valuesTable" . }}
+
+{{ template "helm-docs.versionFooter" . }}
diff --git a/helm/charts/iggy/templates/_helpers.tpl 
b/helm/charts/iggy/templates/_helpers.tpl
index 9f6b2fdcc..92f69780e 100644
--- a/helm/charts/iggy/templates/_helpers.tpl
+++ b/helm/charts/iggy/templates/_helpers.tpl
@@ -11,23 +11,23 @@ We truncate at 63 chars because some Kubernetes name fields 
are limited to this
 If release name contains chart name it will be used as a full name.
 */}}
 {{- define "iggy.fullname" -}}
-{{- if .Values.fullnameOverride }}
+  {{- if .Values.fullnameOverride }}
 {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
-{{- else }}
-{{- $name := default .Chart.Name .Values.nameOverride }}
-{{- if contains $name .Release.Name }}
+  {{- 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 }}
+    {{- 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 "iggy.chart" -}}
-{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | 
trimSuffix "-" }}
+  {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | 
trimSuffix "-" }}
 {{- end }}
 
 {{/*
@@ -36,15 +36,15 @@ Common labels
 {{- define "iggy.labels" -}}
 helm.sh/chart: {{ include "iggy.chart" . }}
 {{ include "iggy.selectorLabels" . }}
-{{- if .Chart.AppVersion }}
+  {{- if .Chart.AppVersion }}
 app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
-{{- end }}
+  {{- end }}
 app.kubernetes.io/component: server
 app.kubernetes.io/managed-by: {{ .Release.Service }}
 app.kubernetes.io/part-of: iggy-server
-{{- if .Values.additionalLabels }}
+  {{- if .Values.additionalLabels }}
 {{ toYaml .Values.additionalLabels }}
-{{- end }}
+  {{- end }}
 {{- end }}
 
 {{/*
@@ -59,7 +59,7 @@ app.kubernetes.io/instance: {{ .Release.Name }}
 Create chart name and version as used by the chart label.
 */}}
 {{- define "iggy-ui.chart" -}}
-{{- printf "%s-ui-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 
| trimSuffix "-" }}
+  {{- printf "%s-ui-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 
63 | trimSuffix "-" }}
 {{- end }}
 
 {{/*
@@ -68,15 +68,15 @@ Common labels
 {{- define "iggy-ui.labels" -}}
 helm.sh/chart: {{ include "iggy-ui.chart" . }}
 {{ include "iggy-ui.selectorLabels" . }}
-{{- if .Chart.AppVersion }}
+  {{- if .Chart.AppVersion }}
 app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
-{{- end }}
+  {{- end }}
 app.kubernetes.io/component: server
 app.kubernetes.io/managed-by: {{ .Release.Service }}
 app.kubernetes.io/part-of: iggy-server
-{{- if .Values.additionalLabels }}
+  {{- if .Values.additionalLabels }}
 {{ toYaml .Values.additionalLabels }}
-{{- end }}
+  {{- end }}
 {{- end }}
 
 {{/*
@@ -91,9 +91,9 @@ app.kubernetes.io/instance: {{ .Release.Name }}-ui
 Create the name of the service account to use
 */}}
 {{- define "iggy.serviceAccountName" -}}
-{{- if .Values.serviceAccount.create }}
+  {{- if .Values.serviceAccount.create }}
 {{- default (include "iggy.fullname" .) .Values.serviceAccount.name }}
-{{- else }}
+  {{- else }}
 {{- default "default" .Values.serviceAccount.name }}
-{{- end }}
+  {{- end }}
 {{- end }}
diff --git a/helm/charts/iggy/templates/deployment.yaml 
b/helm/charts/iggy/templates/deployment.yaml
index d669d6aba..c07651d17 100644
--- a/helm/charts/iggy/templates/deployment.yaml
+++ b/helm/charts/iggy/templates/deployment.yaml
@@ -15,43 +15,43 @@
 # specific language governing permissions and limitations
 # under the License.
 {{ if .Values.server.enabled }}
-{{- if hasKey .Values.server "podSecurityContext" }}
-{{- fail "server.podSecurityContext has been moved to podSecurityContext (root 
level). Please update your values." }}
-{{- end }}
+  {{- if hasKey .Values.server "podSecurityContext" }}
+    {{- fail "server.podSecurityContext has been moved to podSecurityContext 
(root level). Please update your values." }}
+  {{- end }}
 ---
 apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: {{ include "iggy.fullname" . }}
   labels:
-    {{ include "iggy.labels" . | nindent 4 }}
+    {{- include "iggy.labels" . | nindent 4 }}
 spec:
   {{- if not .Values.autoscaling.enabled }}
   replicas: {{ .Values.server.replicaCount }}
   {{- end }}
   selector:
     matchLabels:
-      {{ include "iggy.selectorLabels" . | nindent 6 }}
+      {{- include "iggy.selectorLabels" . | nindent 6 }}
   template:
     metadata:
-      {{- with .Values.podAnnotations }}
+  {{- with .Values.podAnnotations }}
       annotations:
-        {{ toYaml . | nindent 8 }}
-      {{- end }}
+        {{- toYaml . | nindent 8 }}
+  {{- end }}
       labels:
-        {{ include "iggy.labels" . | nindent 8 }}
+        {{- include "iggy.labels" . | nindent 8 }}
     spec:
-      {{- with .Values.imagePullSecrets }}
+  {{- with .Values.imagePullSecrets }}
       imagePullSecrets:
-        {{ toYaml . | nindent 8 }}
-      {{- end }}
+        {{- toYaml . | nindent 8 }}
+  {{- end }}
       serviceAccountName: {{ include "iggy.serviceAccountName" . }}
       securityContext:
-        {{ toYaml .Values.podSecurityContext | nindent 8 }}
+        {{- toYaml .Values.podSecurityContext | nindent 8 }}
       containers:
         - name: {{ .Chart.Name }}
           securityContext:
-            {{ toYaml .Values.securityContext | nindent 12 }}
+            {{- toYaml .Values.securityContext | nindent 12 }}
           image: "{{ .Values.server.image.repository }}:{{ 
.Values.server.image.tag | default .Chart.AppVersion }}"
           imagePullPolicy: {{ .Values.server.image.pullPolicy }}
           ports:
@@ -65,7 +65,7 @@ spec:
               containerPort: {{ .Values.server.ports.quic }}
               protocol: TCP
           env:
-            {{ if .Values.server.users.root.existingSecret.name }}
+  {{- if .Values.server.users.root.existingSecret.name }}
             - name: IGGY_ROOT_USERNAME
               valueFrom:
                 secretKeyRef:
@@ -76,7 +76,7 @@ spec:
                 secretKeyRef:
                   name: {{ .Values.server.users.root.existingSecret.name }}
                   key: {{ .Values.server.users.root.existingSecret.passwordKey 
}}
-            {{ else }}{{ if .Values.server.users.root.createSecret }}
+  {{- else }}{{- if .Values.server.users.root.createSecret }}
             - name: IGGY_ROOT_USERNAME
               valueFrom:
                 secretKeyRef:
@@ -87,13 +87,13 @@ spec:
                 secretKeyRef:
                   name: {{ include "iggy.fullname" . }}-root-credentials
                   key: password
-            {{- end }}{{- end}}
-            {{ if .Values.server.env }}
-            {{- range .Values.server.env }}
+  {{- end }}{{- end}}
+  {{- if .Values.server.env }}
+    {{- range .Values.server.env }}
             - name: {{ .name }}
               value: {{ .value | quote }}
-            {{- end }}
-            {{ end }}
+    {{- end }}
+  {{- end }}
           volumeMounts:
             - name: iggy-server-data
               mountPath: /app/local_data
@@ -115,26 +115,26 @@ spec:
               port: http
           resources:
             {{- toYaml .Values.resources | nindent 12 }}
-      {{- with .Values.server.nodeSelector }}
+  {{- with .Values.server.nodeSelector }}
       nodeSelector:
         {{- toYaml . | nindent 8 }}
-      {{- end }}
-      {{- with .Values.server.affinity }}
+  {{- end }}
+  {{- with .Values.server.affinity }}
       affinity:
         {{- toYaml . | nindent 8 }}
-      {{- end }}
-      {{- with .Values.server.tolerations }}
+  {{- end }}
+  {{- with .Values.server.tolerations }}
       tolerations:
         {{- toYaml . | nindent 8 }}
-      {{- end }}
+  {{- end }}
       volumes:
         - name: iggy-server-data
-          {{- if .Values.server.persistence.enabled }}
+  {{- if .Values.server.persistence.enabled }}
           persistentVolumeClaim:
             claimName: {{ if .Values.server.persistence.existingClaim }}{{ 
.Values.server.persistence.existingClaim }}{{- else }}{{ template 
"iggy.fullname" . }}{{- end }}
-          {{- else }}
+  {{- else }}
           emptyDir: {}
-          {{- end }}
+  {{- end }}
 {{- end }}
 {{ if .Values.ui.enabled }}
 ---
@@ -143,36 +143,36 @@ kind: Deployment
 metadata:
   name: {{ include "iggy.fullname" . }}-ui
   labels:
-    {{ include "iggy-ui.labels" . | nindent 4 }}
+    {{- include "iggy-ui.labels" . | nindent 4 }}
 spec:
   replicas: {{ .Values.ui.replicaCount }}
   selector:
     matchLabels:
-      {{ include "iggy-ui.selectorLabels" . | nindent 6 }}
+      {{- include "iggy-ui.selectorLabels" . | nindent 6 }}
   template:
     metadata:
-      {{- with .Values.podAnnotations }}
+  {{- with .Values.podAnnotations }}
       annotations:
-        {{ toYaml . | nindent 8 }}
-      {{- end }}
+        {{- toYaml . | nindent 8 }}
+  {{- end }}
       labels:
-        {{ include "iggy-ui.labels" . | nindent 8 }}-ui
+        {{- include "iggy-ui.labels" . | nindent 8 }}-ui
     spec:
-      {{- with .Values.imagePullSecrets }}
+  {{- with .Values.imagePullSecrets }}
       imagePullSecrets:
-        {{ toYaml . | nindent 8 }}
-      {{- end }}
+        {{- toYaml . | nindent 8 }}
+  {{- end }}
       serviceAccountName: {{ include "iggy.serviceAccountName" . }}
-      {{- with .Values.ui.podSecurityContext }}
+  {{- with .Values.ui.podSecurityContext }}
       securityContext:
-        {{ toYaml . | nindent 8 }}
-      {{- end }}
+        {{- toYaml . | nindent 8 }}
+  {{- end }}
       containers:
         - name: {{ .Chart.Name }}
-          {{- with .Values.ui.securityContext }}
+  {{- with .Values.ui.securityContext }}
           securityContext:
-            {{ toYaml . | nindent 12 }}
-          {{- end }}
+            {{- toYaml . | nindent 12 }}
+  {{- end }}
           image: "{{ .Values.ui.image.repository }}:{{ .Values.ui.image.tag | 
default .Chart.AppVersion }}"
           imagePullPolicy: {{ .Values.ui.image.pullPolicy }}
           ports:
@@ -188,26 +188,26 @@ spec:
               path: /healthz
               port: http
           resources:
-            {{ toYaml .Values.ui.resources | nindent 12 }}
+            {{- toYaml .Values.ui.resources | nindent 12 }}
           env:
             - name: PUBLIC_IGGY_API_URL
-              {{- if .Values.ui.server.endpoint }}
+  {{- if .Values.ui.server.endpoint }}
               value: {{ .Values.ui.server.endpoint }}
-              {{- else }}
+  {{- else }}
               value: http://{{ include "iggy.fullname" . }}:{{ 
.Values.server.ports.http }}{{- end }}
-          {{- with .Values.ui.env }}
+    {{- with .Values.ui.env }}
           {{ . | toYaml | nindent 10 }}
-          {{- end }}
-      {{- with .Values.ui.nodeSelector }}
+    {{- end }}
+    {{- with .Values.ui.nodeSelector }}
       nodeSelector:
-        {{ toYaml . | nindent 8 }}
-      {{- end }}
-      {{- with .Values.ui.affinity }}
+        {{- toYaml . | nindent 8 }}
+    {{- end }}
+    {{- with .Values.ui.affinity }}
       affinity:
-        {{ toYaml . | nindent 8 }}
-      {{- end }}
-      {{- with .Values.ui.tolerations }}
+        {{- toYaml . | nindent 8 }}
+    {{- end }}
+    {{- with .Values.ui.tolerations }}
       tolerations:
-        {{ toYaml . | nindent 8 }}
-      {{- end }}
-{{- end }}
+        {{- toYaml . | nindent 8 }}
+    {{- end }}
+  {{- end }}
diff --git a/helm/charts/iggy/templates/hpa.yaml 
b/helm/charts/iggy/templates/hpa.yaml
index 5d9f635d9..51612fdc0 100644
--- a/helm/charts/iggy/templates/hpa.yaml
+++ b/helm/charts/iggy/templates/hpa.yaml
@@ -15,11 +15,11 @@
 # specific language governing permissions and limitations
 # under the License.
 {{ if .Values.autoscaling.enabled -}}
-{{- if semverCompare ">=1.23-0" .Capabilities.KubeVersion.GitVersion -}}
+  {{- if semverCompare ">=1.23-0" .Capabilities.KubeVersion.GitVersion -}}
 apiVersion: autoscaling/v2
-{{- else -}}
+  {{- else -}}
 apiVersion: autoscaling/v2beta2
-{{- end }}
+  {{- end }}
 kind: HorizontalPodAutoscaler
 metadata:
   name: {{ include "iggy.fullname" . }}
@@ -33,28 +33,28 @@ spec:
   minReplicas: {{ .Values.autoscaling.minReplicas }}
   maxReplicas: {{ .Values.autoscaling.maxReplicas }}
   metrics:
-    {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
+  {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
     - type: Resource
       resource:
         name: cpu
-        {{- if semverCompare ">=1.23-0" .Capabilities.KubeVersion.GitVersion }}
+    {{- if semverCompare ">=1.23-0" .Capabilities.KubeVersion.GitVersion }}
         target:
           type: Utilization
           averageUtilization: {{ 
.Values.autoscaling.targetCPUUtilizationPercentage }}
-        {{- else }}
+    {{- else }}
         targetAverageUtilization: {{ 
.Values.autoscaling.targetCPUUtilizationPercentage }}
-        {{- end }}
     {{- end }}
-    {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
+  {{- end }}
+  {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
     - type: Resource
       resource:
         name: memory
-        {{- if semverCompare ">=1.23-0" .Capabilities.KubeVersion.GitVersion }}
+    {{- if semverCompare ">=1.23-0" .Capabilities.KubeVersion.GitVersion }}
         target:
           type: Utilization
           averageUtilization: {{ 
.Values.autoscaling.targetMemoryUtilizationPercentage }}
-        {{- else }}
+    {{- else }}
         targetAverageUtilization: {{ 
.Values.autoscaling.targetMemoryUtilizationPercentage }}
-        {{- end }}
     {{- end }}
+  {{- end }}
 {{- end }}
diff --git a/helm/charts/iggy/templates/iggy-pvc.yaml 
b/helm/charts/iggy/templates/iggy-pvc.yaml
index c1a5e8cb5..64fbc20dc 100644
--- a/helm/charts/iggy/templates/iggy-pvc.yaml
+++ b/helm/charts/iggy/templates/iggy-pvc.yaml
@@ -34,10 +34,10 @@ spec:
     requests:
       storage: {{ .Values.server.persistence.size | quote }}
   {{- with .Values.server.persistence.storageClass }}
-  {{- if (eq "-" .) }}
+    {{- if (eq "-" .) }}
   storageClassName: ""
-  {{- else }}
+    {{- else }}
   storageClassName: "{{ . }}"
-  {{- end }}
+    {{- end }}
   {{- end }}
 {{- end }}
diff --git a/helm/charts/iggy/templates/ingress.yaml 
b/helm/charts/iggy/templates/ingress.yaml
index b0339e574..5996ca419 100644
--- a/helm/charts/iggy/templates/ingress.yaml
+++ b/helm/charts/iggy/templates/ingress.yaml
@@ -15,20 +15,20 @@
 # specific language governing permissions and limitations
 # under the License.
 {{ if .Values.server.ingress.enabled -}}
-{{ $fullName := include "iggy.fullname" . -}}
-{{ $svcPort := .Values.server.service.port -}}
-{{- if and .Values.server.ingress.className (not (semverCompare ">=1.18-0" 
.Capabilities.KubeVersion.GitVersion)) }}
-  {{- if not (hasKey .Values.server.ingress.annotations 
"kubernetes.io/ingress.class") }}
-  {{- $_ := set .Values.server.ingress.annotations 
"kubernetes.io/ingress.class" .Values.server.ingress.className}}
+  {{ $fullName := include "iggy.fullname" . -}}
+  {{ $svcPort := .Values.server.service.port -}}
+  {{- if and .Values.server.ingress.className (not (semverCompare ">=1.18-0" 
.Capabilities.KubeVersion.GitVersion)) }}
+    {{- if not (hasKey .Values.server.ingress.annotations 
"kubernetes.io/ingress.class") }}
+      {{- $_ := set .Values.server.ingress.annotations 
"kubernetes.io/ingress.class" .Values.server.ingress.className}}
+    {{- end }}
   {{- end }}
-{{- end }}
-{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
+  {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
 apiVersion: networking.k8s.io/v1
-{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
+  {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
 apiVersion: networking.k8s.io/v1beta1
-{{- else -}}
+  {{- else -}}
 apiVersion: extensions/v1beta1
-{{- end }}
+  {{- end }}
 kind: Ingress
 metadata:
   name: {{ $fullName }}
@@ -46,51 +46,51 @@ spec:
   tls:
     {{- range .Values.server.ingress.tls }}
     - hosts:
-        {{- range .hosts }}
+      {{- range .hosts }}
         - {{ . | quote }}
-        {{- end }}
+      {{- end }}
       secretName: {{ .secretName }}
     {{- end }}
   {{- end }}
   rules:
-    {{- range .Values.server.ingress.hosts }}
+  {{- range .Values.server.ingress.hosts }}
     - host: {{ .host | quote }}
       http:
         paths:
-          {{- range .paths }}
+    {{- range .paths }}
           - path: {{ .path }}
-            {{- if and .pathType (semverCompare ">=1.18-0" 
$.Capabilities.KubeVersion.GitVersion) }}
+      {{- if and .pathType (semverCompare ">=1.18-0" 
$.Capabilities.KubeVersion.GitVersion) }}
             pathType: {{ .pathType }}
-            {{- end }}
+      {{- end }}
             backend:
-              {{- if semverCompare ">=1.19-0" 
$.Capabilities.KubeVersion.GitVersion }}
+      {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
               service:
                 name: {{ $fullName }}
                 port:
                   number: {{ $svcPort }}
-              {{- else }}
+      {{- else }}
               serviceName: {{ $fullName }}
               servicePort: {{ $svcPort }}
-              {{- end }}
-          {{- end }}
+      {{- end }}
     {{- end }}
+  {{- end }}
 {{- end }}
 {{ if .Values.ui.ingress.enabled -}}
 ---
-{{ $fullName := include "iggy.fullname" . -}}
-{{ $svcPort := .Values.ui.service.port -}}
-{{- if and .Values.ui.ingress.className (not (semverCompare ">=1.18-0" 
.Capabilities.KubeVersion.GitVersion)) }}
-  {{- if not (hasKey .Values.ui.ingress.annotations 
"kubernetes.io/ingress.class") }}
-  {{- $_ := set .Values.ui.ingress.annotations "kubernetes.io/ingress.class" 
.Values.ui.ingress.className}}
+  {{ $fullName := include "iggy.fullname" . -}}
+  {{ $svcPort := .Values.ui.service.port -}}
+  {{- if and .Values.ui.ingress.className (not (semverCompare ">=1.18-0" 
.Capabilities.KubeVersion.GitVersion)) }}
+    {{- if not (hasKey .Values.ui.ingress.annotations 
"kubernetes.io/ingress.class") }}
+      {{- $_ := set .Values.ui.ingress.annotations 
"kubernetes.io/ingress.class" .Values.ui.ingress.className}}
+    {{- end }}
   {{- end }}
-{{- end }}
-{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
+  {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
 apiVersion: networking.k8s.io/v1
-{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
+  {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
 apiVersion: networking.k8s.io/v1beta1
-{{- else -}}
+  {{- else -}}
 apiVersion: extensions/v1beta1
-{{- end }}
+  {{- end }}
 kind: Ingress
 metadata:
   name: {{ $fullName }}-ui
@@ -108,32 +108,32 @@ spec:
   tls:
     {{- range .Values.ui.ingress.tls }}
     - hosts:
-        {{- range .hosts }}
+      {{- range .hosts }}
         - {{ . | quote }}
-        {{- end }}
+      {{- end }}
       secretName: {{ .secretName }}
     {{- end }}
   {{- end }}
   rules:
-    {{- range .Values.ui.ingress.hosts }}
+  {{- range .Values.ui.ingress.hosts }}
     - host: {{ .host | quote }}
       http:
         paths:
-          {{- range .paths }}
+    {{- range .paths }}
           - path: {{ .path }}
-            {{- if and .pathType (semverCompare ">=1.18-0" 
$.Capabilities.KubeVersion.GitVersion) }}
+      {{- if and .pathType (semverCompare ">=1.18-0" 
$.Capabilities.KubeVersion.GitVersion) }}
             pathType: {{ .pathType }}
-            {{- end }}
+      {{- end }}
             backend:
-              {{- if semverCompare ">=1.19-0" 
$.Capabilities.KubeVersion.GitVersion }}
+      {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
               service:
                 name: {{ $fullName }}-ui
                 port:
                   number: {{ $svcPort }}
-              {{- else }}
+      {{- else }}
               serviceName: {{ $fullName }}-ui
               servicePort: {{ $svcPort }}
-              {{- end }}
-          {{- end }}
+      {{- end }}
     {{- end }}
+  {{- end }}
 {{- end }}
diff --git a/helm/charts/iggy/templates/service.yaml 
b/helm/charts/iggy/templates/service.yaml
index 98ef767fa..86464d283 100644
--- a/helm/charts/iggy/templates/service.yaml
+++ b/helm/charts/iggy/templates/service.yaml
@@ -46,7 +46,7 @@ kind: Service
 metadata:
   name: {{ include "iggy.fullname" . }}-ui
   labels:
-    {{ include "iggy-ui.labels" . | nindent 4 }}
+    {{- include "iggy-ui.labels" . | nindent 4 }}
 spec:
   type: {{ .Values.ui.service.type }}
   ports:
@@ -55,5 +55,5 @@ spec:
       targetPort: http
       protocol: TCP
   selector:
-    {{ include "iggy-ui.selectorLabels" . | nindent 4 }}
-{{- end }}
\ No newline at end of file
+    {{- include "iggy-ui.selectorLabels" . | nindent 4 }}
+{{- end }}
diff --git a/helm/charts/iggy/templates/servicemonitor.yaml 
b/helm/charts/iggy/templates/servicemonitor.yaml
index 5bd623f86..ffff628b0 100644
--- a/helm/charts/iggy/templates/servicemonitor.yaml
+++ b/helm/charts/iggy/templates/servicemonitor.yaml
@@ -22,9 +22,9 @@ kind: ServiceMonitor
 metadata:
   labels:
     {{- include "iggy.labels" . | nindent 4 }}
-    {{- if .Values.server.serviceMonitor.additionalLabels }}
-    {{ toYaml .Values.server.serviceMonitor.additionalLabels }}
-    {{- end }}
+  {{- if .Values.server.serviceMonitor.additionalLabels }}
+    {{- toYaml .Values.server.serviceMonitor.additionalLabels | nindent 4 }}
+  {{- end }}
   name: {{ template "iggy.fullname" .  }}
   namespace: {{ .Release.Namespace }}
 spec:
diff --git a/helm/charts/iggy/values.yaml b/helm/charts/iggy/values.yaml
index a7316942d..0c8e019f5 100644
--- a/helm/charts/iggy/values.yaml
+++ b/helm/charts/iggy/values.yaml
@@ -15,78 +15,80 @@
 # specific language governing permissions and limitations
 # under the License.
 
----
-# Iggy server configuration
+# -- Iggy server configuration
 server:
+  # -- Enable the Iggy server deployment
   enabled: true
+  # -- Number of server replicas
   replicaCount: 1
   image:
+    # -- Server image repository
     repository: apache/iggy
+    # -- Image pull policy
     pullPolicy: Always
-    # Overrides the image tag whose default is the chart appVersion.
+    # -- Server image tag (overrides chart appVersion)
     tag: "0.7.0"
   ports:
+    # -- HTTP API port
     http: 3000
+    # -- QUIC protocol port
     quic: 8080
+    # -- TCP protocol port
     tcp: 8090
 
   service:
+    # -- Service type for the server
     type: ClusterIP
+    # -- Service port for the server
     port: 3000
 
   serviceMonitor:
-    # -- Enable this if you're using [Prometheus 
Operator](https://github.com/coreos/prometheus-operator)
+    # -- Enable ServiceMonitor for Prometheus Operator
     enabled: false
     # -- Namespace to deploy the ServiceMonitor
     namespace: ""
-    # -- Fallback to the prometheus default unless specified
+    # -- Scrape interval (fallback to Prometheus default)
     interval: 30s
-    # -- Add custom labels to the ServiceMonitor resource
+    # -- Additional labels for the ServiceMonitor
     additionalLabels: {}
+    # -- Honor labels from the target
     honorLabels: false
-    # -- Path to scrape metrics
+    # -- Path to scrape metrics from
     path: /metrics
     # -- Timeout for scrape metrics request
     scrapeTimeout: 10s
 
   users:
     root:
-      # -- Create a secret for the root user
+      # -- Create a secret for the root user credentials
       createSecret: true
+      # -- Root username
       username: iggy
+      # -- Root password
       password: changeit
-      # -- Whether to use an existing secret for the root user
       existingSecret:
+        # -- Name of existing secret for root credentials
         name: ""
-        # -- Key in the secret to get the root username from
+        # -- Key in secret for username
         usernameKey: username
-        # -- Key in the secret to get the root password from
+        # -- Key in secret for password
         passwordKey: password
 
-  # -- Add persistence volume claim configuration
   persistence:
-    # -- Enable persistence using a PVC
+    # -- Enable persistence using PVC
     enabled: false
     # -- PVC annotations
     annotations: {}
-    # -- Persistent Volume Storage Class
-    # If defined, storageClassName: <storageClass>
-    # If set to "-", storageClassName: "", which disables dynamic provisioning
-    # If undefined (the default) or set to null, no storageClassName spec is
-    #   set, choosing the default provisioner.  (gp2 on AWS, standard on
-    #   GKE, AWS & OpenStack)
+    # -- Storage class for PVC (empty uses default provisioner)
     storageClass: ""
-
-    # -- A manually managed Persistent Volume and Claim
-    # Requires persistence.enabled: true
-    # If defined, PVC must be created manually before volume will be bound
+    # -- Use existing PVC (requires persistence.enabled: true)
     existingClaim: ""
-
-    # -- PVC Access mode
+    # -- PVC access mode
     accessMode: ReadWriteOnce
-    # -- PVC claim size
+    # -- PVC storage size
     size: 8Gi
-  # -- Set up environmental variables to be added to the container
+
+  # -- Environment variables for the server container
   env:
     - name: RUST_LOG
       value: info
@@ -99,134 +101,134 @@ server:
     - name: IGGY_WEBSOCKET_ADDRESS
       value: "0.0.0.0:8092"
 
+  # -- Node selector for server pods
   nodeSelector: {}
 
+  # -- Tolerations for server pods
   tolerations: []
 
+  # -- Affinity rules for server pods
   affinity: {}
 
   ingress:
+    # -- Enable ingress for the server
     enabled: false
+    # -- Ingress class name (controller-neutral)
     className: ""
+    # -- Ingress annotations (controller-specific)
     annotations: {}
-    # kubernetes.io/ingress.class: nginx
-    # kubernetes.io/tls-acme: "true"
+    # -- Ingress hosts configuration
     hosts:
       - host: chart-example.local
         paths:
           - path: /
             pathType: ImplementationSpecific
-    tls:
-      []
-      #  - secretName: chart-example-tls
-      #    hosts:
-      #      - chart-example.local
+    # -- Ingress TLS configuration
+    tls: []
 
-# Iggy web-ui configuration
+# -- Iggy web UI configuration
 ui:
+  # -- Enable the web UI deployment
   enabled: true
   ports:
+    # -- HTTP port for web UI
     http: 3050
   server:
-    # -- Set the Iggy server endpoint explicitly. If kept blank, the Iggy 
service URL will be used
+    # -- Iggy server endpoint (blank uses service URL)
     endpoint: ""
 
   service:
+    # -- Service type for the UI
     type: ClusterIP
+    # -- Service port for the UI
     port: 3050
+  # -- Number of UI replicas
   replicaCount: 1
-  # -- Extra environmental variables to add to the container
+  # -- Extra environment variables for UI container
   env: {}
   image:
+    # -- UI image repository
     repository: apache/iggy-web-ui
+    # -- UI image pull policy
     pullPolicy: Always
-    # Overrides the image tag whose default is the chart appVersion.
+    # -- UI image tag (overrides chart appVersion)
     tag: "edge"
   ingress:
+    # -- Enable ingress for the UI
     enabled: false
+    # -- Ingress class name (controller-neutral)
     className: ""
+    # -- Ingress annotations (controller-specific)
     annotations: {}
-    # kubernetes.io/ingress.class: nginx
-    # kubernetes.io/tls-acme: "true"
+    # -- Ingress hosts configuration
     hosts:
       - host: chart-example.local
         paths:
           - path: /
             pathType: ImplementationSpecific
+    # -- Ingress TLS configuration
     tls: []
-    #  - secretName: chart-example-tls
-    #    hosts:
-    #      - chart-example.local
-  resources:
-    {}
-    # We usually recommend not to specify default resources and to leave this 
as a conscious
-    # choice for the user. This also increases chances charts run on 
environments with little
-    # resources, such as Minikube. If you do want to specify resources, 
uncomment the following
-    # lines, adjust them as necessary, and remove the curly braces after 
'resources:'.
-    # limits:
-    #   cpu: 100m
-    #   memory: 128Mi
-    # requests:
-    #   cpu: 100m
-    #   memory: 128Mi
-
-  # -- Web UI pod security context. Leave empty unless your UI deployment 
needs explicit pod-level settings.
+  # -- Resource limits and requests for UI
+  resources: {}
+
+  # -- Pod security context for UI pods
   podSecurityContext: {}
 
-  # -- Web UI container security context. Leave empty unless your UI 
deployment needs explicit container-level settings.
+  # -- Container security context for UI
   securityContext: {}
 
+  # -- Node selector for UI pods
   nodeSelector: {}
 
+  # -- Tolerations for UI pods
   tolerations: []
 
+  # -- Affinity rules for UI pods
   affinity: {}
 
+# -- Image pull secrets for private registries
 imagePullSecrets: []
+# -- Override chart name
 nameOverride: ""
+# -- Override full release name
 fullnameOverride: ""
 
-# -- Additional labels to add to all resources
+# -- Additional labels for all resources
 additionalLabels: {}
 
 serviceAccount:
-  # Specifies whether a service account should be created
+  # -- Create a service account
   create: true
-  # Annotations to add to the service account
+  # -- Service account annotations
   annotations: {}
-  # The name of the service account to use.
-  # If not set and create is true, a name is generated using the fullname 
template
+  # -- Service account name (generated if not set)
   name: ""
 
+# -- Pod annotations
 podAnnotations: {}
 
+# -- Pod security context (server uses io_uring, requires unconfined seccomp)
 podSecurityContext:
-  # Required for Iggy server io_uring syscalls in containerized environments
   seccompProfile:
     type: Unconfined
 
+# -- Container security context (server requires IPC_LOCK for io_uring)
 securityContext:
   capabilities:
     add:
-      # Required for Iggy server memlock (io_uring memory)
       - IPC_LOCK
 
-resources:
-  {}
-  # We usually recommend not to specify default resources and to leave this as 
a conscious
-  # choice for the user. This also increases chances charts run on 
environments with little
-  # resources, such as Minikube. If you do want to specify resources, 
uncomment the following
-  # lines, adjust them as necessary, and remove the curly braces after 
'resources:'.
-  # limits:
-  #   cpu: 100m
-  #   memory: 128Mi
-  # requests:
-  #   cpu: 100m
-  #   memory: 128Mi
+# -- Resource limits and requests for server
+resources: {}
 
 autoscaling:
+  # -- Enable horizontal pod autoscaling
   enabled: false
+  # -- Minimum replicas for autoscaling
   minReplicas: 1
+  # -- Maximum replicas for autoscaling
   maxReplicas: 100
+  # -- Target CPU utilization for autoscaling
   targetCPUUtilizationPercentage: 80
+  # -- Target memory utilization for autoscaling (optional)
   # targetMemoryUtilizationPercentage: 80
diff --git a/scripts/ci/markdownlint.sh b/scripts/ci/markdownlint.sh
index 61ba62467..7fe0c2b13 100755
--- a/scripts/ci/markdownlint.sh
+++ b/scripts/ci/markdownlint.sh
@@ -45,15 +45,16 @@ if ! command -v markdownlint &> /dev/null; then
 fi
 
 # Files to ignore (in addition to .gitignore)
-IGNORE_FILES="CLAUDE.md"
+# helm/charts/iggy/README.md is auto-generated by helm-docs
+IGNORE_ARGS=(--ignore "helm/charts/iggy/README.md")
 
 if [ "$MODE" = "fix" ]; then
   echo "🔧 Fixing markdown files..."
-  markdownlint '**/*.md' --ignore-path .gitignore --ignore "$IGNORE_FILES" 
--fix
+  markdownlint '**/*.md' --ignore-path .gitignore "${IGNORE_ARGS[@]}" --fix
   echo "✅ Markdown files have been fixed"
 else
   echo "🔍 Checking markdown files..."
-  if markdownlint '**/*.md' --ignore-path .gitignore --ignore "$IGNORE_FILES"; 
then
+  if markdownlint '**/*.md' --ignore-path .gitignore "${IGNORE_ARGS[@]}"; then
     echo "✅ All markdown files are properly formatted"
   else
     echo "❌ Markdown linting failed"
diff --git a/scripts/ci/setup-helm-tools.sh b/scripts/ci/setup-helm-tools.sh
index 5c4a2686f..4f2d6e688 100755
--- a/scripts/ci/setup-helm-tools.sh
+++ b/scripts/ci/setup-helm-tools.sh
@@ -22,27 +22,34 @@ set -euo pipefail
 
 usage() {
   cat <<'EOF'
-Usage: scripts/ci/setup-helm-tools.sh [--install-kind]
+Usage: scripts/ci/setup-helm-tools.sh [--install-kind] [--install-helm-docs]
 
 Install the pinned Helm toolchain used by Helm chart CI jobs. When requested,
-also install the pinned kind binary used by the Helm smoke test.
+also install the pinned kind binary used by the Helm smoke test or helm-docs
+for README generation.
 
 Environment:
   HELM_VERSION         Helm release version (default: v4.1.3)
   HELM_CHECKSUM        SHA256 checksum for the Helm tarball
   KIND_VERSION         kind release version (default: v0.31.0)
   KIND_CHECKSUM        SHA256 checksum for the kind binary
+  HELM_DOCS_VERSION    helm-docs release version (default: v1.14.2)
+  HELM_DOCS_CHECKSUM   SHA256 checksum for the helm-docs tarball
   HELM_TOOLS_BIN_DIR   Target binary directory (default: /usr/local/bin)
 EOF
 }
 
 INSTALL_KIND="false"
+INSTALL_HELM_DOCS="false"
 
 while [ $# -gt 0 ]; do
   case "$1" in
     --install-kind)
       INSTALL_KIND="true"
       ;;
+    --install-helm-docs)
+      INSTALL_HELM_DOCS="true"
+      ;;
     --help|-h|help)
       usage
       exit 0
@@ -60,6 +67,8 @@ HELM_VERSION="${HELM_VERSION:-v4.1.3}"
 
HELM_CHECKSUM="${HELM_CHECKSUM:-02ce9722d541238f81459938b84cf47df2fdf1187493b4bfb2346754d82a4700}"
 KIND_VERSION="${KIND_VERSION:-v0.31.0}"
 
KIND_CHECKSUM="${KIND_CHECKSUM:-eb244cbafcc157dff60cf68693c14c9a75c4e6e6fedaf9cd71c58117cb93e3fa}"
+HELM_DOCS_VERSION="${HELM_DOCS_VERSION:-v1.14.2}"
+HELM_DOCS_CHECKSUM="${HELM_DOCS_CHECKSUM:-a8cf72ada34fad93285ba2a452b38bdc5bd52cc9a571236244ec31022928d6cc}"
 HELM_TOOLS_BIN_DIR="${HELM_TOOLS_BIN_DIR:-/usr/local/bin}"
 
 require_command() {
@@ -109,3 +118,14 @@ if [ "$INSTALL_KIND" = "true" ]; then
   echo "${KIND_CHECKSUM}  ${kind_binary}" | sha256sum -c -
   install_binary "$kind_binary" "${HELM_TOOLS_BIN_DIR}/kind"
 fi
+
+if [ "$INSTALL_HELM_DOCS" = "true" ]; then
+  helm_docs_archive="/tmp/helm-docs.tar.gz"
+  helm_docs_dir="/tmp/helm-docs-extract"
+  wget -qO "$helm_docs_archive" 
"https://github.com/norwoodj/helm-docs/releases/download/${HELM_DOCS_VERSION}/helm-docs_${HELM_DOCS_VERSION#v}_Linux_x86_64.tar.gz";
+  echo "${HELM_DOCS_CHECKSUM}  ${helm_docs_archive}" | sha256sum -c -
+  rm -rf "$helm_docs_dir"
+  mkdir -p "$helm_docs_dir"
+  tar -zxf "$helm_docs_archive" -C "$helm_docs_dir" helm-docs
+  install_binary "$helm_docs_dir/helm-docs" "${HELM_TOOLS_BIN_DIR}/helm-docs"
+fi
diff --git a/scripts/ci/test-helm.sh b/scripts/ci/test-helm.sh
index c8cd377f5..6bb1a41b7 100755
--- a/scripts/ci/test-helm.sh
+++ b/scripts/ci/test-helm.sh
@@ -145,6 +145,48 @@ extract_kind_names() {
   ' "$file"
 }
 
+validate_yamllint() {
+  if ! command -v yamllint >/dev/null 2>&1; then
+    echo "Warning: yamllint not found, skipping YAML lint check" >&2
+    return 0
+  fi
+
+  echo "Running yamllint on Helm chart..."
+  yamllint -c "$CHART_DIR/.yamllint.yml" "$CHART_DIR"
+}
+
+validate_helmfmt() {
+  if ! command -v helmfmt >/dev/null 2>&1; then
+    echo "Warning: helmfmt not found, skipping Helm template format check" >&2
+    return 0
+  fi
+
+  echo "Checking Helm template formatting..."
+  if ! helmfmt --check "$CHART_DIR"; then
+    echo "Error: Helm templates are not formatted. Run 'helmfmt $CHART_DIR' 
locally and commit the changes." >&2
+    exit 1
+  fi
+}
+
+validate_helm_docs() {
+  if ! command -v helm-docs >/dev/null 2>&1; then
+    echo "Warning: helm-docs not found, skipping README drift check" >&2
+    return 0
+  fi
+
+  local readme_before
+  local readme_after
+
+  readme_before="$(cat "$CHART_DIR/README.md")"
+  helm-docs --chart-search-root "$CHART_DIR" --template-files README.md.gotmpl
+  readme_after="$(cat "$CHART_DIR/README.md")"
+
+  if [ "$readme_before" != "$readme_after" ]; then
+    echo "Error: README.md is out of sync with values.yaml. Run 'helm-docs' 
locally and commit the updated README." >&2
+    exit 1
+  fi
+}
+
 validate() {
   require_command helm
 
@@ -218,6 +260,10 @@ validate() {
     exit 1
   fi
   grep -q 'name: supersecret' "$HELM_RENDER_DIR/existing-secret.yaml"
+
+  validate_yamllint
+  validate_helmfmt
+  validate_helm_docs
 }
 
 smoke() {

Reply via email to