Helm Chart Builder Production-grade Helm charts. Sensible defaults. Secure by design. No cargo-culting. Opinionated Helm workflow that turns ad-hoc Kubernetes manifests into maintainable, testable, reusable charts. Covers chart structure, values design, template patterns, dependency management, and security hardening. Not a Helm tutorial — a set of concrete decisions about how to build charts that operators trust and developers don't fight. --- Slash Commands | Command | What it does | |---------|-------------| | | Scaffold a production-ready Helm chart with best-practice structure | | | Anal…

,\n \"message\": \"Hardcoded image tag in template — must use .Values.image.repository and .Values.image.tag\",\n \"fix\": 'Use: image: \"{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}\"',\n },\n {\n \"id\": \"TP002\",\n \"severity\": \"high\",\n \"pattern\": r'replicas:\\s*\\d+\\s*

Helm Chart Builder Production-grade Helm charts. Sensible defaults. Secure by design. No cargo-culting. Opinionated Helm workflow that turns ad-hoc Kubernetes manifests into maintainable, testable, reusable charts. Covers chart structure, values design, template patterns, dependency management, and security hardening. Not a Helm tutorial — a set of concrete decisions about how to build charts that operators trust and developers don't fight. --- Slash Commands | Command | What it does | |---------|-------------| | | Scaffold a production-ready Helm chart with best-practice structure | | | Anal…

,\n \"message\": \"Hardcoded replica count — must be configurable via values\",\n \"fix\": \"Use: replicas: {{ .Values.replicaCount }}\",\n },\n {\n \"id\": \"TP003\",\n \"severity\": \"medium\",\n \"pattern\": r'port:\\s*\\d+\\s*

Helm Chart Builder Production-grade Helm charts. Sensible defaults. Secure by design. No cargo-culting. Opinionated Helm workflow that turns ad-hoc Kubernetes manifests into maintainable, testable, reusable charts. Covers chart structure, values design, template patterns, dependency management, and security hardening. Not a Helm tutorial — a set of concrete decisions about how to build charts that operators trust and developers don't fight. --- Slash Commands | Command | What it does | |---------|-------------| | | Scaffold a production-ready Helm chart with best-practice structure | | | Anal…

,\n \"message\": \"Hardcoded port number — should be configurable via values\",\n \"fix\": \"Use: port: {{ .Values.service.port }}\",\n },\n {\n \"id\": \"TP004\",\n \"severity\": \"high\",\n \"pattern\": r'(?:name|namespace):\\s*[a-z][a-z0-9-]+\\s*

Helm Chart Builder Production-grade Helm charts. Sensible defaults. Secure by design. No cargo-culting. Opinionated Helm workflow that turns ad-hoc Kubernetes manifests into maintainable, testable, reusable charts. Covers chart structure, values design, template patterns, dependency management, and security hardening. Not a Helm tutorial — a set of concrete decisions about how to build charts that operators trust and developers don't fight. --- Slash Commands | Command | What it does | |---------|-------------| | | Scaffold a production-ready Helm chart with best-practice structure | | | Anal…

,\n \"message\": \"Hardcoded name/namespace — should use template helpers\",\n \"fix\": 'Use: name: {{ include \"mychart.fullname\" . }}',\n },\n {\n \"id\": \"TP005\",\n \"severity\": \"medium\",\n \"pattern\": r'nodePort:\\s*\\d+',\n \"message\": \"Hardcoded nodePort — should be configurable or avoided\",\n \"fix\": \"Use: nodePort: {{ .Values.service.nodePort }} with conditional\",\n },\n]\n\nSECURITY_CHECKS = [\n {\n \"id\": \"SC001\",\n \"severity\": \"critical\",\n \"check\": \"no_security_context\",\n \"message\": \"No securityContext found in any template — pods run as root with full capabilities\",\n \"fix\": \"Add pod and container securityContext with runAsNonRoot, readOnlyRootFilesystem, drop ALL capabilities\",\n },\n {\n \"id\": \"SC002\",\n \"severity\": \"critical\",\n \"check\": \"privileged_container\",\n \"message\": \"Privileged container detected — full host access\",\n \"fix\": \"Remove privileged: true. Use specific capabilities instead\",\n },\n {\n \"id\": \"SC003\",\n \"severity\": \"high\",\n \"check\": \"no_run_as_non_root\",\n \"message\": \"No runAsNonRoot: true — container may run as root\",\n \"fix\": \"Add runAsNonRoot: true to pod securityContext\",\n },\n {\n \"id\": \"SC004\",\n \"severity\": \"high\",\n \"check\": \"no_readonly_rootfs\",\n \"message\": \"No readOnlyRootFilesystem — container filesystem is writable\",\n \"fix\": \"Add readOnlyRootFilesystem: true and use emptyDir for writable paths\",\n },\n {\n \"id\": \"SC005\",\n \"severity\": \"medium\",\n \"check\": \"no_network_policy\",\n \"message\": \"No NetworkPolicy template — all pod-to-pod traffic allowed\",\n \"fix\": \"Add a NetworkPolicy template with default-deny ingress and explicit allow rules\",\n },\n {\n \"id\": \"SC006\",\n \"severity\": \"medium\",\n \"check\": \"automount_sa_token\",\n \"message\": \"automountServiceAccountToken not set to false — pod can access K8s API\",\n \"fix\": \"Set automountServiceAccountToken: false unless the pod needs K8s API access\",\n },\n {\n \"id\": \"SC007\",\n \"severity\": \"high\",\n \"check\": \"host_network\",\n \"message\": \"hostNetwork: true — pod shares host network namespace\",\n \"fix\": \"Remove hostNetwork unless absolutely required (e.g., CNI plugin)\",\n },\n {\n \"id\": \"SC008\",\n \"severity\": \"critical\",\n \"check\": \"host_pid_ipc\",\n \"message\": \"hostPID or hostIPC enabled — pod can see host processes/IPC\",\n \"fix\": \"Remove hostPID and hostIPC — never needed in application charts\",\n },\n]\n\nLABEL_PATTERNS = [\n r\"app\\.kubernetes\\.io/name\",\n r\"app\\.kubernetes\\.io/instance\",\n r\"app\\.kubernetes\\.io/version\",\n r\"app\\.kubernetes\\.io/managed-by\",\n r\"helm\\.sh/chart\",\n]\n\n\n# --- Demo Chart ---\n\nDEMO_CHART_YAML = \"\"\"apiVersion: v2\nname: demo-app\nversion: 0.1.0\n\"\"\"\n\nDEMO_VALUES_YAML = \"\"\"replicaCount: 1\n\nimage:\n repository: nginx\n tag: latest\n pullPolicy: Always\n\nservice:\n type: ClusterIP\n port: 80\n\"\"\"\n\nDEMO_DEPLOYMENT = \"\"\"apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: demo-app\nspec:\n replicas: 3\n template:\n spec:\n containers:\n - name: demo-app\n image: nginx:1.25\n ports:\n - containerPort: 80\n\"\"\"\n\n\ndef parse_yaml_simple(content):\n \"\"\"Simple key-value parser for YAML (stdlib only).\"\"\"\n result = {}\n for line in content.splitlines():\n stripped = line.strip()\n if not stripped or stripped.startswith(\"#\"):\n continue\n if \":\" in stripped and not stripped.startswith(\"-\"):\n key, _, val = stripped.partition(\":\")\n key = key.strip()\n val = val.strip().strip(\"'\\\"\")\n if val:\n result[key] = val\n return result\n\n\ndef check_structure(chart_dir):\n \"\"\"Check chart directory for required files.\"\"\"\n findings = []\n for check in REQUIRED_FILES:\n path = chart_dir / check[\"path\"]\n if not path.exists():\n findings.append({\n \"id\": \"ST\" + str(REQUIRED_FILES.index(check) + 1).zfill(3),\n \"severity\": check[\"severity\"],\n \"message\": check[\"message\"],\n \"fix\": f\"Create {check['path']}\",\n \"file\": check[\"path\"],\n })\n return findings\n\n\ndef check_chart_yaml(chart_dir):\n \"\"\"Validate Chart.yaml metadata.\"\"\"\n findings = []\n chart_path = chart_dir / \"Chart.yaml\"\n if not chart_path.exists():\n return findings\n\n content = chart_path.read_text(encoding=\"utf-8\")\n parsed = parse_yaml_simple(content)\n\n for check in CHART_YAML_CHECKS:\n if check[\"field\"] not in parsed:\n findings.append({\n \"id\": \"CY\" + str(CHART_YAML_CHECKS.index(check) + 1).zfill(3),\n \"severity\": check[\"severity\"],\n \"message\": check[\"message\"],\n \"fix\": f\"Add '{check['field']}:' to Chart.yaml\",\n \"file\": \"Chart.yaml\",\n })\n\n # Check apiVersion value\n if parsed.get(\"apiVersion\") == \"v1\":\n findings.append({\n \"id\": \"CY007\",\n \"severity\": \"medium\",\n \"message\": \"apiVersion: v1 is Helm 2 format — use v2 for Helm 3\",\n \"fix\": \"Change apiVersion to v2\",\n \"file\": \"Chart.yaml\",\n })\n\n # Check version is semver\n version = parsed.get(\"version\", \"\")\n if version and not re.match(r\"^\\d+\\.\\d+\\.\\d+\", version):\n findings.append({\n \"id\": \"CY008\",\n \"severity\": \"high\",\n \"message\": f\"Version '{version}' is not valid semver\",\n \"fix\": \"Use semver format: MAJOR.MINOR.PATCH (e.g., 1.0.0)\",\n \"file\": \"Chart.yaml\",\n })\n\n return findings\n\n\ndef check_templates(chart_dir):\n \"\"\"Scan templates for anti-patterns.\"\"\"\n findings = []\n templates_dir = chart_dir / \"templates\"\n if not templates_dir.exists():\n return findings\n\n template_files = list(templates_dir.glob(\"*.yaml\")) + list(templates_dir.glob(\"*.yml\")) + list(templates_dir.glob(\"*.tpl\"))\n\n all_content = \"\"\n for tpl_file in template_files:\n content = tpl_file.read_text(encoding=\"utf-8\")\n all_content += content + \"\\n\"\n rel_path = tpl_file.relative_to(chart_dir)\n\n for rule in TEMPLATE_ANTI_PATTERNS:\n # Skip patterns that would false-positive on template expressions\n for match in re.finditer(rule[\"pattern\"], content, re.MULTILINE):\n line = match.group(0).strip()\n # Skip if the line contains a template expression\n if \"{{\" in line or \"}}\" in line:\n continue\n findings.append({\n \"id\": rule[\"id\"],\n \"severity\": rule[\"severity\"],\n \"message\": rule[\"message\"],\n \"fix\": rule[\"fix\"],\n \"file\": str(rel_path),\n \"line\": line[:80],\n })\n\n # Check for standard labels\n helpers_file = templates_dir / \"_helpers.tpl\"\n if helpers_file.exists():\n helpers_content = helpers_file.read_text(encoding=\"utf-8\")\n for label_pattern in LABEL_PATTERNS:\n if not re.search(label_pattern, helpers_content) and not re.search(label_pattern, all_content):\n label_name = label_pattern.replace(\"\\\\.\", \".\")\n findings.append({\n \"id\": \"LB001\",\n \"severity\": \"high\",\n \"message\": f\"Standard label '{label_name}' not found in helpers or templates\",\n \"fix\": f\"Add {label_name} to the labels helper in _helpers.tpl\",\n \"file\": \"templates/_helpers.tpl\",\n \"line\": \"(label not found)\",\n })\n\n # Check for resource limits\n if \"resources:\" not in all_content and template_files:\n findings.append({\n \"id\": \"TP006\",\n \"severity\": \"critical\",\n \"message\": \"No resource requests/limits in any template — pods can consume unlimited node resources\",\n \"fix\": \"Add resources section: {{ toYaml .Values.resources | nindent 12 }}\",\n \"file\": \"templates/\",\n \"line\": \"(no resources block found)\",\n })\n\n # Check for probes\n if \"livenessProbe\" not in all_content and \"readinessProbe\" not in all_content and template_files:\n has_deployment = any(\"Deployment\" in f.read_text(encoding=\"utf-8\") for f in template_files if f.suffix in (\".yaml\", \".yml\"))\n if has_deployment:\n findings.append({\n \"id\": \"TP007\",\n \"severity\": \"high\",\n \"message\": \"No liveness/readiness probes — Kubernetes cannot detect unhealthy pods\",\n \"fix\": \"Add livenessProbe and readinessProbe with configurable values\",\n \"file\": \"templates/deployment.yaml\",\n \"line\": \"(no probes found)\",\n })\n\n return findings\n\n\ndef check_security(chart_dir):\n \"\"\"Run security-focused checks.\"\"\"\n findings = []\n templates_dir = chart_dir / \"templates\"\n if not templates_dir.exists():\n return findings\n\n template_files = list(templates_dir.glob(\"*.yaml\")) + list(templates_dir.glob(\"*.yml\"))\n all_content = \"\"\n for tpl_file in template_files:\n all_content += tpl_file.read_text(encoding=\"utf-8\") + \"\\n\"\n\n for check in SECURITY_CHECKS:\n triggered = False\n\n if check[\"check\"] == \"no_security_context\":\n if \"securityContext\" not in all_content and template_files:\n triggered = True\n elif check[\"check\"] == \"privileged_container\":\n if re.search(r\"privileged:\\s*true\", all_content):\n triggered = True\n elif check[\"check\"] == \"no_run_as_non_root\":\n if \"securityContext\" in all_content and \"runAsNonRoot\" not in all_content:\n triggered = True\n elif check[\"check\"] == \"no_readonly_rootfs\":\n if \"securityContext\" in all_content and \"readOnlyRootFilesystem\" not in all_content:\n triggered = True\n elif check[\"check\"] == \"no_network_policy\":\n np_file = templates_dir / \"networkpolicy.yaml\"\n if not np_file.exists() and \"NetworkPolicy\" not in all_content:\n triggered = True\n elif check[\"check\"] == \"automount_sa_token\":\n if \"automountServiceAccountToken\" not in all_content and template_files:\n triggered = True\n elif check[\"check\"] == \"host_network\":\n if re.search(r\"hostNetwork:\\s*true\", all_content):\n triggered = True\n elif check[\"check\"] == \"host_pid_ipc\":\n if re.search(r\"host(?:PID|IPC):\\s*true\", all_content):\n triggered = True\n\n if triggered:\n findings.append({\n \"id\": check[\"id\"],\n \"severity\": check[\"severity\"],\n \"message\": check[\"message\"],\n \"fix\": check[\"fix\"],\n \"file\": \"templates/\",\n })\n\n # Check for secrets in values.yaml\n values_path = chart_dir / \"values.yaml\"\n if values_path.exists():\n values_content = values_path.read_text(encoding=\"utf-8\")\n for match in re.finditer(r\"^(\\s*\\S*(?:password|secret|token|apiKey|api_key)\\s*:\\s*)(\\S+)\", values_content, re.MULTILINE | re.IGNORECASE):\n val = match.group(2).strip(\"'\\\"\")\n if val and val not in (\"null\", \"~\", '\"\"', \"''\", \"changeme\", \"CHANGEME\", \"TODO\"):\n findings.append({\n \"id\": \"SC009\",\n \"severity\": \"critical\",\n \"message\": f\"Potential secret in values.yaml default: {match.group(0).strip()[:60]}\",\n \"fix\": \"Remove default secret values. Use empty string or null with documentation\",\n \"file\": \"values.yaml\",\n \"line\": match.group(0).strip()[:80],\n })\n\n return findings\n\n\ndef analyze_chart(chart_dir, output_format=\"text\", security_focus=False):\n \"\"\"Run full chart analysis.\"\"\"\n findings = []\n findings.extend(check_structure(chart_dir))\n findings.extend(check_chart_yaml(chart_dir))\n findings.extend(check_templates(chart_dir))\n\n if security_focus:\n findings.extend(check_security(chart_dir))\n # Filter to security-relevant items only\n security_ids = {\"SC001\", \"SC002\", \"SC003\", \"SC004\", \"SC005\", \"SC006\", \"SC007\", \"SC008\", \"SC009\"}\n security_severities = {\"critical\", \"high\"}\n findings = [f for f in findings if f[\"id\"] in security_ids or f[\"severity\"] in security_severities]\n else:\n findings.extend(check_security(chart_dir))\n\n # Deduplicate\n seen = set()\n unique = []\n for f in findings:\n key = (f[\"id\"], f.get(\"line\", \"\"), f.get(\"file\", \"\"))\n if key not in seen:\n seen.add(key)\n unique.append(f)\n findings = unique\n\n # Sort by severity\n severity_order = {\"critical\": 0, \"high\": 1, \"medium\": 2, \"low\": 3}\n findings.sort(key=lambda f: severity_order.get(f[\"severity\"], 4))\n\n # Score\n deductions = {\"critical\": 25, \"high\": 15, \"medium\": 5, \"low\": 2}\n score = max(0, 100 - sum(deductions.get(f[\"severity\"], 0) for f in findings))\n\n counts = {\n \"critical\": sum(1 for f in findings if f[\"severity\"] == \"critical\"),\n \"high\": sum(1 for f in findings if f[\"severity\"] == \"high\"),\n \"medium\": sum(1 for f in findings if f[\"severity\"] == \"medium\"),\n \"low\": sum(1 for f in findings if f[\"severity\"] == \"low\"),\n }\n\n # Chart metadata\n chart_yaml_path = chart_dir / \"Chart.yaml\"\n chart_meta = parse_yaml_simple(chart_yaml_path.read_text(encoding=\"utf-8\")) if chart_yaml_path.exists() else {}\n\n result = {\n \"score\": score,\n \"chart_name\": chart_meta.get(\"name\", chart_dir.name),\n \"chart_version\": chart_meta.get(\"version\", \"unknown\"),\n \"app_version\": chart_meta.get(\"appVersion\", \"unknown\"),\n \"findings\": findings,\n \"finding_counts\": counts,\n }\n\n if output_format == \"json\":\n print(json.dumps(result, indent=2))\n return result\n\n # Text output\n print(f\"\\n{'=' * 60}\")\n print(f\" Helm Chart Analysis Report\")\n print(f\"{'=' * 60}\")\n print(f\" Score: {score}/100\")\n print(f\" Chart: {result['chart_name']} v{result['chart_version']}\")\n print(f\" App Version: {result['app_version']}\")\n print()\n print(f\" Findings: {counts['critical']} critical | {counts['high']} high | {counts['medium']} medium | {counts['low']} low\")\n print(f\"{'─' * 60}\")\n\n for f in findings:\n icon = {\"critical\": \"!!!\", \"high\": \"!!\", \"medium\": \"!\", \"low\": \"~\"}.get(f[\"severity\"], \"?\")\n print(f\"\\n [{f['id']}] {icon} {f['severity'].upper()}\")\n print(f\" {f['message']}\")\n if \"file\" in f:\n print(f\" File: {f['file']}\")\n if \"line\" in f:\n print(f\" Line: {f['line']}\")\n print(f\" Fix: {f['fix']}\")\n\n if not findings:\n print(\"\\n No issues found. Chart looks good.\")\n\n print(f\"\\n{'=' * 60}\\n\")\n return result\n\n\ndef run_demo():\n \"\"\"Run analysis on demo chart data.\"\"\"\n import tempfile\n import os\n\n with tempfile.TemporaryDirectory() as tmpdir:\n chart_dir = Path(tmpdir) / \"demo-app\"\n chart_dir.mkdir()\n (chart_dir / \"Chart.yaml\").write_text(DEMO_CHART_YAML)\n (chart_dir / \"values.yaml\").write_text(DEMO_VALUES_YAML)\n templates_dir = chart_dir / \"templates\"\n templates_dir.mkdir()\n (templates_dir / \"deployment.yaml\").write_text(DEMO_DEPLOYMENT)\n\n return chart_dir, analyze_chart\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description=\"helm-chart-builder: Helm chart static analyzer\"\n )\n parser.add_argument(\"chartdir\", nargs=\"?\", help=\"Path to Helm chart directory (omit for demo)\")\n parser.add_argument(\n \"--output\", \"-o\",\n choices=[\"text\", \"json\"],\n default=\"text\",\n help=\"Output format (default: text)\",\n )\n parser.add_argument(\n \"--security\",\n action=\"store_true\",\n help=\"Security-focused analysis only\",\n )\n args = parser.parse_args()\n\n if args.chartdir:\n chart_dir = Path(args.chartdir)\n if not chart_dir.is_dir():\n print(f\"Error: Not a directory: {args.chartdir}\", file=sys.stderr)\n sys.exit(1)\n analyze_chart(chart_dir, args.output, args.security)\n else:\n print(\"No chart directory provided. Running demo analysis...\\n\")\n import tempfile\n with tempfile.TemporaryDirectory() as tmpdir:\n chart_dir = Path(tmpdir) / \"demo-app\"\n chart_dir.mkdir()\n (chart_dir / \"Chart.yaml\").write_text(DEMO_CHART_YAML)\n (chart_dir / \"values.yaml\").write_text(DEMO_VALUES_YAML)\n templates_dir = chart_dir / \"templates\"\n templates_dir.mkdir()\n (templates_dir / \"deployment.yaml\").write_text(DEMO_DEPLOYMENT)\n analyze_chart(chart_dir, args.output, args.security)\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":20022,"content_sha256":"5702d86985df261a100ac6b1d9f0b6b5d0aa7bd4f8acd98508e39a19095e6c0d"},{"filename":"scripts/values_validator.py","content":"#!/usr/bin/env python3\n\"\"\"\nhelm-chart-builder: Values Validator\n\nValidate values.yaml files against Helm best practices — documentation coverage,\ntype consistency, naming conventions, default quality, and security.\n\nUsage:\n python scripts/values_validator.py values.yaml\n python scripts/values_validator.py values.yaml --output json\n python scripts/values_validator.py values.yaml --strict\n\"\"\"\n\nimport argparse\nimport json\nimport re\nimport sys\nfrom pathlib import Path\n\n\n# --- Demo values.yaml ---\n\nDEMO_VALUES = \"\"\"# Default values for demo-app\nreplicaCount: 1\n\nimage:\n repository: nginx\n tag: latest\n pullPolicy: Always\n\nservice:\n type: ClusterIP\n port: 80\n\ningress:\n enabled: false\n\nresources: {}\n\nPASSWORD: supersecret123\ndb_password: changeme\napi-key: sk-12345\n\ndeeply:\n nested:\n structure:\n that:\n goes:\n too:\n deep: true\n\nundocumented_value: something\nAnotherValue: 42\nsnake_case_key: bad\n\"\"\"\n\n\n# --- Validation Rules ---\n\nNAMING_PATTERN = re.compile(r\"^[a-z][a-zA-Z0-9]*$\") # camelCase\nSNAKE_CASE_PATTERN = re.compile(r\"^[a-z][a-z0-9]*(_[a-z0-9]+)+$\") # snake_case\nUPPER_CASE_PATTERN = re.compile(r\"^[A-Z]\") # Starts with uppercase\n\nSECRET_KEY_PATTERNS = [\n re.compile(r\"(?:password|secret|token|apiKey|api_key|api-key|private_key|credentials)\", re.IGNORECASE),\n]\n\nKNOWN_STRUCTURES = {\n \"image\": [\"repository\", \"tag\", \"pullPolicy\"],\n \"service\": [\"type\", \"port\"],\n \"ingress\": [\"enabled\"],\n \"resources\": [],\n \"serviceAccount\": [\"create\", \"name\"],\n \"autoscaling\": [\"enabled\", \"minReplicas\", \"maxReplicas\"],\n}\n\n\ndef parse_values(content):\n \"\"\"Parse values.yaml into structured data with metadata.\n\n Returns a list of entries with key paths, values, depth, and comment info.\n \"\"\"\n entries = []\n key_stack = []\n indent_stack = [0]\n prev_comment = None\n\n for line_num, line in enumerate(content.splitlines(), 1):\n stripped = line.strip()\n\n # Track comments for documentation coverage\n if stripped.startswith(\"#\"):\n prev_comment = stripped\n continue\n\n if not stripped:\n prev_comment = None\n continue\n\n indent = len(line) - len(line.lstrip())\n\n # Pop stack for dedented lines\n while len(indent_stack) > 1 and indent \u003c= indent_stack[-1]:\n indent_stack.pop()\n if key_stack:\n key_stack.pop()\n\n # Parse key: value\n match = re.match(r\"^(\\S+)\\s*:\\s*(.*)\", stripped)\n if match and not stripped.startswith(\"-\"):\n key = match.group(1)\n raw_value = match.group(2).strip()\n\n # Check for inline comment\n inline_comment = None\n if \"#\" in raw_value:\n val_part, _, comment_part = raw_value.partition(\"#\")\n raw_value = val_part.strip()\n inline_comment = comment_part.strip()\n\n # Build full key path\n full_path = \".\".join(key_stack + [key])\n depth = len(key_stack) + 1\n\n # Determine value type\n value_type = \"unknown\"\n if not raw_value or raw_value == \"\":\n value_type = \"map\"\n key_stack.append(key)\n indent_stack.append(indent)\n elif raw_value in (\"true\", \"false\"):\n value_type = \"boolean\"\n elif raw_value == \"null\" or raw_value == \"~\":\n value_type = \"null\"\n elif raw_value == \"{}\":\n value_type = \"empty_map\"\n elif raw_value == \"[]\":\n value_type = \"empty_list\"\n elif re.match(r\"^-?\\d+$\", raw_value):\n value_type = \"integer\"\n elif re.match(r\"^-?\\d+\\.\\d+$\", raw_value):\n value_type = \"float\"\n elif raw_value.startswith('\"') or raw_value.startswith(\"'\"):\n value_type = \"string\"\n else:\n value_type = \"string\"\n\n has_doc = prev_comment is not None or inline_comment is not None\n\n entries.append({\n \"key\": key,\n \"full_path\": full_path,\n \"value\": raw_value,\n \"value_type\": value_type,\n \"depth\": depth,\n \"line\": line_num,\n \"has_documentation\": has_doc,\n \"comment\": prev_comment or inline_comment,\n })\n\n prev_comment = None\n else:\n prev_comment = None\n\n return entries\n\n\ndef validate_naming(entries):\n \"\"\"Check key naming conventions.\"\"\"\n findings = []\n\n for entry in entries:\n key = entry[\"key\"]\n\n # Skip map entries (they're parent keys)\n if entry[\"value_type\"] == \"map\":\n # Parent keys should still be camelCase\n pass\n\n if SNAKE_CASE_PATTERN.match(key):\n findings.append({\n \"severity\": \"medium\",\n \"category\": \"naming\",\n \"message\": f\"Key '{entry['full_path']}' uses snake_case — Helm convention is camelCase\",\n \"fix\": f\"Rename to camelCase: {to_camel_case(key)}\",\n \"line\": entry[\"line\"],\n })\n elif UPPER_CASE_PATTERN.match(key) and not key.isupper():\n findings.append({\n \"severity\": \"medium\",\n \"category\": \"naming\",\n \"message\": f\"Key '{entry['full_path']}' starts with uppercase — use camelCase\",\n \"fix\": f\"Rename: {key[0].lower() + key[1:]}\",\n \"line\": entry[\"line\"],\n })\n elif \"-\" in key:\n findings.append({\n \"severity\": \"medium\",\n \"category\": \"naming\",\n \"message\": f\"Key '{entry['full_path']}' uses kebab-case — Helm convention is camelCase\",\n \"fix\": f\"Rename to camelCase: {to_camel_case(key)}\",\n \"line\": entry[\"line\"],\n })\n\n return findings\n\n\ndef validate_documentation(entries):\n \"\"\"Check documentation coverage.\"\"\"\n findings = []\n total = len(entries)\n documented = sum(1 for e in entries if e[\"has_documentation\"])\n\n if total > 0:\n coverage = (documented / total) * 100\n if coverage \u003c 50:\n findings.append({\n \"severity\": \"high\",\n \"category\": \"documentation\",\n \"message\": f\"Only {coverage:.0f}% of values have comments ({documented}/{total})\",\n \"fix\": \"Add inline YAML comments explaining purpose, type, and valid options for each value\",\n \"line\": 0,\n })\n elif coverage \u003c 80:\n findings.append({\n \"severity\": \"medium\",\n \"category\": \"documentation\",\n \"message\": f\"{coverage:.0f}% documentation coverage ({documented}/{total}) — aim for 80%+\",\n \"fix\": \"Add comments for undocumented values\",\n \"line\": 0,\n })\n\n # Flag specific undocumented top-level keys\n for entry in entries:\n if entry[\"depth\"] == 1 and not entry[\"has_documentation\"]:\n findings.append({\n \"severity\": \"low\",\n \"category\": \"documentation\",\n \"message\": f\"Top-level key '{entry['key']}' has no comment\",\n \"fix\": f\"Add a comment above '{entry['key']}' explaining its purpose\",\n \"line\": entry[\"line\"],\n })\n\n return findings\n\n\ndef validate_defaults(entries):\n \"\"\"Check default value quality.\"\"\"\n findings = []\n\n for entry in entries:\n # Check for :latest tag\n if entry[\"key\"] == \"tag\" and entry[\"value\"] in (\"latest\", '\"latest\"', \"'latest'\"):\n findings.append({\n \"severity\": \"high\",\n \"category\": \"defaults\",\n \"message\": f\"image.tag defaults to 'latest' — not reproducible\",\n \"fix\": \"Use a specific version tag or reference .Chart.AppVersion in template\",\n \"line\": entry[\"line\"],\n })\n\n # Check pullPolicy\n if entry[\"key\"] == \"pullPolicy\" and entry[\"value\"] in (\"Always\", '\"Always\"', \"'Always'\"):\n findings.append({\n \"severity\": \"low\",\n \"category\": \"defaults\",\n \"message\": \"imagePullPolicy defaults to 'Always' — 'IfNotPresent' is better for production\",\n \"fix\": \"Change default to IfNotPresent (Always is appropriate for :latest only)\",\n \"line\": entry[\"line\"],\n })\n\n # Check empty resources\n if entry[\"key\"] == \"resources\" and entry[\"value_type\"] == \"empty_map\":\n findings.append({\n \"severity\": \"medium\",\n \"category\": \"defaults\",\n \"message\": \"resources defaults to {} — no requests or limits set\",\n \"fix\": \"Provide default resource requests (e.g., cpu: 100m, memory: 128Mi)\",\n \"line\": entry[\"line\"],\n })\n\n return findings\n\n\ndef validate_secrets(entries):\n \"\"\"Check for secrets in default values.\"\"\"\n findings = []\n\n for entry in entries:\n for pattern in SECRET_KEY_PATTERNS:\n if pattern.search(entry[\"full_path\"]):\n val = entry[\"value\"].strip(\"'\\\"\")\n if val and val not in (\"\", \"null\", \"~\", \"{}\", \"[]\", \"changeme\", \"CHANGEME\", \"TODO\", '\"\"', \"''\"):\n findings.append({\n \"severity\": \"critical\",\n \"category\": \"security\",\n \"message\": f\"Potential secret with default value: {entry['full_path']} = {val[:30]}...\",\n \"fix\": \"Remove default. Use empty string, null, or 'changeme' placeholder with comment\",\n \"line\": entry[\"line\"],\n })\n break\n\n return findings\n\n\ndef validate_depth(entries):\n \"\"\"Check nesting depth.\"\"\"\n findings = []\n max_depth = max((e[\"depth\"] for e in entries), default=0)\n\n if max_depth > 4:\n deep_entries = [e for e in entries if e[\"depth\"] > 4]\n for entry in deep_entries[:3]: # Report first 3\n findings.append({\n \"severity\": \"medium\",\n \"category\": \"structure\",\n \"message\": f\"Deeply nested key ({entry['depth']} levels): {entry['full_path']}\",\n \"fix\": \"Flatten structure — max 3-4 levels deep for usability\",\n \"line\": entry[\"line\"],\n })\n\n return findings\n\n\ndef to_camel_case(name):\n \"\"\"Convert snake_case or kebab-case to camelCase.\"\"\"\n parts = re.split(r\"[-_]\", name)\n return parts[0].lower() + \"\".join(p.capitalize() for p in parts[1:])\n\n\ndef generate_report(content, output_format=\"text\", strict=False):\n \"\"\"Generate full validation report.\"\"\"\n entries = parse_values(content)\n findings = []\n\n findings.extend(validate_naming(entries))\n findings.extend(validate_documentation(entries))\n findings.extend(validate_defaults(entries))\n findings.extend(validate_secrets(entries))\n findings.extend(validate_depth(entries))\n\n if strict:\n # Elevate medium to high, low to medium\n for f in findings:\n if f[\"severity\"] == \"medium\":\n f[\"severity\"] = \"high\"\n elif f[\"severity\"] == \"low\":\n f[\"severity\"] = \"medium\"\n\n # Sort by severity\n severity_order = {\"critical\": 0, \"high\": 1, \"medium\": 2, \"low\": 3}\n findings.sort(key=lambda f: severity_order.get(f[\"severity\"], 4))\n\n # Score\n deductions = {\"critical\": 25, \"high\": 15, \"medium\": 5, \"low\": 2}\n score = max(0, 100 - sum(deductions.get(f[\"severity\"], 0) for f in findings))\n\n counts = {\n \"critical\": sum(1 for f in findings if f[\"severity\"] == \"critical\"),\n \"high\": sum(1 for f in findings if f[\"severity\"] == \"high\"),\n \"medium\": sum(1 for f in findings if f[\"severity\"] == \"medium\"),\n \"low\": sum(1 for f in findings if f[\"severity\"] == \"low\"),\n }\n\n # Stats\n total_keys = len(entries)\n documented = sum(1 for e in entries if e[\"has_documentation\"])\n max_depth = max((e[\"depth\"] for e in entries), default=0)\n\n result = {\n \"score\": score,\n \"total_keys\": total_keys,\n \"documented_keys\": documented,\n \"documentation_coverage\": f\"{(documented / total_keys * 100):.0f}%\" if total_keys > 0 else \"N/A\",\n \"max_depth\": max_depth,\n \"findings\": findings,\n \"finding_counts\": counts,\n }\n\n if output_format == \"json\":\n print(json.dumps(result, indent=2))\n return result\n\n # Text output\n print(f\"\\n{'=' * 60}\")\n print(f\" Values.yaml Validation Report\")\n print(f\"{'=' * 60}\")\n print(f\" Score: {score}/100\")\n print(f\" Keys: {total_keys} | Documented: {documented} ({result['documentation_coverage']})\")\n print(f\" Max Depth: {max_depth}\")\n print()\n print(f\" Findings: {counts['critical']} critical | {counts['high']} high | {counts['medium']} medium | {counts['low']} low\")\n print(f\"{'─' * 60}\")\n\n for f in findings:\n icon = {\"critical\": \"!!!\", \"high\": \"!!\", \"medium\": \"!\", \"low\": \"~\"}.get(f[\"severity\"], \"?\")\n print(f\"\\n {icon} {f['severity'].upper()} [{f['category']}]\")\n print(f\" {f['message']}\")\n if f.get(\"line\", 0) > 0:\n print(f\" Line: {f['line']}\")\n print(f\" Fix: {f['fix']}\")\n\n if not findings:\n print(\"\\n No issues found. Values file looks good.\")\n\n print(f\"\\n{'=' * 60}\\n\")\n return result\n\n\ndef main():\n parser = argparse.ArgumentParser(\n description=\"helm-chart-builder: values.yaml best-practice validator\"\n )\n parser.add_argument(\"valuesfile\", nargs=\"?\", help=\"Path to values.yaml (omit for demo)\")\n parser.add_argument(\n \"--output\", \"-o\",\n choices=[\"text\", \"json\"],\n default=\"text\",\n help=\"Output format (default: text)\",\n )\n parser.add_argument(\n \"--strict\",\n action=\"store_true\",\n help=\"Strict mode — elevate warnings to higher severity\",\n )\n args = parser.parse_args()\n\n if args.valuesfile:\n path = Path(args.valuesfile)\n if not path.exists():\n print(f\"Error: File not found: {args.valuesfile}\", file=sys.stderr)\n sys.exit(1)\n content = path.read_text(encoding=\"utf-8\")\n else:\n print(\"No values file provided. Running demo validation...\\n\")\n content = DEMO_VALUES\n\n generate_report(content, args.output, args.strict)\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":14524,"content_sha256":"d8e3d687d9e1ff59f47e413b8445231651843ce3ae2b3f49495216ad2c247482"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Helm Chart Builder","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Production-grade Helm charts. Sensible defaults. Secure by design. No cargo-culting.","type":"text"}]}]},{"type":"paragraph","content":[{"text":"Opinionated Helm workflow that turns ad-hoc Kubernetes manifests into maintainable, testable, reusable charts. Covers chart structure, values design, template patterns, dependency management, and security hardening.","type":"text"}]},{"type":"paragraph","content":[{"text":"Not a Helm tutorial — a set of concrete decisions about how to build charts that operators trust and developers don't fight.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Slash Commands","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Command","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"What it does","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"/helm:create","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Scaffold a production-ready Helm chart with best-practice structure","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"/helm:review","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Analyze an existing chart for issues — missing labels, hardcoded values, template anti-patterns","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"/helm:security","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Audit chart for security issues — RBAC, network policies, pod security, secrets handling","type":"text"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"When This Skill Activates","type":"text"}]},{"type":"paragraph","content":[{"text":"Recognize these patterns from the user:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Create a Helm chart for this service\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Review my Helm chart\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Is this chart secure?\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Design a values.yaml\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Add a subchart dependency\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Set up helm tests\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Helm best practices for [workload type]\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Any request involving: Helm chart, values.yaml, Chart.yaml, templates, helpers, _helpers.tpl, subcharts, helm lint, helm test","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"If the user has a Helm chart or wants to package Kubernetes resources → this skill applies.","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Workflow","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"/helm:create","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Chart Scaffolding","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Identify workload type","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Web service (Deployment + Service + Ingress)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Worker (Deployment, no Service)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CronJob (CronJob + ServiceAccount)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Stateful service (StatefulSet + PVC + Headless Service)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Library chart (no templates, only helpers)","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Scaffold chart structure","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"mychart/\n├── Chart.yaml # Chart metadata and dependencies\n├── values.yaml # Default configuration\n├── values.schema.json # Optional: JSON Schema for values validation\n├── .helmignore # Files to exclude from packaging\n├── templates/\n│ ├── _helpers.tpl # Named templates and helper functions\n│ ├── deployment.yaml # Workload resource\n│ ├── service.yaml # Service exposure\n│ ├── ingress.yaml # Ingress (if applicable)\n│ ├── serviceaccount.yaml # ServiceAccount\n│ ├── hpa.yaml # HorizontalPodAutoscaler\n│ ├── pdb.yaml # PodDisruptionBudget\n│ ├── networkpolicy.yaml # NetworkPolicy\n│ ├── configmap.yaml # ConfigMap (if needed)\n│ ├── secret.yaml # Secret (if needed)\n│ ├── NOTES.txt # Post-install usage instructions\n│ └── tests/\n│ └── test-connection.yaml\n└── charts/ # Subcharts (dependencies)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Apply Chart.yaml best practices","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"METADATA\n├── apiVersion: v2 (Helm 3 only — never v1)\n├── name: matches directory name exactly\n├── version: semver (chart version, not app version)\n├── appVersion: application version string\n├── description: one-line summary of what the chart deploys\n└── type: application (or library for shared helpers)\n\nDEPENDENCIES\n├── Pin dependency versions with ~X.Y.Z (patch-level float)\n├── Use condition field to make subcharts optional\n├── Use alias for multiple instances of same subchart\n└── Run helm dependency update after changes","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Generate values.yaml with documentation","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Every value has an inline comment explaining purpose and type","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Sensible defaults that work for development","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Override-friendly structure (flat where possible, nested only when logical)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No hardcoded cluster-specific values (image registry, domain, storage class)","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Validate","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python3 scripts/chart_analyzer.py mychart/\nhelm lint mychart/\nhelm template mychart/ --debug","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"/helm:review","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Chart Analysis","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check chart structure","type":"text","marks":[{"type":"strong"}]}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Check","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Severity","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fix","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Missing _helpers.tpl","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"High","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Create helpers for common labels and selectors","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No NOTES.txt","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Medium","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add post-install instructions","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No .helmignore","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Low","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Create one to exclude .git, CI files, tests","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Missing Chart.yaml fields","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Medium","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add description, appVersion, maintainers","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Hardcoded values in templates","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"High","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Extract to values.yaml with defaults","type":"text"}]}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check template quality","type":"text","marks":[{"type":"strong"}]}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Check","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Severity","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fix","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Missing standard labels","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"High","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"app.kubernetes.io/*","type":"text","marks":[{"type":"code_inline"}]},{"text":" labels via _helpers.tpl","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No resource requests/limits","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Critical","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add resources section with defaults in values.yaml","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Hardcoded image tag","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"High","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"{{ .Values.image.repository }}:{{ .Values.image.tag }}","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No imagePullPolicy","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Medium","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Default to ","type":"text"},{"text":"IfNotPresent","type":"text","marks":[{"type":"code_inline"}]},{"text":", overridable","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Missing liveness/readiness probes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"High","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add probes with configurable paths and ports","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No pod anti-affinity","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Medium","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add preferred anti-affinity for HA","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Duplicate template code","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Medium","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Extract into named templates in _helpers.tpl","type":"text"}]}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check values.yaml quality","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python3 scripts/values_validator.py mychart/values.yaml","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Generate review report","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"HELM CHART REVIEW — [chart name]\nDate: [timestamp]\n\nCRITICAL: [count]\nHIGH: [count]\nMEDIUM: [count]\nLOW: [count]\n\n[Detailed findings with fix recommendations]","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"/helm:security","type":"text","marks":[{"type":"code_inline"}]},{"text":" — Security Audit","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Pod security audit","type":"text","marks":[{"type":"strong"}]}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Check","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Severity","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fix","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No securityContext","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Critical","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add runAsNonRoot, readOnlyRootFilesystem","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Running as root","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Critical","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Set ","type":"text"},{"text":"runAsNonRoot: true","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"runAsUser: 1000","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Writable root filesystem","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"High","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Set ","type":"text"},{"text":"readOnlyRootFilesystem: true","type":"text","marks":[{"type":"code_inline"}]},{"text":" + emptyDir for tmp","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"All capabilities retained","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"High","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Drop ALL, add only specific needed caps","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Privileged container","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Critical","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Set ","type":"text"},{"text":"privileged: false","type":"text","marks":[{"type":"code_inline"}]},{"text":", use specific capabilities","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No seccomp profile","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Medium","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Set ","type":"text"},{"text":"seccompProfile.type: RuntimeDefault","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"allowPrivilegeEscalation true","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"High","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Set ","type":"text"},{"text":"allowPrivilegeEscalation: false","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"RBAC audit","type":"text","marks":[{"type":"strong"}]}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Check","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Severity","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fix","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No ServiceAccount","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Medium","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Create dedicated SA, don't use default","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"automountServiceAccountToken true","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Medium","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Set to false unless pod needs K8s API access","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"ClusterRole instead of Role","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Medium","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use namespace-scoped Role unless cluster-wide needed","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Wildcard permissions","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Critical","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use specific resource names and verbs","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No RBAC at all","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Low","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Acceptable if pod doesn't need K8s API access","type":"text"}]}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Network and secrets audit","type":"text","marks":[{"type":"strong"}]}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Check","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Severity","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Fix","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No NetworkPolicy","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Medium","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add default-deny ingress + explicit allow rules","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Secrets in values.yaml","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Critical","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Use external secrets operator or sealed-secrets","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"No PodDisruptionBudget","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Medium","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Add PDB with minAvailable for HA workloads","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"hostNetwork: true","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"High","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Remove unless absolutely required (e.g., CNI plugin)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"hostPID or hostIPC","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Critical","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Never use in application charts","type":"text"}]}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Generate security report","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"SECURITY AUDIT — [chart name]\nDate: [timestamp]\n\nCRITICAL: [count]\nHIGH: [count]\nMEDIUM: [count]\nLOW: [count]\n\n[Detailed findings with remediation steps]","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Tooling","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"scripts/chart_analyzer.py","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"CLI utility for static analysis of Helm chart directories.","type":"text"}]},{"type":"paragraph","content":[{"text":"Features:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Chart structure validation (required files, directory layout)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Template anti-pattern detection (hardcoded values, missing labels, no resource limits)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Chart.yaml metadata checks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Standard labels verification (app.kubernetes.io/*)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Security baseline checks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"JSON and text output","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Usage:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Analyze a chart directory\npython3 scripts/chart_analyzer.py mychart/\n\n# JSON output\npython3 scripts/chart_analyzer.py mychart/ --output json\n\n# Security-focused analysis\npython3 scripts/chart_analyzer.py mychart/ --security","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"scripts/values_validator.py","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"CLI utility for validating values.yaml against best practices.","type":"text"}]},{"type":"paragraph","content":[{"text":"Features:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Documentation coverage (inline comments)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Type consistency checks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Hardcoded secrets detection","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Default value quality analysis","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Structure depth analysis","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Naming convention validation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"JSON and text output","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Usage:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Validate values.yaml\npython3 scripts/values_validator.py values.yaml\n\n# JSON output\npython3 scripts/values_validator.py values.yaml --output json\n\n# Strict mode (fail on warnings)\npython3 scripts/values_validator.py values.yaml --strict","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Template Patterns","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Pattern 1: Standard Labels (_helpers.tpl)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"{{/*\nCommon labels for all resources.\n*/}}\n{{- define \"mychart.labels\" -}}\nhelm.sh/chart: {{ include \"mychart.chart\" . }}\napp.kubernetes.io/name: {{ include \"mychart.name\" . }}\napp.kubernetes.io/instance: {{ .Release.Name }}\napp.kubernetes.io/version: {{ .Chart.AppVersion | quote }}\napp.kubernetes.io/managed-by: {{ .Release.Service }}\n{{- end }}\n\n{{/*\nSelector labels (subset of common labels — must be immutable).\n*/}}\n{{- define \"mychart.selectorLabels\" -}}\napp.kubernetes.io/name: {{ include \"mychart.name\" . }}\napp.kubernetes.io/instance: {{ .Release.Name }}\n{{- end }}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Pattern 2: Conditional Resources","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"{{- if .Values.ingress.enabled -}}\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n name: {{ include \"mychart.fullname\" . }}\n labels:\n {{- include \"mychart.labels\" . | nindent 4 }}\n {{- with .Values.ingress.annotations }}\n annotations:\n {{- toYaml . | nindent 4 }}\n {{- end }}\nspec:\n {{- if .Values.ingress.tls }}\n tls:\n {{- range .Values.ingress.tls }}\n - hosts:\n {{- range .hosts }}\n - {{ . | quote }}\n {{- end }}\n secretName: {{ .secretName }}\n {{- end }}\n {{- end }}\n rules:\n {{- range .Values.ingress.hosts }}\n - host: {{ .host | quote }}\n http:\n paths:\n {{- range .paths }}\n - path: {{ .path }}\n pathType: {{ .pathType }}\n backend:\n service:\n name: {{ include \"mychart.fullname\" $ }}\n port:\n number: {{ $.Values.service.port }}\n {{- end }}\n {{- end }}\n{{- end }}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Pattern 3: Security-Hardened Pod Spec","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"spec:\n serviceAccountName: {{ include \"mychart.serviceAccountName\" . }}\n automountServiceAccountToken: false\n securityContext:\n runAsNonRoot: true\n runAsUser: 1000\n fsGroup: 1000\n seccompProfile:\n type: RuntimeDefault\n containers:\n - name: {{ .Chart.Name }}\n securityContext:\n allowPrivilegeEscalation: false\n readOnlyRootFilesystem: true\n capabilities:\n drop:\n - ALL\n image: \"{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}\"\n imagePullPolicy: {{ .Values.image.pullPolicy }}\n resources:\n {{- toYaml .Values.resources | nindent 8 }}\n volumeMounts:\n - name: tmp\n mountPath: /tmp\n volumes:\n - name: tmp\n emptyDir: {}","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Values Design Principles","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"STRUCTURE\n├── Flat over nested (image.tag > container.spec.image.tag)\n├── Group by resource (service.*, ingress.*, resources.*)\n├── Use enabled: true/false for optional resources\n├── Document every key with inline YAML comments\n└── Provide sensible development defaults\n\nNAMING\n├── camelCase for keys (replicaCount, not replica_count)\n├── Boolean keys: use adjectives (enabled, required) not verbs\n├── Nested keys: max 3 levels deep\n└── Match upstream conventions (image.repository, image.tag, image.pullPolicy)\n\nANTI-PATTERNS\n├── Hardcoded cluster URLs or domains\n├── Secrets as default values\n├── Empty strings where null is correct\n├── Deeply nested structures (>3 levels)\n├── Undocumented values\n└── values.yaml that doesn't work without overrides","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Dependency Management","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"SUBCHARTS\n├── Use Chart.yaml dependencies (not requirements.yaml — Helm 3)\n├── Pin versions: version: ~15.x.x (patch float)\n├── Use condition: to make optional: condition: postgresql.enabled\n├── Use alias: for multiple instances of same chart\n├── Override subchart values under subchart name key in values.yaml\n└── Run helm dependency update before packaging\n\nLIBRARY CHARTS\n├── type: library in Chart.yaml — no templates directory\n├── Export named templates only — no rendered resources\n├── Use for shared labels, annotations, security contexts\n└── Version independently from application charts","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Proactive Triggers","type":"text"}]},{"type":"paragraph","content":[{"text":"Flag these without being asked:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No _helpers.tpl","type":"text","marks":[{"type":"strong"}]},{"text":" → Create one. Every chart needs standard labels and fullname helpers.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Hardcoded image tag in template","type":"text","marks":[{"type":"strong"}]},{"text":" → Extract to values.yaml. Tags must be overridable.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No resource requests/limits","type":"text","marks":[{"type":"strong"}]},{"text":" → Add them. Pods without limits can starve the node.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Running as root","type":"text","marks":[{"type":"strong"}]},{"text":" → Add securityContext. No exceptions for production charts.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No NOTES.txt","type":"text","marks":[{"type":"strong"}]},{"text":" → Create one. Users need post-install instructions.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Secrets in values.yaml defaults","type":"text","marks":[{"type":"strong"}]},{"text":" → Remove them. Use placeholders with comments explaining how to provide secrets.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"No liveness/readiness probes","type":"text","marks":[{"type":"strong"}]},{"text":" → Add them. Kubernetes needs to know if the pod is healthy.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Missing app.kubernetes.io labels","type":"text","marks":[{"type":"strong"}]},{"text":" → Add via _helpers.tpl. Required for proper resource tracking.","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Installation","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"One-liner (any tool)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"git clone https://github.com/alirezarezvani/claude-skills.git\ncp -r claude-skills/engineering/helm-chart-builder ~/.claude/skills/","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Multi-tool install","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"./scripts/convert.sh --skill helm-chart-builder --tool codex|gemini|cursor|windsurf|openclaw","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"OpenClaw","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"clawhub install cs-helm-chart-builder","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Related Skills","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"senior-devops","type":"text","marks":[{"type":"strong"}]},{"text":" — Broader DevOps scope (CI/CD, IaC, monitoring). Complementary — use helm-chart-builder for chart-specific work, senior-devops for pipeline and infrastructure.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"docker-development","type":"text","marks":[{"type":"strong"}]},{"text":" — Container building. Complementary — docker-development builds the images, helm-chart-builder deploys them to Kubernetes.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ci-cd-pipeline-builder","type":"text","marks":[{"type":"strong"}]},{"text":" — Pipeline construction. Complementary — helm-chart-builder defines the deployment artifact, ci-cd-pipeline-builder automates its delivery.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"senior-security","type":"text","marks":[{"type":"strong"}]},{"text":" — Application security. Complementary — helm-chart-builder covers Kubernetes-level security (RBAC, pod security), senior-security covers application-level threats.","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"helm-chart-builder","author":"@skillopedia","source":{"stars":16818,"repo_name":"claude-skills","origin_url":"https://github.com/alirezarezvani/claude-skills/blob/HEAD/engineering/helm-chart-builder/skills/helm-chart-builder/SKILL.md","repo_owner":"alirezarezvani","body_sha256":"7e49c7c873f4ee63af9e95bdc860e787bacae31a32135a0e778430bce1e6c17e","cluster_key":"08a29a7371b0498b676a610a420c278b51a54918cf6921a0926e2665aee383b3","clean_bundle":{"format":"clean-skill-bundle-v1","source":"alirezarezvani/claude-skills/engineering/helm-chart-builder/skills/helm-chart-builder/SKILL.md","attachments":[{"id":"5676aa1d-fcba-5ade-954d-bec9045c3dfa","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5676aa1d-fcba-5ade-954d-bec9045c3dfa/attachment.md","path":"references/chart-patterns.md","size":11818,"sha256":"4913cf7bda2a2d5a8f45907c43b6c75f82af946aef88a7a6a88d1e4e3b749861","contentType":"text/markdown; charset=utf-8"},{"id":"5ab26eaa-cb7a-5d19-928d-511a7a449217","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5ab26eaa-cb7a-5d19-928d-511a7a449217/attachment.md","path":"references/values-design.md","size":9736,"sha256":"f4a138e3793c990ccffe2c67092264d0a635d188eec1f9c541170cf50417c688","contentType":"text/markdown; charset=utf-8"},{"id":"84cc42bc-9821-5e72-b885-4ebdb4a5ecbf","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/84cc42bc-9821-5e72-b885-4ebdb4a5ecbf/attachment.py","path":"scripts/chart_analyzer.py","size":20022,"sha256":"5702d86985df261a100ac6b1d9f0b6b5d0aa7bd4f8acd98508e39a19095e6c0d","contentType":"text/x-python; charset=utf-8"},{"id":"b5cd312b-e353-59e0-b057-d37436366c29","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b5cd312b-e353-59e0-b057-d37436366c29/attachment.py","path":"scripts/values_validator.py","size":14524,"sha256":"d8e3d687d9e1ff59f47e413b8445231651843ce3ae2b3f49495216ad2c247482","contentType":"text/x-python; charset=utf-8"}],"bundle_sha256":"934aebebc82050a66374ef8b87897c4bf20acfd212962f8548cee5d7ba19b58d","attachment_count":4,"text_attachments":4,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":3,"skill_md_path":"engineering/helm-chart-builder/skills/helm-chart-builder/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"security","category_label":"Security"},"exact_dupes_collapsed_into_this":2},"license":"MIT","version":"v1","category":"security","metadata":{"author":"Alireza Rezvani","updated":"2026-03-15T00:00:00.000Z","version":"1.0.0","category":"engineering"},"import_tag":"clean-skills-v1","description":"Helm chart development agent skill and plugin for Claude Code, Codex, Gemini CLI, Cursor, OpenClaw — chart scaffolding, values design, template patterns, dependency management, security hardening, and chart testing. Use when: user wants to create or improve Helm charts, design values.yaml files, implement template helpers, audit chart security (RBAC, network policies, pod security), manage subcharts, or run helm lint/test."}},"renderedAt":1782979719193}

Helm Chart Builder Production-grade Helm charts. Sensible defaults. Secure by design. No cargo-culting. Opinionated Helm workflow that turns ad-hoc Kubernetes manifests into maintainable, testable, reusable charts. Covers chart structure, values design, template patterns, dependency management, and security hardening. Not a Helm tutorial — a set of concrete decisions about how to build charts that operators trust and developers don't fight. --- Slash Commands | Command | What it does | |---------|-------------| | | Scaffold a production-ready Helm chart with best-practice structure | | | Anal…