
Helm Validator
- 423 installs
- 286 repo stars
- Updated July 26, 2026
- akin-ozer/cc-devops-skills
helm-validator is a Claude Code agent skill that runs a ten-stage Helm chart validation pipeline for developers who need to catch template, schema, CRD, and packaging errors before Kubernetes release.
About
helm-validator is a read-only DevOps agent skill from akin-ozer/cc-devops-skills that validates Helm charts through ten ordered stages before release. The workflow runs scripts such as setup_tools.sh and validate_chart_structure.sh, then chains helm lint, helm template, yamllint, kubeconform, CRD documentation lookup via detect_crd_wrapper.sh, optional kubectl dry-run, and mandatory security best-practice checks across Chart.yaml, values.yaml, templates, and rendered manifests. Mode A works without a cluster; Mode B adds cluster dry-run when kubectl is available. Developers reach for helm-validator when helm template fails, kubeconform reports schema errors, or CI needs a severity-based pre-deploy report with Passed, Warning, Failed, or Skipped status per stage. The skill continues through partial tool or stage failures to collect complete findings and only applies fixes when explicitly requested, pairing with helm-generator in the same plugin pack for chart scaffolding.
- Helm chart linting and template validation
- Values and manifest consistency checks
- CI-friendly pre-deploy gate for Kubernetes
- Catches broken references before cluster apply
- Pairs with cc-devops-skills release workflows
Helm Validator by the numbers
- 423 all-time installs (skills.sh)
- Ranked #282 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/akin-ozer/cc-devops-skills --skill helm-validatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 423 |
|---|---|
| repo stars | ★ 286 |
| Last updated | July 26, 2026 |
| Repository | akin-ozer/cc-devops-skills ↗ |
How do you validate Helm charts before Kubernetes deploy?
Validate Helm chart templates, values, and Kubernetes manifests before release to catch schema errors, bad references, and packaging issues early in CI.
Who is it for?
DevOps engineers and platform developers who maintain Helm charts and need automated lint, render, schema, and security checks in CI or pre-release review.
Skip if: Developers who only need to scaffold a new Helm chart from scratch should use helm-generator instead of this read-only validator skill.
When should I use this skill?
User asks to validate, lint, test, or troubleshoot a Helm chart, values.yaml, templates, or pre-deployment Kubernetes packaging.
What you get
Ten-stage validation report, rendered manifest directory, kubeconform findings, CRD documentation notes, and categorized error and warning summaries.
- Ten-stage severity validation report
- Rendered manifest output directory
- CRD and security finding summaries
By the numbers
- Runs 10 ordered validation stages from tool check through mandatory final report
- Chains helm lint, helm template, yamllint, and kubeconform in the default local validation path
Files
Helm Chart Validator & Analysis Toolkit
Overview
This skill provides a comprehensive validation and analysis workflow for Helm charts, combining Helm-native linting, template rendering, YAML validation, schema validation, CRD documentation lookup, and security best practices checking.
IMPORTANT: This validator is read-only by default. It analyzes charts and proposes improvements. Only modify files when the user explicitly asks to apply fixes.
Trigger Cases
Use this skill when one or more of these top cases apply:
- The user asks to validate, lint, check, test, or troubleshoot a Helm chart
- Helm templates fail to render, lint, or produce valid Kubernetes YAML
- A pre-deployment quality gate is needed (schema, dry-run, security checks)
- CRD resources are present and their spec fields must be verified against docs
- The user wants a severity-based validation report with proposed remediations
Trigger phrase examples:
- "Validate this Helm chart before release"
- "Why does
helm templatefail?" - "Check this chart for Kubernetes and security issues"
Out of scope by default:
- New chart scaffolding or broad chart generation (use
helm-generator)
Role Boundaries
- This skill validates and reports; it does not silently rewrite user files.
- It can propose concrete patches and apply them only when the user explicitly requests fixes.
- If execution constraints block a stage, it must continue with reachable stages and document the skip reason.
Execution Model
1. Run stages in order (1 through 10). 2. Keep going after stage-level failures to collect complete findings, unless rendering fails and no manifests exist. 3. If Stage 4 produces no manifests, mark Stages 5 to 9 as blocked and continue to Stage 10 reporting. 4. Treat Stage 8 as environment-dependent optional; treat Stage 9 and Stage 10 as mandatory when manifests exist. 5. For every skipped stage, record the exact tool/environment reason in the final summary table.
Quick Execution Modes
Mode A: Local Validation (no cluster required)
bash scripts/setup_tools.sh
bash scripts/validate_chart_structure.sh <chart-directory>
helm lint <chart-directory> --strict
helm template <release-name> <chart-directory> --values <values-file> --debug --output-dir ./rendered
find ./rendered -type f \( -name "*.yaml" -o -name "*.yml" \) -exec yamllint -c assets/.yamllint {} +
find ./rendered -type f \( -name "*.yaml" -o -name "*.yml" \) -exec kubeconform -summary -verbose {} +Mode B: Full Validation (cluster available)
Run Mode A plus Stage 8 dry-run commands in this document.
Validation & Testing Workflow
Follow this sequential validation workflow. Each stage catches different types of issues:
Stage 1: Tool Check
Before starting validation, verify required tools are installed:
bash scripts/setup_tools.shRequired tools:
- helm: Helm package manager for Kubernetes (v3+)
- yamllint: YAML syntax and style linting
- kubeconform: Kubernetes schema validation with CRD support
- kubectl: Cluster dry-run testing (optional but recommended)
Fallback policy for unavailable tools or environment constraints:
| Condition | Action | Stage status |
|---|---|---|
helm missing | Run Stage 2 only, then report Stages 3 to 9 as skipped/blocked | ⚠️ Warning |
yamllint missing | Use yq syntax checks if available; otherwise skip Stage 5 | ⚠️ Warning |
kubeconform missing | Skip Stage 7 and rely on Stage 6 CRD/manual checks | ⚠️ Warning |
kubectl missing or no kube-context | Skip Stage 8, continue with remaining stages | ⚠️ Warning |
| No internet access for CRD docs | Use local CRD manifests and kubeconform output, mark doc lookup incomplete | ⚠️ Warning |
If tools are missing, provide installation instructions from scripts/setup_tools.sh output and continue with the fallback path above.
Stage 2: Helm Chart Structure Validation
Verify the chart follows the standard Helm directory structure:
bash scripts/validate_chart_structure.sh <chart-directory>Expected structure:
mychart/
Chart.yaml # Chart metadata (required)
values.yaml # Default values (required)
values.schema.json # JSON Schema for values validation (optional)
templates/ # Template directory (required)
_helpers.tpl # Template helpers (recommended)
NOTES.txt # Post-install notes (recommended)
*.yaml # Kubernetes manifest templates
charts/ # Chart dependencies (optional)
crds/ # Custom Resource Definitions (optional)
.helmignore # Files to ignore during packaging (optional)Common issues caught:
- Missing required files (Chart.yaml, values.yaml, templates/)
- Invalid Chart.yaml syntax or missing required fields
- Malformed values.schema.json
- Incorrect file permissions
Stage 3: Helm Lint
Run Helm's built-in linter to catch chart-specific issues:
helm lint <chart-directory> --strictOptional flags:
--values <values-file>: Test with specific values--set key=value: Override specific values--debug: Show detailed error information
Common issues caught:
- Invalid Chart.yaml metadata
- Template syntax errors
- Missing or undefined values
- Deprecated Kubernetes API versions
- Chart best practice violations
Auto-fix approach:
- For template errors, identify the problematic template file
- Show the user the specific line causing issues
- Propose a patch/diff for the fix
- Apply fixes only if the user explicitly asks
- Re-run
helm lintafter fixes are applied
Stage 4: Template Rendering
Render templates locally to verify they produce valid YAML:
helm template <release-name> <chart-directory> \
--values <values-file> \
--debug \
--output-dir ./renderedOptions to consider:
--values values.yaml: Use specific values file--set key=value: Override individual values--show-only templates/deployment.yaml: Render specific template--validate: Validate against Kubernetes OpenAPI schema--include-crds: Include CRDs in rendered output--is-upgrade: Simulate upgrade scenario--kube-version 1.28.0: Target specific Kubernetes version
Common issues caught:
- Template syntax errors (Go template issues)
- Undefined variables or values
- Type mismatches (string vs. integer)
- Missing required values
- Logic errors in conditionals or loops
- Incorrect indentation in nested templates
For template errors:
- Identify the template file and line number
- Check if values are properly defined in values.yaml
- Verify template function usage (quote, required, default, include, etc.)
- Test with different value combinations
Stage 5: YAML Syntax Validation
Validate YAML syntax and formatting of rendered templates:
find ./rendered -type f \( -name "*.yaml" -o -name "*.yml" \) \
-exec yamllint -c assets/.yamllint {} +Common issues caught:
- Indentation errors (tabs vs spaces)
- Trailing whitespace
- Line length violations
- Syntax errors
- Duplicate keys
- Document start/end markers
Auto-fix approach:
- For simple issues (indentation, trailing spaces), propose fixes using the Edit tool
- For template-generated issues, fix the source template, not rendered output
- Always show the user what will be changed before applying fixes
Stage 6: CRD Detection and Documentation Lookup
Before schema validation, detect if the chart contains or renders Custom Resource Definitions:
# Check crds/ directory
if [ -d <chart-directory>/crds ]; then
find <chart-directory>/crds -type f \( -name "*.yaml" -o -name "*.yml" \) \
-exec bash scripts/detect_crd_wrapper.sh {} +
fi
# Check rendered templates
find ./rendered -type f \( -name "*.yaml" -o -name "*.yml" \) \
-exec bash scripts/detect_crd_wrapper.sh {} +The script outputs JSON with resource information:
[
{
"kind": "Certificate",
"apiVersion": "cert-manager.io/v1",
"group": "cert-manager.io",
"version": "v1",
"isCRD": true,
"name": "example-cert"
}
]For each detected CRD:
1. Try context7 MCP first (preferred):
Use mcp__context7__resolve-library-id with the CRD project name
Example: "cert-manager" for cert-manager.io CRDs
"prometheus-operator" for monitoring.coreos.com CRDs
"istio" for networking.istio.io CRDs
Then use mcp__context7__query-docs with:
- libraryId from resolve step
- query: The CRD kind and relevant features (e.g., "Certificate spec required fields")2. Fallback to `web.search_query` (web search) if Context7 fails:
Search query pattern:
"<kind>" "<group>" kubernetes CRD "<version>" documentation spec
Example:
"Certificate" "cert-manager.io" kubernetes CRD "v1" documentation spec
"Prometheus" "monitoring.coreos.com" kubernetes CRD "v1" documentation spec3. Extract key information:
- Required fields in
spec - Field types and validation rules
- Examples from documentation
- Version-specific changes or deprecations
- Common configuration patterns
Why this matters: CRDs have custom schemas not available in standard Kubernetes validation tools. Understanding the CRD's spec requirements prevents validation errors and ensures correct resource configuration.
Stage 7: Schema Validation
Validate rendered templates against Kubernetes schemas:
find ./rendered -type f \( -name "*.yaml" -o -name "*.yml" \) -exec \
kubeconform \
-schema-location default \
-schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \
-summary \
-verbose \
{} +Options to consider:
- Add
-strictto reject unknown fields (recommended for production) - Add
-ignore-missing-schemasif working with custom/internal CRDs - Add
-kubernetes-version 1.28.0to validate against specific K8s version - Add
-output jsonfor programmatic processing
Common issues caught:
- Invalid apiVersion or kind
- Missing required fields
- Wrong field types
- Invalid enum values
- Unknown fields (with -strict)
For CRDs: If kubeconform reports "no schema found", this is expected. Use the documentation from Stage 6 to manually validate the spec fields.
Stage 7 success criteria (explicit):
- ✅ Passed:
kubeconformexits0, and no invalid resources are reported. - ⚠️ Warning: only CRD schema-missing findings remain and Stage 6 documentation/manual verification is completed.
- ❌ Failed: any non-CRD schema violation, parse error, or unresolved required-field/type error.
Stage 8: Cluster Dry-Run (if available)
If kubectl is configured and cluster access is available, perform a server-side dry-run:
# Test installation
helm install <release-name> <chart-directory> \
--dry-run=server \
--debug \
--values <values-file>
# Test upgrade
helm upgrade <release-name> <chart-directory> \
--dry-run=server \
--debug \
--values <values-file>If the Helm version does not support --dry-run=server, use --dry-run and document that only client-side Helm simulation was executed.
This catches:
- Admission controller rejections
- Policy violations (PSP, OPA, Kyverno, etc.)
- Resource quota violations
- Missing namespaces
- Invalid ConfigMap/Secret references
- Webhook validations
- Existing resource conflicts
If dry-run is not possible:
- Use kubectl with rendered templates:
kubectl apply --dry-run=server -f ./rendered/ - Skip if no cluster access
- Document that cluster-specific validation was skipped
For updates to existing releases:
helm diff upgrade <release-name> <chart-directory>This shows what would change, helping catch unintended modifications. (Requires helm-diff plugin)
Stage 8 success criteria (explicit):
- ✅ Passed: dry-run install and upgrade commands exit
0with no admission/policy errors. - ⚠️ Warning: stage skipped because
kubectl/cluster context/access is unavailable, or only client-side fallback was possible. - ❌ Failed: dry-run commands return non-zero due to admission webhooks, policy violations, namespace/quota errors, or reference errors.
Stage 9: Security Best Practices Check (MANDATORY)
IMPORTANT: This stage is MANDATORY. Analyze rendered templates for security best practices compliance.
Check rendered Deployment/Pod templates for:
1. Missing securityContext - Look for pods/containers without security settings:
# Check if pod-level securityContext exists
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 20002. Missing container securityContext - Each container should have:
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop:
- ALL3. Missing resource limits/requests - Check for:
resources:
limits:
cpu: "100m"
memory: "128Mi"
requests:
cpu: "100m"
memory: "128Mi"4. Image tag issues - Flag if using :latest or no tag
5. Missing probes - Check for liveness/readiness probes
How to check: Read the rendered deployment YAML files and grep for these patterns:
# Check for securityContext
find ./rendered -type f \( -name "*.yaml" -o -name "*.yml" \) \
-exec grep -l "securityContext" {} +
# Check for resources
find ./rendered -type f \( -name "*.yaml" -o -name "*.yml" \) \
-exec grep -l "resources:" {} +
# Check for latest tag
find ./rendered -type f \( -name "*.yaml" -o -name "*.yml" \) \
-exec grep "image:.*:latest" {} +Stage 10: Final Report (MANDATORY)
IMPORTANT: This stage is MANDATORY even if all validations pass. You MUST complete ALL of the following actions.
Default behavior is read-only. Do not modify files unless the user explicitly asks you to apply fixes.
Step 1: Load Reference Files (MANDATORY when warnings exist)
If ANY warnings, errors, or security issues were found, you MUST read:
Read references/helm_best_practices.md
Read references/k8s_best_practices.mdUse these references to provide context and recommendations for each issue found.
Step 2: Present Validation Summary
Always present a validation summary formatted as a table showing:
- Each validation stage executed (Stages 1-9)
- Status of each stage (✅ Passed, ⚠️ Warning, ❌ Failed)
- Count of issues found per stage
Example:
| Stage | Status | Issues |
|-------|--------|--------|
| 1. Tool Check | ✅ Passed | All tools available |
| 2. Structure | ⚠️ Warning | Missing: .helmignore, NOTES.txt |
| 3. Helm Lint | ✅ Passed | 0 errors |
| 4. Template Render | ✅ Passed | 5 templates rendered |
| 5. YAML Syntax | ✅ Passed | No yamllint errors |
| 6. CRD Detection | ✅ Passed | 1 CRD documented |
| 7. Schema Validation | ✅ Passed | All resources valid |
| 8. Dry-Run | ✅ Passed | No cluster errors |
| 9. Security Check | ⚠️ Warning | Missing securityContext |Step 3: Categorize All Issues
Group findings by severity:
❌ Errors (must fix):
- Template syntax errors
- Missing required fields
- Schema validation failures
- Dry-run failures
⚠️ Warnings (should fix):
- Deprecated Kubernetes APIs
- Missing securityContext
- Missing resource limits/requests
- Using
:latestimage tag - Missing recommended files (_helpers.tpl, .helmignore, NOTES.txt)
ℹ️ Info (recommendations):
- Missing values.schema.json
- Missing README.md
- Optimization opportunities
Step 4: List Proposed Changes (DO NOT APPLY)
For each issue, provide a proposed fix with:
- File path and line number (if applicable)
- Before/after code blocks
- Explanation of why this change is recommended
Example format:
## Proposed Changes
### 1. Add securityContext to Deployment
**File:** templates/deployment.yaml:25
**Severity:** ⚠️ Warning
**Reason:** Running containers as root is a security risk
**Current:**spec: containers:
- name: app
image: nginx:1.21
**Proposed:**spec: securityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 2000 containers:
- name: app
image: nginx:1.21 securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop:
- ALL
### 2. Add .helmignore file
**File:** .helmignore (new file)
**Severity:** ⚠️ Warning
**Reason:** Excludes unnecessary files from chart packaging
**Proposed:** Copy from `assets/.helmignore`Step 5: Automation Opportunities
List all detected automation opportunities:
- If
_helpers.tplis missing → Recommend:bash scripts/generate_helpers.sh <chart> - If
.helmignoreis missing → Recommend: Copy fromassets/.helmignore - If
values.schema.jsonis missing → Recommend: Copy and customize fromassets/values.schema.json - If
NOTES.txtis missing → Recommend: Create post-install notes template - If
README.mdis missing → Recommend: Create chart documentation
Step 6: Final Summary
Provide a final summary:
## Validation Summary
**Chart:** <chart-name>
**Status:** ⚠️ Warnings Found (or ✅ Ready for Deployment)
**Issues Found:**
- Errors: X
- Warnings: Y
- Info: Z
**Proposed Changes:** N changes recommended
**Next Steps:**
1. Review proposed changes above
2. Apply changes manually or use helm-generator skill
3. Re-run validation to confirm fixesWorkflow Done Criteria
Validation is complete only when all of the following are true:
- A Stage 1 to Stage 10 status table is present with
✅ Passed,⚠️ Warning,❌ Failed, or⏭️ Skippedfor each stage. - Every skipped stage includes a concrete tool or environment reason.
- Stage 7 and Stage 8 are evaluated against their explicit success criteria above.
- Severity totals are reported (
Errors,Warnings,Info) with proposed remediation actions. - Role boundary is respected: no file edits unless explicitly requested by the user.
Helm Templating Automation & Best Practices
This section covers advanced Helm templating techniques, helper functions, and automation strategies.
Template Helpers (_helpers.tpl)
Template helpers are reusable functions defined in templates/_helpers.tpl. They promote DRY principles and consistency.
Standard helper patterns:
1. Chart name helper:
{{/*
Expand the name of the chart.
*/}}
{{- define "mychart.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}2. Fullname helper:
{{/*
Create a default fully qualified app name.
*/}}
{{- define "mychart.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}3. Chart reference helper:
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "mychart.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}4. Standard labels helper:
{{/*
Common labels
*/}}
{{- define "mychart.labels" -}}
helm.sh/chart: {{ include "mychart.chart" . }}
{{ include "mychart.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}5. Selector labels helper:
{{/*
Selector labels
*/}}
{{- define "mychart.selectorLabels" -}}
app.kubernetes.io/name: {{ include "mychart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}6. ServiceAccount name helper:
{{/*
Create the name of the service account to use
*/}}
{{- define "mychart.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "mychart.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}When to create helpers:
- Values used in multiple templates
- Complex logic that's repeated
- Label sets that should be consistent
- Name generation patterns
- Conditional resource inclusion
Essential Template Functions
Reference and use these Helm template functions for robust charts:
1. `required` - Enforce required values:
apiVersion: v1
kind: Service
metadata:
name: {{ required "A valid service name is required!" .Values.service.name }}2. `default` - Provide fallback values:
replicas: {{ .Values.replicaCount | default 1 }}3. `quote` - Safely quote string values:
env:
- name: DATABASE_HOST
value: {{ .Values.database.host | quote }}4. `include` - Use helpers with pipeline:
metadata:
labels:
{{- include "mychart.labels" . | nindent 4 }}5. `tpl` - Render strings as templates:
{{- tpl .Values.customConfig . }}6. `toYaml` - Convert objects to YAML:
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 2 }}
{{- end }}7. `fromYaml` - Parse YAML strings:
{{- $config := .Values.configYaml | fromYaml }}8. `merge` - Merge maps:
{{- $merged := merge .Values.override .Values.defaults }}9. `lookup` - Query cluster resources (use carefully):
{{- $secret := lookup "v1" "Secret" .Release.Namespace "my-secret" }}
{{- if $secret }}
# Secret exists, use it
{{- else }}
# Create new secret
{{- end }}Advanced Template Patterns
1. Conditional resource creation:
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
# ... ingress definition
{{- end }}2. Range over lists:
{{- range .Values.extraEnvVars }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}3. Range over maps:
{{- range $key, $value := .Values.configMap }}
{{ $key }}: {{ $value | quote }}
{{- end }}4. With blocks for scoping:
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 2 }}
{{- end }}5. Named templates with custom context:
{{- include "mychart.container" (dict "root" . "container" .Values.mainContainer) }}Values Structure Best Practices
Prefer flat structures when possible:
# Good - Flat structure
serverName: nginx
serverPort: 80
# Acceptable - Nested structure for related settings
server:
name: nginx
port: 80
replicas: 3Always provide defaults in values.yaml:
replicaCount: 1
image:
repository: nginx
pullPolicy: IfNotPresent
tag: "1.21.0"
service:
type: ClusterIP
port: 80
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 100m
memory: 128MiDocument all values:
# replicaCount is the number of pod replicas for the deployment
replicaCount: 1
# image configures the container image
image:
# image.repository is the container image registry and name
repository: nginx
# image.tag overrides the image tag (default is chart appVersion)
tag: "1.21.0"Template Comments and Documentation
Use Helm template comments for documentation:
{{- /*
mychart.fullname generates the fullname for resources.
It supports nameOverride and fullnameOverride values.
Usage: {{ include "mychart.fullname" . }}
*/ -}}
{{- define "mychart.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}Use YAML comments for user-facing notes:
# WARNING: Changing the storage class will not migrate existing data
storageClass: "standard"Whitespace Management
Use - to chomp whitespace in template directives:
{{- if .Values.enabled }}
# Remove leading whitespace
{{- end }}
{{ .Values.name -}}
# Remove trailing whitespaceGood formatting:
{{- if .Values.enabled }}
key: value
{{- end }}Bad formatting:
{{if .Values.enabled}}
key: value
{{end}}Helper Patterns Reference
When analyzing charts, identify opportunities for helper functions:
1. Identify repetition:
- Same label sets across resources
- Repeated name generation logic
- Common conditional patterns
2. Common helper patterns to recommend:
- Chart name helper (
.name) - Fullname helper (
.fullname) - Chart version label (
.chart) - Common labels (
.labels) - Selector labels (
.selectorLabels) - ServiceAccount name (
.serviceAccountName)
3. When to recommend helpers:
- Missing
_helpers.tplfile - Repeated code patterns across templates
- Inconsistent label usage
- Long resource names that need truncation
Best Practices Reference
For detailed Helm and Kubernetes best practices, load the references:
Read references/helm_best_practices.md
Read references/k8s_best_practices.mdThese references include:
- Chart structure and metadata
- Template conventions and patterns
- Values file organization
- Security best practices
- Resource limits and requests
- Common validation issues and fixes
When to load: When validation reveals issues that need context, when implementing new features, or when the user asks about best practices.
Working with Chart Dependencies
When a chart has dependencies (in Chart.yaml or charts/ directory):
1. Update dependencies:
helm dependency update <chart-directory>2. List dependencies:
helm dependency list <chart-directory>3. Validate dependencies:
- Check that dependency versions are available
- Verify dependency values are properly scoped
- Test templates with dependency resources
4. Override dependency values:
# values.yaml
postgresql:
enabled: true
postgresqlPassword: "secret"
persistence:
size: 10GiError Handling Strategies
Tool Not Available
- Run
scripts/setup_tools.shto check availability - Provide installation instructions
- Skip optional stages but document what was skipped
- Continue with available tools
Template Rendering Errors
- Show the specific template file and line number
- Check if values are defined in values.yaml
- Verify template function syntax
- Test with simpler value combinations
- Use
--debugflag for detailed error messages
Cluster Access Issues
- Fall back to client-side validation
- Use rendered templates with kubectl
- Skip cluster validation if no kubectl config
- Document limitations in validation report
CRD Documentation Not Found
- Document that documentation lookup failed
- Attempt validation with kubeconform CRD schemas
- Suggest manual CRD inspection:
kubectl get crd <crd-name>.group -o yaml
kubectl explain <kind>Validation Stage Failures
- Continue to next stage even if one fails
- Collect all errors before presenting to user
- Prioritize fixing Helm lint errors first
- Then fix template errors
- Finally fix schema/validation errors
macOS Extended Attributes Issue
Symptom: Helm reports "Chart.yaml file is missing" even though the file exists and is readable.
Cause: On macOS, files created programmatically (via Write tool, scripts, or certain editors) may have extended attributes (e.g., com.apple.provenance, com.apple.quarantine) that interfere with Helm's file detection.
Diagnosis:
# Check for extended attributes
xattr /path/to/chart/Chart.yaml
# If attributes are present, you'll see output like:
# com.apple.provenance
# com.apple.quarantineSolutions:
1. Remove extended attributes:
# Remove all extended attributes from a file
xattr -c /path/to/chart/Chart.yaml
# Remove all extended attributes recursively from chart directory
xattr -cr /path/to/chart/2. Create files using shell commands instead:
# Use cat with heredoc instead of direct file writes
cat > Chart.yaml << 'EOF'
apiVersion: v2
name: mychart
version: 0.1.0
EOF3. Copy from helm-created chart:
# Create a fresh chart and copy structure
helm create temp-chart
cp -r temp-chart/* /path/to/your/chart/
rm -rf temp-chartPrevention: When creating new chart files on macOS, prefer using helm create as a base or use shell heredocs (cat > file << 'EOF') rather than direct file creation tools.
Communication Guidelines
When presenting validation results and fixes:
1. Be clear and concise about what was found 2. Explain why issues matter (e.g., "This will cause pod creation to fail") 3. Provide context from Helm best practices when relevant 4. Group related issues (e.g., all missing helper issues together) 5. Use file:line references when available 6. Show confidence level for auto-fixes (high confidence = syntax, low = logic changes) 7. Always provide a summary after proposing fixes (and after applying fixes when explicitly requested) including:
- What was changed and why
- File and line references for each fix
- Total count of issues resolved
- Final validation status
- Any remaining warnings or recommendations
Version Awareness
Always consider Kubernetes and Helm version compatibility:
- Check for deprecated Kubernetes APIs
- Ensure Helm chart apiVersion is v2 (for Helm 3+)
- For CRDs, ensure the apiVersion matches what's in the cluster
- Use
kubectl api-versionsto list available API versions - Reference version-specific documentation when available
- Set
kubeVersionconstraint in Chart.yaml if needed
Chart Testing
For comprehensive testing, use Helm test resources:
1. Create test resources:
# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "mychart.fullname" . }}-test-connection"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "mychart.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never2. Run tests:
helm test <release-name>Automation Opportunities Reference
During Stage 10 (Final Report), list all detected automation opportunities in the summary.
Do NOT ask user questions or modify files. Simply list recommendations.
Automation opportunities to detect and list:
| Missing Item | Recommendation |
|---|---|
_helpers.tpl | Run: bash scripts/generate_helpers.sh <chart> |
.helmignore | Copy from: assets/.helmignore |
values.schema.json | Copy and customize from: assets/values.schema.json |
NOTES.txt | Create post-install notes template |
README.md | Create chart documentation |
| Repeated patterns | Extract to helper functions |
Security recommendations to include when issues found:
| Issue | Recommendation |
|---|---|
| Missing pod securityContext | Add runAsNonRoot: true, runAsUser: 1000, fsGroup: 2000 |
| Missing container securityContext | Add allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, capabilities.drop: [ALL] |
| Missing resource limits | Add CPU/memory limits and requests |
Using :latest tag | Pin to specific image version |
| Missing probes | Add liveness and readiness probes |
Template improvement recommendations:
| Issue | Recommendation |
|---|---|
Using template instead of include | Replace with include for pipeline support |
Missing nindent | Add nindent for proper YAML indentation |
| No default values | Add default function for optional values |
Missing required function | Add required for critical values |
Resources
scripts/
setup_tools.sh
- Checks for required validation tools (helm, yamllint, kubeconform, kubectl)
- Provides installation instructions for missing tools
- Verifies versions of installed tools
- Usage:
bash scripts/setup_tools.sh
validate_chart_structure.sh
- Validates Helm chart directory structure
- Checks for required files (Chart.yaml, values.yaml, templates/)
- Verifies file formats and syntax
- Usage:
bash scripts/validate_chart_structure.sh <chart-directory>
detect_crd_wrapper.sh
- Wrapper script that handles Python dependency management
- Automatically creates temporary venv if PyYAML is not available
- Calls detect_crd.py to parse YAML files
- Usage:
bash scripts/detect_crd_wrapper.sh <file.yaml> [file2.yaml ...]
detect_crd.py
- Parses YAML files to identify Custom Resource Definitions
- Extracts kind, apiVersion, group, and version information
- Outputs JSON for programmatic processing
- Requires PyYAML (handled automatically by wrapper script)
- Can be called directly:
python3 scripts/detect_crd.py <file.yaml> [file2.yaml ...]
generate_helpers.sh
- Generates standard Helm helpers (_helpers.tpl) for a chart
- Creates fullname, labels, and selector helpers
- Usage:
bash scripts/generate_helpers.sh <chart-directory>
references/
helm_best_practices.md
- Comprehensive guide to Helm chart best practices
- Covers template patterns, helper functions, values structure
- Common validation issues and how to fix them
- Security and performance recommendations
- Load when providing context for Helm-specific issues
k8s_best_practices.md
- Comprehensive guide to Kubernetes YAML best practices
- Covers metadata, labels, resource limits, security context
- Common validation issues and how to fix them
- Load when providing context for Kubernetes-specific issues
template_functions.md
- Reference guide for Helm template functions
- Examples of all built-in functions
- Sprig function library reference
- Custom function patterns
- Load when implementing complex templates
assets/
.helmignore
- Standard .helmignore file for excluding files from packaging
- Pre-configured with common patterns
.yamllint
- Pre-configured yamllint rules for Kubernetes YAML
- Follows Kubernetes conventions (2-space indentation, line length, etc.)
- Can be customized per project
- Usage:
yamllint -c assets/.yamllint <file.yaml>
values.schema.json
- Example JSON Schema for values validation
- Can be copied and customized for specific charts
- Provides type safety and validation
# Rendered templates output
/rendered/
rendered-*.yaml
# Test artifacts
/tmp/
# Helm artifacts
*.tgz
charts/*.tgz
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
*.sublime-project
*.sublime-workspace
# OS files
.DS_Store
Thumbs.db
# Documentation
*.md
!README.md
OWNERS
# CI/CD
.github/
.gitlab-ci.yml
.travis.yml
.circleci/
Jenkinsfile
# Test files (only match test-related YAML, not directories as this can conflict with chart paths)
*_test.yaml
*_test.yml
# Development
.editorconfig
.golangci.yml
# Helm-related
*.lock
requirements.lock
# Examples
examples/
example-values.yaml
values-*.yaml
!values.yaml
# yamllint configuration for Kubernetes YAML files
# This config follows Kubernetes best practices
extends: default
rules:
# Allow longer lines for Kubernetes resources
line-length:
max: 120
level: warning
# Kubernetes uses 2-space indentation
indentation:
spaces: 2
indent-sequences: true
# Allow comments without space from content
comments:
min-spaces-from-content: 1
# Allow document start markers (helpful for multi-resource files)
document-start: disable
# Allow truthy values like "yes", "on" (common in K8s)
truthy:
allowed-values: ['true', 'false', 'yes', 'no', 'on', 'off']
# Kubernetes YAMLs often have long lines in annotations/labels
comments-indentation: {}
# Allow empty values (common in K8s for optional fields)
empty-values:
forbid-in-block-mappings: false
forbid-in-flow-mappings: false
# K8s uses hyphens in resource names
key-duplicates: {}
# Disallow trailing spaces (including comments) to keep manifests clean
trailing-spaces: {}
# Be strict about new lines at end of file
new-line-at-end-of-file: enable
# K8s resources often have complex nested structures
braces:
max-spaces-inside: 1
brackets:
max-spaces-inside: 1
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Helm Chart Values Schema",
"description": "JSON Schema for validating Helm chart values. This is an example that should be customized for your specific chart.",
"type": "object",
"properties": {
"replicaCount": {
"description": "Number of pod replicas",
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 1
},
"image": {
"description": "Container image configuration",
"type": "object",
"properties": {
"repository": {
"description": "Container image repository",
"type": "string",
"pattern": "^[a-z0-9.-]+(/[a-z0-9._-]+)*$"
},
"pullPolicy": {
"description": "Image pull policy",
"type": "string",
"enum": ["Always", "IfNotPresent", "Never"],
"default": "IfNotPresent"
},
"tag": {
"description": "Container image tag",
"type": "string",
"default": ""
}
},
"required": ["repository"]
},
"imagePullSecrets": {
"description": "List of image pull secrets",
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"required": ["name"]
},
"default": []
},
"nameOverride": {
"description": "Override the chart name",
"type": "string",
"default": ""
},
"fullnameOverride": {
"description": "Override the full resource name",
"type": "string",
"default": ""
},
"serviceAccount": {
"description": "Service account configuration",
"type": "object",
"properties": {
"create": {
"description": "Create a service account",
"type": "boolean",
"default": true
},
"annotations": {
"description": "Annotations for service account",
"type": "object",
"additionalProperties": {
"type": "string"
},
"default": {}
},
"name": {
"description": "Service account name",
"type": "string",
"default": ""
}
}
},
"podAnnotations": {
"description": "Annotations for pods",
"type": "object",
"additionalProperties": {
"type": "string"
},
"default": {}
},
"podSecurityContext": {
"description": "Security context for pods",
"type": "object",
"properties": {
"runAsNonRoot": {
"type": "boolean"
},
"runAsUser": {
"type": "integer",
"minimum": 0
},
"fsGroup": {
"type": "integer",
"minimum": 0
}
}
},
"securityContext": {
"description": "Security context for containers",
"type": "object",
"properties": {
"capabilities": {
"type": "object",
"properties": {
"drop": {
"type": "array",
"items": {
"type": "string"
}
},
"add": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"readOnlyRootFilesystem": {
"type": "boolean"
},
"runAsNonRoot": {
"type": "boolean"
},
"runAsUser": {
"type": "integer",
"minimum": 0
}
}
},
"service": {
"description": "Service configuration",
"type": "object",
"properties": {
"type": {
"description": "Service type",
"type": "string",
"enum": ["ClusterIP", "NodePort", "LoadBalancer", "ExternalName"],
"default": "ClusterIP"
},
"port": {
"description": "Service port",
"type": "integer",
"minimum": 1,
"maximum": 65535,
"default": 80
},
"targetPort": {
"description": "Target port on the pod",
"anyOf": [
{"type": "integer", "minimum": 1, "maximum": 65535},
{"type": "string"}
]
},
"nodePort": {
"description": "Node port for NodePort service",
"type": "integer",
"minimum": 30000,
"maximum": 32767
}
},
"required": ["type", "port"]
},
"ingress": {
"description": "Ingress configuration",
"type": "object",
"properties": {
"enabled": {
"description": "Enable ingress",
"type": "boolean",
"default": false
},
"className": {
"description": "Ingress class name",
"type": "string",
"default": ""
},
"annotations": {
"description": "Ingress annotations",
"type": "object",
"additionalProperties": {
"type": "string"
},
"default": {}
},
"hosts": {
"description": "Ingress hosts",
"type": "array",
"items": {
"type": "object",
"properties": {
"host": {
"type": "string",
"format": "hostname"
},
"paths": {
"type": "array",
"items": {
"type": "object",
"properties": {
"path": {
"type": "string"
},
"pathType": {
"type": "string",
"enum": ["Prefix", "Exact", "ImplementationSpecific"]
}
},
"required": ["path", "pathType"]
}
}
},
"required": ["host", "paths"]
},
"default": []
},
"tls": {
"description": "Ingress TLS configuration",
"type": "array",
"items": {
"type": "object",
"properties": {
"secretName": {
"type": "string"
},
"hosts": {
"type": "array",
"items": {
"type": "string",
"format": "hostname"
}
}
},
"required": ["hosts"]
},
"default": []
}
}
},
"resources": {
"description": "Resource limits and requests",
"type": "object",
"properties": {
"limits": {
"type": "object",
"properties": {
"cpu": {
"type": "string",
"pattern": "^[0-9]+(m|[0-9]*\\.?[0-9]+)?$"
},
"memory": {
"type": "string",
"pattern": "^[0-9]+(Mi|Gi|Ki|M|G|K)?$"
}
}
},
"requests": {
"type": "object",
"properties": {
"cpu": {
"type": "string",
"pattern": "^[0-9]+(m|[0-9]*\\.?[0-9]+)?$"
},
"memory": {
"type": "string",
"pattern": "^[0-9]+(Mi|Gi|Ki|M|G|K)?$"
}
}
}
}
},
"autoscaling": {
"description": "Horizontal pod autoscaling configuration",
"type": "object",
"properties": {
"enabled": {
"description": "Enable autoscaling",
"type": "boolean",
"default": false
},
"minReplicas": {
"description": "Minimum number of replicas",
"type": "integer",
"minimum": 1,
"default": 1
},
"maxReplicas": {
"description": "Maximum number of replicas",
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 10
},
"targetCPUUtilizationPercentage": {
"description": "Target CPU utilization percentage",
"type": "integer",
"minimum": 1,
"maximum": 100
},
"targetMemoryUtilizationPercentage": {
"description": "Target memory utilization percentage",
"type": "integer",
"minimum": 1,
"maximum": 100
}
}
},
"nodeSelector": {
"description": "Node selector for pod assignment",
"type": "object",
"additionalProperties": {
"type": "string"
},
"default": {}
},
"tolerations": {
"description": "Tolerations for pod assignment",
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"operator": {
"type": "string",
"enum": ["Equal", "Exists"]
},
"value": {
"type": "string"
},
"effect": {
"type": "string",
"enum": ["NoSchedule", "PreferNoSchedule", "NoExecute"]
},
"tolerationSeconds": {
"type": "integer",
"minimum": 0
}
}
},
"default": []
},
"affinity": {
"description": "Affinity rules for pod assignment",
"type": "object",
"default": {}
},
"env": {
"description": "Environment variables",
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"value": {
"type": "string"
},
"valueFrom": {
"type": "object"
}
},
"required": ["name"]
},
"default": []
},
"volumeMounts": {
"description": "Volume mounts",
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"mountPath": {
"type": "string"
},
"readOnly": {
"type": "boolean"
}
},
"required": ["name", "mountPath"]
},
"default": []
},
"volumes": {
"description": "Volumes",
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"required": ["name"]
},
"default": []
}
},
"required": ["replicaCount", "image", "service"]
}
Helm Chart Best Practices
This reference provides comprehensive best practices for creating, maintaining, and validating Helm charts.
Chart Structure
Required Files
Every Helm chart must have:
mychart/
Chart.yaml # Chart metadata
values.yaml # Default configuration values
templates/ # Template directoryChart.yaml Structure
Use apiVersion v2 for Helm 3+ charts:
apiVersion: v2
name: mychart
description: A Helm chart for Kubernetes
type: application # or 'library' for helper charts
version: 0.1.0 # Chart version (SemVer)
appVersion: "1.16.0" # Version of the app
# Optional but recommended
keywords:
- web
- application
home: https://github.com/example/mychart
sources:
- https://github.com/example/mychart
maintainers:
- name: Your Name
email: your.email@example.com
# Dependencies
dependencies:
- name: postgresql
version: "~11.6.0"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
# Kubernetes version constraint
kubeVersion: ">=1.21.0-0"Template Best Practices
1. Use Named Templates (Helpers)
Define reusable templates in templates/_helpers.tpl:
Good:
{{- define "mychart.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}Usage:
metadata:
name: {{ include "mychart.fullname" . }}2. Use include Instead of template
Good:
metadata:
labels:
{{- include "mychart.labels" . | nindent 4 }}Bad:
metadata:
labels:
{{- template "mychart.labels" . }}Why: include allows piping the output to other functions like nindent, indent, quote, etc.
3. Always Quote String Values
Good:
env:
- name: DATABASE_HOST
value: {{ .Values.database.host | quote }}Bad:
env:
- name: DATABASE_HOST
value: {{ .Values.database.host }}Why: Prevents YAML parsing issues with special characters and ensures strings are treated as strings.
4. Use required for Critical Values
Good:
apiVersion: v1
kind: Secret
metadata:
name: {{ include "mychart.fullname" . }}
data:
password: {{ required "A valid .Values.password is required!" .Values.password | b64enc }}Why: Fails early with a helpful error message if required values are missing.
5. Provide Defaults with default Function
Good:
replicas: {{ .Values.replicaCount | default 1 }}Why: Makes charts more resilient and easier to use with minimal configuration.
6. Use nindent for Proper Indentation
Good:
spec:
template:
metadata:
labels:
{{- include "mychart.labels" . | nindent 8 }}Bad:
spec:
template:
metadata:
labels:
{{ include "mychart.labels" . | indent 8 }}Why: nindent adds a newline before indenting, which is usually what you want in YAML.
7. Use toYaml for Complex Structures
Good:
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 2 }}
{{- end }}Why: Allows users to specify complex YAML structures in values without template complexity.
8. Conditional Resource Creation
Good:
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "mychart.fullname" . }}
# ... ingress definition
{{- end }}Why: Allows users to optionally enable/disable resources.
9. Use with for Scoping
Good:
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 2 }}
{{- end }}Why: Changes the scope of . to the specified value, making templates cleaner.
10. Template Comments
Good:
{{- /*
This helper creates the fullname for resources.
It supports nameOverride and fullnameOverride values.
*/ -}}
{{- define "mychart.fullname" -}}
{{- end }}Bad:
# This creates the fullname
{{- define "mychart.fullname" -}}
{{- end }}Why: Template comments ({{- /* */ -}}) are removed during rendering, while YAML comments (#) remain in output.
Values File Best Practices
1. Use Flat Structures When Possible
Good (Simple):
replicaCount: 1
imagePullPolicy: IfNotPresentGood (Related Settings):
image:
repository: nginx
pullPolicy: IfNotPresent
tag: "1.21.0"Bad (Overly Nested):
app:
deployment:
pod:
container:
image:
repository: nginx2. Document All Values
Good:
# replicaCount is the number of pod replicas for the deployment
replicaCount: 1
# image configures the container image
image:
# image.repository is the container image registry and name
repository: nginx
# image.pullPolicy is the image pull policy
pullPolicy: IfNotPresent
# image.tag overrides the image tag (default is chart appVersion)
tag: "1.21.0"Why: Makes charts self-documenting and easier to use.
3. Provide Sensible Defaults
Good:
replicaCount: 1
service:
type: ClusterIP
port: 80
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 100m
memory: 128MiWhy: Charts should work out of the box with minimal configuration.
4. Use Boolean Flags for Feature Toggles
Good:
ingress:
enabled: false
className: ""
annotations: {}
hosts:
- host: chart-example.local
paths:
- path: /
pathType: ImplementationSpecific
tls: []Why: Makes it clear when features are optional and how to enable them.
5. Group Related Configuration
Good:
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 806. Provide Empty Structures for Optional Config
Good:
nodeSelector: {}
tolerations: []
affinity: {}Why: Shows users what optional configurations are available.
Kubernetes Resource Best Practices
1. Always Set Resource Limits and Requests
Good:
resources:
limits:
cpu: 100m
memory: 128Mi
requests:
cpu: 100m
memory: 128MiWhy: Ensures proper scheduling and prevents resource exhaustion.
2. Use Proper Label Conventions
Good:
metadata:
labels:
app.kubernetes.io/name: {{ include "mychart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ include "mychart.chart" . }}Why: Follows Kubernetes recommended labels for better tooling integration.
3. Use SecurityContext
Good:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
capabilities:
drop:
- ALL
readOnlyRootFilesystem: trueWhy: Improves security posture.
4. Define Probes
Good:
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 5
periodSeconds: 5Why: Ensures Kubernetes can properly manage application health.
5. Use ConfigMaps and Secrets Appropriately
Good:
# ConfigMap for non-sensitive config
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "mychart.fullname" . }}
data:
app.conf: |
{{- .Values.config | nindent 4 }}# Secret for sensitive data
apiVersion: v1
kind: Secret
metadata:
name: {{ include "mychart.fullname" . }}
type: Opaque
data:
password: {{ .Values.password | b64enc }}Template Function Reference
String Functions
quote- Quote a stringsquote- Single quote a stringtrim- Remove whitespacetrimSuffix- Remove suffixtrimPrefix- Remove prefixupper- Convert to uppercaselower- Convert to lowercasetitle- Title casetrunc- Truncate stringrepeat- Repeat stringsubstr- Substringnospace- Remove all whitespace
Type Conversion
toYaml- Convert to YAMLfromYaml- Parse YAMLtoJson- Convert to JSONfromJson- Parse JSONtoString- Convert to stringtoStrings- Convert list to strings
Flow Control
default- Provide default valuerequired- Require a valuefail- Fail with error messagecoalesce- Return first non-empty value
Collections
list- Create a listdict- Create a dictionarymerge- Merge dictionariespick- Pick keys from dictionaryomit- Omit keys from dictionarykeys- Get dictionary keysvalues- Get dictionary values
Encoding
b64enc- Base64 encodeb64dec- Base64 decodesha256sum- SHA256 hash
Testing Best Practices
1. Create Test Resources
Good:
# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "mychart.fullname" . }}-test-connection"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "mychart.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: NeverRun tests:
helm test <release-name>2. Test with Multiple Values
# Test with production values
helm template my-release ./mychart -f values-prod.yaml
# Test with development values
helm template my-release ./mychart -f values-dev.yaml
# Test with overrides
helm template my-release ./mychart --set replicaCount=33. Validate Before Installing
# Lint the chart
helm lint ./mychart
# Dry-run install
helm install my-release ./mychart --dry-run --debug
# Validate against cluster
helm install my-release ./mychart --dry-runSecurity Best Practices
1. Don't Hardcode Secrets
Bad:
data:
password: cGFzc3dvcmQ= # Don't do this!Good:
data:
password: {{ .Values.password | b64enc }}2. Use RBAC
Good:
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "mychart.serviceAccountName" . }}
{{- end }}3. Run as Non-Root
Good:
securityContext:
runAsNonRoot: true
runAsUser: 10004. Drop Capabilities
Good:
securityContext:
capabilities:
drop:
- ALLPerformance Best Practices
1. Use .helmignore
Exclude unnecessary files from the chart package:
# .helmignore
.git/
.gitignore
*.md
.DS_Store
*.swp
test/2. Minimize Template Complexity
Good:
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 2 }}
{{- end }}Bad:
resources:
{{- if .Values.resources.limits }}
limits:
{{- if .Values.resources.limits.cpu }}
cpu: {{ .Values.resources.limits.cpu }}
{{- end }}
{{- if .Values.resources.limits.memory }}
memory: {{ .Values.resources.limits.memory }}
{{- end }}
{{- end }}
# ... more nested conditionals3. Use Helpers for Repeated Logic
Don't repeat the same template logic in multiple places - extract it to a helper.
Version Control Best Practices
1. Use SemVer for Chart Versions
MAJOR.MINOR.PATCH- Increment MAJOR for breaking changes
- Increment MINOR for new features
- Increment PATCH for bug fixes
2. Maintain a CHANGELOG
Document changes between versions.
3. Tag Releases
git tag -a v1.0.0 -m "Release version 1.0.0"
git push origin v1.0.0Common Pitfalls to Avoid
1. Not Using - for Whitespace Control
Bad:
{{ if .Values.enabled }}
key: value
{{ end }}Good:
{{- if .Values.enabled }}
key: value
{{- end }}2. Not Truncating Resource Names
Kubernetes resource names must be <= 63 characters:
Bad:
name: {{ .Release.Name }}-{{ .Chart.Name }}-deploymentGood:
name: {{ include "mychart.fullname" . | trunc 63 | trimSuffix "-" }}3. Using template Instead of include
Use include when you need to pipe the output to other functions.
4. Not Validating User Input
Bad:
replicas: {{ .Values.replicaCount }}Good:
replicas: {{ required "replicaCount is required" .Values.replicaCount }}5. Hardcoding Values in Templates
Bad:
image: nginx:1.21.0Good:
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"Upgrade and Migration Best Practices
1. Use Helm Hooks
metadata:
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "1"
"helm.sh/hook-delete-policy": hook-succeededAvailable hooks:
pre-installpost-installpre-upgradepost-upgradepre-deletepost-deletepre-rollbackpost-rollbacktest
2. Test Upgrades
# Show what would change
helm diff upgrade my-release ./mychart
# Dry-run upgrade
helm upgrade my-release ./mychart --dry-run --debug3. Support Rolling Back
Ensure your charts support rollback by not using hooks that delete critical resources.
Documentation Best Practices
1. Create a Comprehensive README
Include:
- Chart description
- Prerequisites
- Installation instructions
- Configuration options (values)
- Examples
- Upgrade notes
2. Document Template Functions
Add comments to your _helpers.tpl:
{{- /*
mychart.fullname generates a fully qualified application name.
It supports fullnameOverride and nameOverride values.
Maximum length is 63 characters per DNS naming spec.
Usage: {{ include "mychart.fullname" . }}
*/ -}}
{{- define "mychart.fullname" -}}
{{- end }}3. Provide NOTES.txt
Create templates/NOTES.txt with post-installation instructions:
Thank you for installing {{ .Chart.Name }}.
Your release is named {{ .Release.Name }}.
To learn more about the release, try:
$ helm status {{ .Release.Name }}
$ helm get all {{ .Release.Name }}
{{- if .Values.ingress.enabled }}
Application URL:
{{- range .Values.ingress.hosts }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ . }}{{ $.Values.ingress.path }}
{{- end }}
{{- end }}Packaging and Distribution
1. Package the Chart
helm package ./mychart2. Create Chart Repository
# Create index
helm repo index .
# Serve repository
helm serve3. Publish to Artifact Hub
Create artifacthub-repo.yml in your repository:
repositoryID: <uuid>
owners:
- name: Your Name
email: your.email@example.comSummary Checklist
Before releasing a chart, verify:
- [ ] Chart.yaml has all required fields
- [ ] values.yaml has sensible defaults
- [ ] All values are documented
- [ ] Templates use helpers for repeated logic
- [ ] Resource names are properly truncated
- [ ] Labels follow Kubernetes conventions
- [ ] Resources have limits and requests
- [ ] SecurityContext is defined
- [ ] Probes are configured
- [ ] Secrets are parameterized, not hardcoded
- [ ]
helm lintpasses - [ ]
helm templaterenders successfully - [ ] Dry-run install succeeds
- [ ] Tests are defined and pass
- [ ] README.md is comprehensive
- [ ] NOTES.txt provides helpful post-install info
- [ ] Chart version follows SemVer
- [ ] .helmignore excludes unnecessary files
Kubernetes YAML Best Practices
This reference provides comprehensive best practices for creating, maintaining, and validating Kubernetes resources in Helm charts.
General YAML Best Practices
Formatting and Style
- Use 2 spaces for indentation (not tabs)
- Keep lines under 120 characters when possible
- Use lowercase for keys
- Quote string values containing special characters
- Always specify apiVersion and kind
- Include metadata.name for all resources
- Use consistent naming conventions (lowercase, hyphens for separators)
Resource Organization
- One resource per file for clarity (unless logically grouped)
- Use
---to separate multiple resources in a single file - Name files descriptively:
<resource-type>-<name>.yaml - Group related resources together (e.g., deployment + service + configmap)
Metadata Best Practices
Standard Metadata Structure
metadata:
name: my-app
namespace: production
labels:
app.kubernetes.io/name: my-app
app.kubernetes.io/instance: my-app-prod
app.kubernetes.io/version: "1.0.0"
app.kubernetes.io/component: backend
app.kubernetes.io/part-of: my-system
app.kubernetes.io/managed-by: helm
helm.sh/chart: my-app-1.0.0
annotations:
description: "Backend service for my-app"
prometheus.io/scrape: "true"
prometheus.io/port: "8080"Kubernetes Recommended Labels
Always include these standard labels for better tooling integration:
| Label | Description | Example |
|---|---|---|
app.kubernetes.io/name | Application name | nginx |
app.kubernetes.io/instance | Unique instance identifier | nginx-prod |
app.kubernetes.io/version | Application version | 1.21.0 |
app.kubernetes.io/component | Component within architecture | frontend |
app.kubernetes.io/part-of | Higher-level application | wordpress |
app.kubernetes.io/managed-by | Tool managing the resource | helm |
Helm-Specific Labels
labels:
helm.sh/chart: {{ include "mychart.chart" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}Labels and Selectors
Selector Best Practices
- Selectors must match pod labels exactly
- Use immutable selectors (they cannot be changed after creation)
- Keep selector labels minimal but unique
Good:
spec:
selector:
matchLabels:
app.kubernetes.io/name: my-app
app.kubernetes.io/instance: my-app-prod
template:
metadata:
labels:
app.kubernetes.io/name: my-app
app.kubernetes.io/instance: my-app-prod
app.kubernetes.io/version: "1.0.0" # Additional labels OKBad:
spec:
selector:
matchLabels:
app: my-app
version: "1.0.0" # Version in selector prevents rolling updates!Label Key Conventions
- Use DNS subdomain format for prefixed keys:
prefix/key - Keys without prefix are private to the user
- Standard prefixes:
app.kubernetes.io/,helm.sh/,kubernetes.io/
Resource Limits and Requests
Why They Matter
- Requests: Minimum resources guaranteed to the container
- Limits: Maximum resources the container can use
- Required for proper scheduling, QoS class assignment, and cluster stability
Recommended Configuration
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "500m"Guidelines by Resource Type
| Resource Type | Requests | Limits | Notes |
|---|---|---|---|
| CPU | Set always | Optional | Consider burstable workloads |
| Memory | Set always | Set always | Prevents OOM kills |
| Ephemeral Storage | Optional | Recommended | For disk-intensive apps |
QoS Classes
Kubernetes assigns Quality of Service classes based on resource settings:
1. Guaranteed: requests == limits for all containers 2. Burstable: At least one container has requests < limits 3. BestEffort: No requests or limits set (not recommended)
# Guaranteed QoS
resources:
requests:
memory: "128Mi"
cpu: "500m"
limits:
memory: "128Mi"
cpu: "500m"Memory Guidelines
- Set memory limits to prevent OOM kills affecting other pods
- Memory is incompressible - exceeding limits causes termination
- Monitor actual usage before setting production values
CPU Guidelines
- CPU is compressible - exceeding limits causes throttling, not termination
- Consider not setting CPU limits for burstable workloads
- Use millicores:
100m= 0.1 CPU core
Probes Configuration
Liveness Probe
Determines if the container should be restarted:
livenessProbe:
httpGet:
path: /healthz
port: 8080
httpHeaders:
- name: X-Health-Check
value: liveness
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
successThreshold: 1Readiness Probe
Determines if the container can receive traffic:
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
successThreshold: 1Startup Probe
For slow-starting containers (Kubernetes 1.18+):
startupProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 0
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 30 # 30 * 10 = 300s max startup time
successThreshold: 1Probe Types
| Type | Use Case | Example |
|---|---|---|
httpGet | HTTP endpoints | REST APIs, web apps |
tcpSocket | TCP connections | Databases, message queues |
exec | Custom scripts | Complex health checks |
grpc | gRPC services | gRPC health protocol |
Probe Best Practices
- Liveness: Check if app is running, not dependencies
- Readiness: Check if app can serve traffic (including dependencies)
- Startup: Use for slow-starting apps instead of long
initialDelaySeconds - Set appropriate timeouts to prevent false positives
- Don't make probes too aggressive (high CPU overhead)
Security Context
Pod-Level Security Context
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
fsGroupChangePolicy: "OnRootMismatch"
seccompProfile:
type: RuntimeDefaultContainer-Level Security Context
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE # Only if needed
seccompProfile:
type: RuntimeDefaultSecurity Context Fields Explained
| Field | Level | Description |
|---|---|---|
runAsNonRoot | Pod/Container | Prevents running as root |
runAsUser | Pod/Container | Specifies UID to run as |
runAsGroup | Pod/Container | Specifies GID to run as |
fsGroup | Pod | Group ownership for volumes |
readOnlyRootFilesystem | Container | Makes root filesystem read-only |
allowPrivilegeEscalation | Container | Prevents privilege escalation |
capabilities | Container | Linux capabilities management |
seccompProfile | Pod/Container | Syscall filtering |
Recommended Security Baseline
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALLImage Management
Image Specification Best Practices
containers:
- name: app
image: registry.example.com/my-app:v1.2.3
imagePullPolicy: IfNotPresentImage Pull Policy
| Policy | When to Use |
|---|---|
Always | For :latest tags or mutable tags |
IfNotPresent | For immutable tags (recommended) |
Never | For pre-loaded images (rare) |
Image Best Practices
- Always use specific tags: Never use
:latestin production - Use digest for immutability:
image@sha256:abc123... - Use private registries: For security and reliability
- Scan images: Implement vulnerability scanning in CI/CD
# Recommended: Specific tag
image: nginx:1.21.6
# Better: Digest for immutability
image: nginx@sha256:2834dc507516af02784808c5f48b7cbe38b8ed5d0f4837f16e78d00deb7e7767
# Avoid: Mutable tags
image: nginx:latest # Don't do this in productionPod Disruption Budgets
Why PDBs Matter
Pod Disruption Budgets ensure high availability during voluntary disruptions like:
- Node drains
- Cluster upgrades
- Deployment rollouts
PDB Configuration
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-pdb
spec:
# Option 1: Minimum available pods
minAvailable: 2
# Option 2: Maximum unavailable pods (use one, not both)
# maxUnavailable: 1
selector:
matchLabels:
app.kubernetes.io/name: my-appPDB Best Practices
- Set PDB for all production workloads with multiple replicas
- Use
minAvailablewhen you need minimum capacity guarantee - Use
maxUnavailablewhen you want to limit disruption rate - Don't set
minAvailableequal to replicas (blocks all disruptions)
# Good: Allows 1 pod to be unavailable
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ include "mychart.fullname" . }}-pdb
spec:
maxUnavailable: 1
selector:
matchLabels:
{{- include "mychart.selectorLabels" . | nindent 6 }}Horizontal Pod Autoscaler
HPA Configuration
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
- type: Pods
value: 4
periodSeconds: 15
selectPolicy: MaxHPA Best Practices
- Always set resource requests (required for CPU/memory-based scaling)
- Set appropriate
minReplicasfor base capacity - Use stabilization windows to prevent flapping
- Consider custom metrics for business-specific scaling
- Don't use HPA with
replicasfield in Deployment (conflicts)
Helm Template for HPA
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "mychart.fullname" . }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "mychart.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}Network Policies
Default Deny All
Start with a default deny policy, then allow specific traffic:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: my-namespace
spec:
podSelector: {}
policyTypes:
- Ingress
- EgressAllow Specific Ingress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080Allow Egress to External Services
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress-to-database
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/8
ports:
- protocol: TCP
port: 5432
# Allow DNS resolution
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53Network Policy Best Practices
- Start with default deny, then allow specific traffic
- Always allow DNS egress (UDP port 53)
- Use namespace selectors for cross-namespace communication
- Label namespaces for policy targeting
- Test policies in staging before production
Service Configuration
Service Types
| Type | Use Case |
|---|---|
ClusterIP | Internal cluster communication (default) |
NodePort | External access via node ports (30000-32767) |
LoadBalancer | Cloud provider load balancers |
ExternalName | DNS CNAME for external services |
Service Best Practices
apiVersion: v1
kind: Service
metadata:
name: my-app
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
type: ClusterIP
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
{{- include "mychart.selectorLabels" . | nindent 4 }}Named Ports
Always use named ports for clarity:
# In Deployment
ports:
- name: http
containerPort: 8080
protocol: TCP
- name: metrics
containerPort: 9090
protocol: TCP
# In Service
ports:
- name: http
port: 80
targetPort: http # References named portConfigMaps and Secrets
ConfigMap Best Practices
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "mychart.fullname" . }}-config
data:
# For simple key-value pairs
LOG_LEVEL: "info"
MAX_CONNECTIONS: "100"
# For file content
app.properties: |
server.port=8080
server.host=0.0.0.0Secret Best Practices
apiVersion: v1
kind: Secret
metadata:
name: {{ include "mychart.fullname" . }}-secret
type: Opaque
data:
# Base64 encoded values
password: {{ .Values.password | b64enc | quote }}
api-key: {{ .Values.apiKey | b64enc | quote }}
stringData:
# Plain text (will be encoded)
config.yaml: |
database:
host: {{ .Values.database.host }}Mounting as Environment Variables
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: my-config
key: LOG_LEVEL
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: my-secret
key: passwordMounting as Files
volumes:
- name: config
configMap:
name: my-config
- name: secrets
secret:
secretName: my-secret
defaultMode: 0400
volumeMounts:
- name: config
mountPath: /etc/config
readOnly: true
- name: secrets
mountPath: /etc/secrets
readOnly: trueCommon Validation Issues
Missing Required Fields
| Issue | Fix |
|---|---|
Missing apiVersion | Add appropriate API version |
Missing kind | Add resource kind |
Missing metadata.name | Add resource name |
Missing spec.selector | Add pod selector for Deployments/Services |
Empty containers | Add at least one container |
Selector Mismatches
# Error: Selector doesn't match pod labels
spec:
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
application: my-app # Wrong label key!Invalid Values
| Field | Valid Format | Example |
|---|---|---|
| CPU | Millicores or decimal | 500m, 0.5 |
| Memory | Binary units | 512Mi, 1Gi |
| Port | 1-65535 | 8080 |
| DNS names | Lowercase alphanumeric with hyphens | my-app-service |
Namespace Issues
- Not all resources are namespaced (e.g., ClusterRole, PersistentVolume)
- Services must be in the same namespace as pods they target
- Default namespace is "default" if not specified
Deprecation Warnings
Deprecated APIs
| Old API | New API | K8s Version |
|---|---|---|
extensions/v1beta1 Deployment | apps/v1 | 1.16+ |
extensions/v1beta1 Ingress | networking.k8s.io/v1 | 1.19+ |
networking.k8s.io/v1beta1 Ingress | networking.k8s.io/v1 | 1.19+ |
policy/v1beta1 PodDisruptionBudget | policy/v1 | 1.21+ |
policy/v1beta1 PodSecurityPolicy | Removed (use PSA) | 1.25+ |
autoscaling/v2beta1 HPA | autoscaling/v2 | 1.23+ |
batch/v1beta1 CronJob | batch/v1 | 1.21+ |
Checking API Versions
# List available API versions
kubectl api-versions
# Check if resource supports specific version
kubectl api-resources | grep deploymentsCRD-Specific Considerations
API Version Compatibility
- Check the CRD version installed in the cluster
- Use the correct apiVersion for the CRD
- Be aware of deprecations (e.g., v1alpha1 → v1beta1 → v1)
Required Fields
- CRDs often have custom required fields in spec
- Check the CRD documentation for field requirements
- Use
kubectl explain <kind>to see field documentation
Validation
- CRDs may have custom validation rules
- OpenAPI schema validation is stricter in newer K8s versions
- Use dry-run to catch validation errors before applying
# Explain CRD fields
kubectl explain certificate.spec
kubectl explain certificate.spec.issuerRefAnti-Patterns to Avoid
1. Running as Root
# Bad
securityContext:
runAsUser: 0
# Good
securityContext:
runAsNonRoot: true
runAsUser: 10002. Using Latest Tag
# Bad
image: nginx:latest
# Good
image: nginx:1.21.63. No Resource Limits
# Bad - No limits
containers:
- name: app
image: my-app:1.0
# Good - With limits
containers:
- name: app
image: my-app:1.0
resources:
limits:
memory: "256Mi"
cpu: "500m"4. Privileged Containers
# Bad
securityContext:
privileged: true
# Good
securityContext:
privileged: false
allowPrivilegeEscalation: false5. No Probes
# Bad - No health checks
containers:
- name: app
# Good - With probes
containers:
- name: app
livenessProbe:
httpGet:
path: /health
port: 8080
readinessProbe:
httpGet:
path: /ready
port: 8080Summary Checklist
Before deploying Kubernetes resources, verify:
Required
- [ ]
apiVersionandkindare correct - [ ]
metadata.namefollows naming conventions - [ ] Labels include recommended Kubernetes labels
- [ ] Selectors match pod labels exactly
- [ ] At least one container is defined
Recommended
- [ ] Resource requests and limits are set
- [ ] Liveness and readiness probes are configured
- [ ] Security context is defined (non-root, read-only fs)
- [ ] Image uses specific tag (not
latest) - [ ] Secrets are used for sensitive data (not ConfigMaps)
Production
- [ ] Pod Disruption Budget is configured
- [ ] Network Policies are in place
- [ ] HPA is configured for scalable workloads
- [ ] Service accounts are properly scoped
- [ ] Pod anti-affinity for high availability
Helm Template Functions Reference
This reference provides a comprehensive guide to Helm template functions, including built-in functions and Sprig library functions.
Essential Helm Functions
include
Includes a named template and allows piping the output to other functions.
Syntax:
{{ include "template.name" . }}Examples:
# Include and indent
metadata:
labels:
{{- include "mychart.labels" . | nindent 4 }}
# Include and quote
value: {{ include "mychart.value" . | quote }}
# Include with custom context
{{- include "mychart.container" (dict "root" . "container" .Values.mainContainer) }}When to use: Prefer include over template when you need to manipulate the output with functions.
tpl
Evaluates a string as a template, allowing dynamic template rendering.
Syntax:
{{ tpl <string> <context> }}Examples:
# Render a value as template
{{ tpl .Values.customConfig . }}
# Render external file as template
{{ tpl (.Files.Get "config/app.conf") . }}
# values.yaml
customConfig: |
server:
host: {{ .Values.server.host }}
port: {{ .Values.server.port }}When to use: When users need to provide template strings in values or external files.
required
Enforces that a value must be provided, failing with a custom error message if missing.
Syntax:
{{ required "error message" .Values.path }}Examples:
# Require a critical value
apiVersion: v1
kind: Service
metadata:
name: {{ required "A valid service name is required!" .Values.service.name }}
# Require database password
data:
password: {{ required "database.password must be set" .Values.database.password | b64enc }}
# Require multiple values
env:
- name: API_KEY
value: {{ required "apiKey must be provided" .Values.apiKey | quote }}When to use: For critical values that have no sensible default.
lookup
Queries existing Kubernetes resources in the cluster during template rendering.
Syntax:
{{ lookup "apiVersion" "kind" "namespace" "name" }}Examples:
# Look up existing secret
{{- $secret := lookup "v1" "Secret" .Release.Namespace "my-secret" }}
{{- if $secret }}
# Secret exists, use existing password
password: {{ $secret.data.password }}
{{- else }}
# Create new password
password: {{ randAlphaNum 16 | b64enc }}
{{- end }}
# List all pods in namespace
{{- $pods := lookup "v1" "Pod" .Release.Namespace "" }}
# Get specific resource
{{- $cm := lookup "v1" "ConfigMap" "default" "my-config" }}⚠️ Cautions:
- Only works during
helm installandhelm upgrade, not withhelm template - Requires cluster access
- Can slow down rendering
- Creates tight coupling between chart and cluster state
When to use: When you need to check for existing resources or migrate from existing deployments.
String Functions
quote / squote
Wraps a string in double or single quotes.
Examples:
# Double quotes
env:
- name: HOST
value: {{ .Values.host | quote }} # Output: "localhost"
# Single quotes
value: {{ .Values.name | squote }} # Output: 'myapp'default
Provides a fallback value if the input is empty.
Examples:
# Simple default
replicas: {{ .Values.replicaCount | default 1 }}
# Chain with other functions
image: {{ .Values.image.tag | default .Chart.AppVersion | quote }}
# Default for nested values
{{ .Values.server.port | default 8080 }}trim / trimSuffix / trimPrefix
Removes whitespace or specific strings.
Examples:
# Remove whitespace
name: {{ .Values.name | trim }}
# Remove suffix
name: {{ .Release.Name | trimSuffix "-dev" }}
# Remove prefix
name: {{ .Values.fullName | trimPrefix "app-" }}
# Common pattern for resource names
name: {{ include "mychart.fullname" . | trunc 63 | trimSuffix "-" }}upper / lower / title
Changes string case.
Examples:
# Uppercase
env: {{ .Values.environment | upper }} # Output: PRODUCTION
# Lowercase
name: {{ .Values.name | lower }} # Output: myapp
# Title case
label: {{ .Values.label | title }} # Output: My Applicationtrunc
Truncates a string to a specified length.
Examples:
# Truncate to 63 chars (K8s DNS limit)
name: {{ .Release.Name | trunc 63 | trimSuffix "-" }}
# Truncate to 20 chars
shortName: {{ .Values.name | trunc 20 }}repeat
Repeats a string N times.
Examples:
# Repeat string
value: {{ "=" | repeat 10 }} # Output: ==========
# Create separator
comment: {{ "#" | repeat 20 }} # Output: ####################replace
Replaces occurrences of a substring.
Examples:
# Replace underscores with hyphens
name: {{ .Values.name | replace "_" "-" }}
# Replace spaces
label: {{ .Values.label | replace " " "-" | lower }}
# Chart label (replace + with _)
chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" }}substr
Extracts a substring.
Examples:
# Get first 10 characters
short: {{ .Values.name | substr 0 10 }}
# Get characters 5-15
middle: {{ .Values.name | substr 5 15 }}nospace
Removes all whitespace from a string.
Examples:
# Remove all spaces
compact: {{ .Values.value | nospace }} # "hello world" → "helloworld"contains / hasPrefix / hasSuffix
Checks if a string contains, starts with, or ends with a substring.
Examples:
# Check if contains
{{- if contains "prod" .Values.environment }}
# Production configuration
{{- end }}
# Check prefix
{{- if hasPrefix "app-" .Values.name }}
name: {{ .Values.name }}
{{- else }}
name: {{ printf "app-%s" .Values.name }}
{{- end }}
# Check suffix
{{- if hasSuffix "-service" .Values.name }}
# Already has suffix
{{- end }}Type Conversion Functions
toYaml / fromYaml
Converts between Go objects and YAML strings.
Examples:
# Convert to YAML
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 2 }}
{{- end }}
# Parse YAML string
{{- $config := .Values.configYaml | fromYaml }}
{{- $config.database.host }}toJson / fromJson
Converts between Go objects and JSON strings.
Examples:
# Convert to JSON
data:
config.json: |
{{- .Values.config | toJson | nindent 4 }}
# Parse JSON
{{- $data := .Values.jsonString | fromJson }}
{{- $data.key }}toString
Converts any value to a string.
Examples:
# Convert number to string
port: {{ .Values.port | toString | quote }}
# Convert boolean to string
enabled: {{ .Values.enabled | toString }}List/Array Functions
list
Creates a list.
Examples:
# Create list
{{- $myList := list "a" "b" "c" }}
# Pass multiple arguments to template
{{- include "mychart.template" (list . "arg1" "arg2") }}append / prepend
Adds elements to a list.
Examples:
# Append to list
{{- $list := list "a" "b" }}
{{- $list = append $list "c" }} # ["a", "b", "c"]
# Prepend to list
{{- $list := list "b" "c" }}
{{- $list = prepend $list "a" }} # ["a", "b", "c"]first / rest / last
Gets elements from a list.
Examples:
# Get first element
{{- $first := first $myList }}
# Get all but first
{{- $rest := rest $myList }}
# Get last element
{{- $last := last $myList }}has
Checks if a list contains an element.
Examples:
{{- if has "production" .Values.environments }}
# Production configuration
{{- end }}compact
Removes empty/nil elements from a list.
Examples:
{{- $list := list "a" "" "b" nil "c" }}
{{- $cleaned := compact $list }} # ["a", "b", "c"]uniq
Removes duplicate elements.
Examples:
{{- $list := list "a" "b" "a" "c" "b" }}
{{- $unique := uniq $list }} # ["a", "b", "c"]sortAlpha
Sorts list alphabetically.
Examples:
{{- $sorted := .Values.items | sortAlpha }}Dictionary/Map Functions
dict
Creates a dictionary.
Examples:
# Create dict
{{- $myDict := dict "key1" "value1" "key2" "value2" }}
# Pass custom context to template
{{- include "mychart.template" (dict "root" . "custom" "value") }}
# Complex context
{{- $ctx := dict "top" . "container" .Values.mainContainer "port" .Values.service.port }}
{{- include "mychart.container" $ctx }}merge / mergeOverwrite
Merges dictionaries.
Examples:
# Merge dictionaries (dest, src1, src2, ...)
{{- $defaults := dict "replicas" 1 "port" 80 }}
{{- $overrides := dict "replicas" 3 }}
{{- $final := merge $overrides $defaults }}
# Result: {"replicas": 3, "port": 80}
# mergeOverwrite (right-most wins)
{{- $result := mergeOverwrite $dict1 $dict2 }}keys / values
Gets keys or values from a dictionary.
Examples:
# Get all keys
{{- $keys := keys .Values.config }}
# Get all values
{{- $vals := values .Values.config }}
# Iterate over keys
{{- range $key := keys .Values.labels | sortAlpha }}
{{ $key }}: {{ index $.Values.labels $key }}
{{- end }}pick / omit
Selects or excludes keys from a dictionary.
Examples:
# Pick specific keys
{{- $subset := pick .Values.config "host" "port" }}
# Omit specific keys
{{- $filtered := omit .Values.config "password" "secret" }}hasKey
Checks if a dictionary has a key.
Examples:
{{- if hasKey .Values "database" }}
{{- if hasKey .Values.database "password" }}
# Password is configured
{{- end }}
{{- end }}pluck
Gets a value by key from multiple dictionaries.
Examples:
# Get "name" from first dict that has it
{{- $name := pluck "name" .Values.override .Values.defaults | first }}Encoding Functions
b64enc / b64dec
Base64 encode/decode.
Examples:
# Encode secret
apiVersion: v1
kind: Secret
data:
password: {{ .Values.password | b64enc }}
# Decode existing secret
{{- $secret := lookup "v1" "Secret" .Release.Namespace "my-secret" }}
{{- if $secret }}
{{- $decoded := $secret.data.password | b64dec }}
{{- end }}sha256sum
Generates SHA256 hash.
Examples:
# Create checksum annotation to trigger rolling update
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
# Hash password
data:
passwordHash: {{ .Values.password | sha256sum }}uuidv4
Generates a random UUID v4.
Examples:
# Generate unique ID
id: {{ uuidv4 }}Mathematical Functions
add / sub / mul / div / mod
Basic arithmetic operations.
Examples:
# Addition
replicas: {{ add .Values.baseReplicas 2 }}
# Subtraction
port: {{ sub .Values.maxPort 100 }}
# Multiplication
memory: {{ mul .Values.memoryPerPod .Values.replicas }}
# Division
cpuPerPod: {{ div .Values.totalCpu .Values.replicas }}
# Modulo
remainder: {{ mod .Values.value 10 }}max / min
Gets maximum or minimum value.
Examples:
# Ensure at least 1 replica
replicas: {{ max 1 .Values.replicaCount }}
# Cap at 10 replicas
replicas: {{ min 10 .Values.replicaCount }}floor / ceil / round
Rounding functions.
Examples:
# Round down
value: {{ floor 3.7 }} # 3
# Round up
value: {{ ceil 3.2 }} # 4
# Round to nearest
value: {{ round 3.5 }} # 4Date Functions
now
Gets current time.
Examples:
# Current timestamp
annotations:
timestamp: {{ now | date "2006-01-02T15:04:05Z" }}date
Formats a date/time.
Examples:
# Format date
date: {{ now | date "2006-01-02" }} # 2024-01-15
# Full timestamp
timestamp: {{ now | date "2006-01-02T15:04:05Z07:00" }}
# Custom format
generated: {{ now | date "Monday, 02-Jan-06 15:04:05 MST" }}dateModify
Modifies a date.
Examples:
# Add 24 hours
tomorrow: {{ now | dateModify "24h" }}
# Subtract 7 days
lastWeek: {{ now | dateModify "-168h" }}Comparison Functions
eq / ne
Equality and inequality.
Examples:
{{- if eq .Values.environment "production" }}
# Production settings
{{- end }}
{{- if ne .Values.replicaCount 1 }}
# Multiple replicas
{{- end }}lt / le / gt / ge
Less than, less than or equal, greater than, greater than or equal.
Examples:
{{- if gt .Values.replicaCount 1 }}
# Multiple replicas
{{- end }}
{{- if le .Values.maxConnections 100 }}
# Low connection count
{{- end }}and / or / not
Logical operations.
Examples:
{{- if and .Values.ingress.enabled .Values.ingress.tls.enabled }}
# Ingress with TLS
{{- end }}
{{- if or (eq .Values.env "dev") (eq .Values.env "staging") }}
# Non-production environment
{{- end }}
{{- if not .Values.production }}
# Development mode
{{- end }}Flow Control Functions
fail
Fails template rendering with an error message.
Examples:
{{- if not .Values.required }}
{{- fail "required value is not set" }}
{{- end }}
{{- if lt .Values.replicas 1 }}
{{- fail "replicas must be at least 1" }}
{{- end }}coalesce
Returns the first non-empty value.
Examples:
# Use first non-empty value
name: {{ coalesce .Values.nameOverride .Values.name .Chart.Name }}
# Multiple fallbacks
host: {{ coalesce .Values.database.host .Values.defaultHost "localhost" }}ternary
Inline if-then-else.
Examples:
# Ternary operator
type: {{ ternary "LoadBalancer" "ClusterIP" .Values.production }}
# With comparison
replicas: {{ ternary 3 1 (eq .Values.env "production") }}Indentation Functions
indent
Indents each line by N spaces.
Examples:
# Indent by 4 spaces
metadata:
labels:
{{ include "mychart.labels" . | indent 4 }}nindent
Adds a newline then indents.
Examples:
# Newline + indent (preferred)
metadata:
labels:
{{- include "mychart.labels" . | nindent 4 }}Why prefer nindent: Most YAML structures need a newline before the indented content, making nindent the right choice in most cases.
Random Functions
randAlphaNum
Generates random alphanumeric string.
Examples:
# Generate random password
{{- $password := randAlphaNum 16 }}
# Generate unique suffix
name: {{ printf "%s-%s" .Release.Name (randAlphaNum 5) }}randAlpha / randNumeric
Generates random alphabetic or numeric string.
Examples:
# Random letters only
code: {{ randAlpha 8 }}
# Random numbers only
id: {{ randNumeric 6 }}randAscii
Generates random ASCII string.
Examples:
# Random ASCII characters
token: {{ randAscii 32 }}File Functions
Files.Get
Reads a file from the chart.
Examples:
# Read configuration file
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "mychart.fullname" . }}
data:
config.yaml: |
{{- .Files.Get "config/app.yaml" | nindent 4 }}Files.GetBytes
Reads a file as bytes (for binary files).
Examples:
# Include binary file
data:
image.png: {{ .Files.GetBytes "files/image.png" | b64enc }}Files.Glob
Reads multiple files matching a pattern.
Examples:
# Include all YAML files
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "mychart.fullname" . }}
data:
{{- range $path, $content := .Files.Glob "config/*.yaml" }}
{{ base $path }}: |
{{- $content | nindent 4 }}
{{- end }}Files.Lines
Reads a file line by line.
Examples:
# Process file line by line
{{- range .Files.Lines "config/servers.txt" }}
- {{ . }}
{{- end }}Files.AsConfig / Files.AsSecrets
Creates ConfigMap or Secret data from files.
Examples:
# ConfigMap from files
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "mychart.fullname" . }}
data:
{{- (.Files.Glob "config/*").AsConfig | nindent 2 }}
# Secret from files
apiVersion: v1
kind: Secret
metadata:
name: {{ include "mychart.fullname" . }}
data:
{{- (.Files.Glob "secrets/*").AsSecrets | nindent 2 }}Path Functions
base / dir / ext / clean
Path manipulation functions.
Examples:
# Get filename
{{- $filename := base "/path/to/file.yaml" }} # file.yaml
# Get directory
{{- $dir := dir "/path/to/file.yaml" }} # /path/to
# Get extension
{{- $ext := ext "file.yaml" }} # .yaml
# Clean path
{{- $clean := clean "/path//to/../file" }} # /path/fileRegex Functions
regexMatch
Tests if a string matches a regex.
Examples:
{{- if regexMatch "^[0-9]+$" .Values.port }}
# Port is numeric
{{- end }}regexFind / regexFindAll
Finds regex matches.
Examples:
# Find first match
{{- $match := regexFind "[0-9]+" .Values.version }}
# Find all matches
{{- $matches := regexFindAll "[A-Z]+" .Values.name -1 }}regexReplaceAll
Replaces regex matches.
Examples:
# Replace all digits
name: {{ regexReplaceAll "[0-9]" .Values.name "" }}
# Replace pattern
clean: {{ regexReplaceAll "[^a-z0-9-]" .Values.name "" }}regexSplit
Splits string by regex.
Examples:
# Split by delimiter
{{- $parts := regexSplit ":" .Values.imageTag -1 }}Semantic Version Functions
semver / semverCompare
Work with semantic versions.
Examples:
# Parse semantic version
{{- $version := semver "1.2.3" }}
{{- $version.Major }} # 1
{{- $version.Minor }} # 2
{{- $version.Patch }} # 3
# Compare versions
{{- if semverCompare ">=1.20.0" .Capabilities.KubeVersion.Version }}
# Kubernetes 1.20 or higher
{{- end }}Advanced Patterns
Custom Context Passing
{{- define "mychart.container" -}}
{{- $root := .root }}
{{- $container := .container }}
{{- $port := .port }}
name: {{ $container.name }}
image: {{ $container.image }}
ports:
- containerPort: {{ $port }}
{{- end }}
# Usage
{{- include "mychart.container" (dict "root" . "container" .Values.mainContainer "port" 8080) }}Multi-Stage Processing
{{- $config := .Values.configYaml | fromYaml }}
{{- $merged := merge .Values.overrides $config }}
{{- $filtered := omit $merged "internalKey" }}
{{- toYaml $filtered | nindent 2 }}Conditional Value Selection
{{- $value := "" }}
{{- if .Values.custom }}
{{- $value = .Values.custom }}
{{- else if .Values.default }}
{{- $value = .Values.default }}
{{- else }}
{{- $value = "fallback" }}
{{- end }}Pipeline Composition
# Chain multiple functions
value: {{ .Values.name | trim | lower | replace " " "-" | trunc 63 | trimSuffix "-" | quote }}
# Multi-line pipeline
{{- .Values.config
| toYaml
| indent 2
| trim }}Function Combination Examples
Resource Name Generation
{{- define "mychart.resourceName" -}}
{{- $name := include "mychart.fullname" . -}}
{{- $suffix := .suffix | default "" -}}
{{- if $suffix }}
{{- printf "%s-%s" $name $suffix | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}Safe Value Extraction
{{- $password := "" }}
{{- if and .Values.database (hasKey .Values.database "password") }}
{{- $password = .Values.database.password }}
{{- else }}
{{- $password = randAlphaNum 16 }}
{{- end }}Configuration Merging
{{- $defaults := .Files.Get "config/defaults.yaml" | fromYaml }}
{{- $overrides := .Values.config | default (dict) }}
{{- $final := merge $overrides $defaults }}
config: |
{{- $final | toYaml | nindent 2 }}Performance Tips
1. Cache template results - Use variables to avoid recalculating:
{{- $fullname := include "mychart.fullname" . }}
name: {{ $fullname }}
matchLabels:
app: {{ $fullname }}2. Minimize lookups - lookup queries are expensive:
{{- $secret := lookup "v1" "Secret" .Release.Namespace "my-secret" }}
{{- if $secret }}
# Use $secret multiple times
{{- end }}3. Use with for scoping - Reduces template complexity:
{{- with .Values.ingress }}
{{- if .enabled }}
host: {{ .host }}
{{- end }}
{{- end }}Debugging Functions
printf
Formatted string output for debugging.
Examples:
# Debug values
{{- printf "Debug: name=%s, replicas=%d" .Values.name .Values.replicas | fail }}toYaml for inspection
# Inspect values
{{- toYaml .Values | fail }}Common Gotchas
1. Nil vs Empty String
# This fails if value is nil
{{- if .Values.optional }} # Error if nil!
# This works
{{- if .Values.optional | default "" }} # Safe2. Type Conversion
# Port is integer in values but needs string comparison
{{- if eq (.Values.port | toString) "80" }}3. Pipeline Precedence
# Wrong - quote applies to "true", not the result
{{- if .Values.enabled | quote }}
# Right - use parentheses
{{- if (.Values.enabled | quote) }}4. Whitespace in Conditionals
# Creates extra whitespace
{{ if .Values.enabled }}
value: true
{{ end }}
# Better - chomp whitespace
{{- if .Values.enabled }}
value: true
{{- end }}#!/bin/bash
# Wrapper script for detect_crd.py that handles PyYAML dependency
# Creates a temporary venv if PyYAML is not available
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PYTHON_SCRIPT="$SCRIPT_DIR/detect_crd.py"
# Check if we have arguments
if [ $# -lt 1 ]; then
echo "Usage: detect_crd_wrapper.sh <yaml-file> [yaml-file ...]" >&2
exit 1
fi
YAML_FILES=("$@")
VALID_FILES=()
for yaml_file in "${YAML_FILES[@]}"; do
if [ -f "$yaml_file" ]; then
VALID_FILES+=("$yaml_file")
else
echo "Warning: file not found, skipping: $yaml_file" >&2
fi
done
if [ ${#VALID_FILES[@]} -eq 0 ]; then
# No valid files to process is not an error for batch workflows.
echo "[]"
exit 0
fi
# Try to run with system Python first
if python3 -c "import yaml" 2>/dev/null; then
# PyYAML is available, run directly
python3 "$PYTHON_SCRIPT" "${VALID_FILES[@]}"
exit 0
fi
# PyYAML not available, create temporary venv
TEMP_VENV=$(mktemp -d -t helm-validator.XXXXXX)
trap 'rm -rf "$TEMP_VENV"' EXIT
echo "PyYAML not found in system Python. Creating temporary environment..." >&2
# Create venv and install PyYAML
python3 -m venv "$TEMP_VENV" >/dev/null 2>&1 || {
echo "Error: failed to create temporary virtual environment" >&2
exit 1
}
"$TEMP_VENV/bin/python3" -m pip install --quiet --disable-pip-version-check pyyaml
# Run the script
"$TEMP_VENV/bin/python3" "$PYTHON_SCRIPT" "${VALID_FILES[@]}"
# Cleanup happens automatically via trap
#!/usr/bin/env python3
"""
Detect Custom Resource Definitions (CRDs) in Kubernetes YAML files.
Extracts kind, apiVersion, and group information for CRD documentation lookup.
"""
import json
import sys
from pathlib import Path
try:
import yaml
except ImportError:
print("Error: PyYAML is not installed. Please run: pip install pyyaml", file=sys.stderr)
print("Or use the wrapper script: bash scripts/detect_crd_wrapper.sh", file=sys.stderr)
sys.exit(1)
STANDARD_API_GROUPS = {
# Core APIs are represented by apiVersion "v1" (handled separately)
"admissionregistration.k8s.io",
"apiextensions.k8s.io",
"apiregistration.k8s.io",
"apps",
"authentication.k8s.io",
"authorization.k8s.io",
"autoscaling",
"batch",
"certificates.k8s.io",
"coordination.k8s.io",
"discovery.k8s.io",
"events.k8s.io",
"extensions",
"flowcontrol.apiserver.k8s.io",
"internal.apiserver.k8s.io",
"networking.k8s.io",
"node.k8s.io",
"policy",
"rbac.authorization.k8s.io",
"resource.k8s.io",
"scheduling.k8s.io",
"storage.k8s.io",
}
HELM_TEMPLATE_HINTS = (
".Values",
".Release",
".Chart",
".Capabilities",
".Files",
".Template",
"include ",
"tpl ",
"required ",
"toYaml",
"nindent",
"indent",
"{{- if",
"{{ if",
"{{- with",
"{{ with",
"{{- range",
"{{ range",
"{{- end",
"{{ end",
)
def looks_like_unrendered_helm_template(content, error_message):
"""Classify YAML parse failures that are likely caused by raw Helm templates."""
if "{{" not in content or "}}" not in content:
return False
if any(marker in content for marker in HELM_TEMPLATE_HINTS):
return True
# PyYAML commonly reports this when encountering template actions in scalar positions.
return "unhashable key" in error_message
def parse_yaml_file(file_path):
"""Parse a YAML file that may contain multiple documents."""
try:
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
except Exception as e:
print(f"Error reading file {file_path}: {e}", file=sys.stderr)
return None
try:
return list(yaml.safe_load_all(content))
except yaml.YAMLError as e:
err = str(e)
if looks_like_unrendered_helm_template(content, err):
print(
f"Error: {file_path} appears to be an unrendered Helm template. "
f"Run 'helm template <release> <chart> --output-dir ./rendered' "
f"first, then pass the rendered files to this script.",
file=sys.stderr,
)
else:
print(f"Error parsing YAML file {file_path}: {e}", file=sys.stderr)
return None
except Exception as e:
print(f"Unexpected error while parsing YAML file {file_path}: {e}", file=sys.stderr)
return None
def is_standard_k8s_resource(api_version):
"""Check if a resource is a standard Kubernetes resource."""
# Core API group has no slash (e.g. "v1")
if api_version == "v1":
return True
if "/" not in api_version:
return False
group = api_version.split("/", 1)[0]
return group in STANDARD_API_GROUPS
def extract_resource_info(doc):
"""Extract resource information from a Kubernetes resource document."""
if not doc or not isinstance(doc, dict):
return None
kind = doc.get("kind")
api_version = doc.get("apiVersion")
if not kind or not api_version:
return None
# Extract group and version from apiVersion.
# Three canonical forms:
# "v1" → core group (no group prefix, Kubernetes built-in)
# "apps/v1" → group "apps", version "v1"
# "cert-manager.io/v1" → group "cert-manager.io", version "v1"
if "/" in api_version:
group, version = api_version.split("/", 1)
elif api_version == "v1":
group = "core"
version = "v1"
else:
# Non-standard: no slash, not "v1". Use the full apiVersion string as
# the group so the output is consistent with isCRD=True.
group = api_version
version = api_version
is_crd = not is_standard_k8s_resource(api_version)
return {
"kind": kind,
"apiVersion": api_version,
"group": group,
"version": version,
"isCRD": is_crd,
"name": doc.get("metadata", {}).get("name", "unnamed"),
}
def main():
if len(sys.argv) < 2:
print("Usage: detect_crd.py <yaml-file> [yaml-file ...]", file=sys.stderr)
sys.exit(1)
file_paths = sys.argv[1:]
resources = []
has_errors = False
for file_path in file_paths:
path = Path(file_path)
if not path.exists():
print(f"File not found: {file_path}", file=sys.stderr)
has_errors = True
continue
documents = parse_yaml_file(path)
if documents is None:
has_errors = True
continue
for doc in documents:
resource_info = extract_resource_info(doc)
if resource_info:
resources.append(resource_info)
# Output as JSON for easy parsing
print(json.dumps(resources, indent=2))
if has_errors:
sys.exit(1)
if __name__ == "__main__":
main()
#!/bin/bash
# Generate standard Helm helpers (_helpers.tpl) for a chart
set -euo pipefail
# Parse arguments
FORCE=false
CHART_DIR=""
usage() {
echo "Usage: $0 [OPTIONS] <chart-directory>"
echo ""
echo "Options:"
echo " -f, --force Overwrite existing _helpers.tpl without prompting"
echo " -h, --help Show this help message"
echo ""
echo "Examples:"
echo " $0 ./mychart"
echo " $0 --force ./mychart"
exit 0
}
while [[ $# -gt 0 ]]; do
case $1 in
-f|--force)
FORCE=true
shift
;;
-h|--help)
usage
;;
-*)
echo "❌ Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
*)
if [ -z "$CHART_DIR" ]; then
CHART_DIR="$1"
else
echo "❌ Multiple chart directories specified"
exit 1
fi
shift
;;
esac
done
if [ -z "$CHART_DIR" ]; then
echo "❌ Error: No chart directory specified"
echo "Use --help for usage information"
exit 1
fi
if [ ! -d "$CHART_DIR" ]; then
echo "❌ Error: Directory '$CHART_DIR' does not exist"
exit 1
fi
if [ ! -f "$CHART_DIR/Chart.yaml" ]; then
echo "❌ Error: Chart.yaml not found in '$CHART_DIR'"
exit 1
fi
HELPERS_FILE="$CHART_DIR/templates/_helpers.tpl"
# Get chart name from Chart.yaml using awk (no yq dependency)
CHART_NAME=$(awk '/^name:/ {gsub(/^name:[[:space:]]*/, ""); gsub(/["\047]/, ""); print; exit}' "$CHART_DIR/Chart.yaml" 2>/dev/null)
if [ -z "$CHART_NAME" ]; then
echo "❌ Error: Could not read chart name from Chart.yaml"
exit 1
fi
echo "Generating Helm helpers for chart: $CHART_NAME"
echo
# Check if _helpers.tpl already exists
if [ -f "$HELPERS_FILE" ]; then
echo "⚠️ Warning: $HELPERS_FILE already exists"
echo " This script will overwrite the existing file."
echo
if [ "$FORCE" = true ]; then
echo "ℹ️ Force mode enabled - proceeding without prompt"
else
# Check if stdin is a terminal (interactive mode)
if [ -t 0 ]; then
read -p "Continue? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Aborted."
exit 0
fi
else
echo "❌ Error: _helpers.tpl exists and running in non-interactive mode"
echo " Use --force flag to overwrite without prompting"
exit 1
fi
fi
else
# Create templates directory if it doesn't exist
mkdir -p "$CHART_DIR/templates"
fi
cat > "$HELPERS_FILE" << EOF
{{/*
Expand the name of the chart.
*/}}
{{- define "${CHART_NAME}.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "${CHART_NAME}.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- \$name := default .Chart.Name .Values.nameOverride }}
{{- if contains \$name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name \$name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "${CHART_NAME}.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "${CHART_NAME}.labels" -}}
helm.sh/chart: {{ include "${CHART_NAME}.chart" . }}
{{ include "${CHART_NAME}.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "${CHART_NAME}.selectorLabels" -}}
app.kubernetes.io/name: {{ include "${CHART_NAME}.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "${CHART_NAME}.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "${CHART_NAME}.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
EOF
echo "✅ Generated standard helpers in: $HELPERS_FILE"
echo
echo "Generated helper templates:"
echo " • ${CHART_NAME}.name - Expand chart name"
echo " • ${CHART_NAME}.fullname - Create fully qualified app name"
echo " • ${CHART_NAME}.chart - Chart name and version label"
echo " • ${CHART_NAME}.labels - Common labels"
echo " • ${CHART_NAME}.selectorLabels - Selector labels"
echo " • ${CHART_NAME}.serviceAccountName - Service account name"
echo
echo "Usage in templates:"
echo " metadata:"
echo " name: {{ include \"${CHART_NAME}.fullname\" . }}"
echo " labels:"
echo " {{- include \"${CHART_NAME}.labels\" . | nindent 4 }}"
echo
echo "Next steps:"
echo " 1. Review the generated helpers"
echo " 2. Update your templates to use these helpers"
echo " 3. Test with: helm template <release-name> $CHART_DIR"
#!/bin/bash
# Check for required Helm validation tools and provide installation instructions
set -euo pipefail
echo "Checking for Helm chart validation tools..."
echo
MISSING_TOOLS=()
OPTIONAL_TOOLS=()
HELM_AVAILABLE=false
# Check for helm (required)
if ! command -v helm &> /dev/null; then
echo "❌ helm not found (REQUIRED)"
MISSING_TOOLS+=("helm")
else
HELM_AVAILABLE=true
HELM_VERSION=$(helm version --short 2>/dev/null || helm version)
echo "✅ helm found: $HELM_VERSION"
# Check if Helm 3+
if [[ "$HELM_VERSION" =~ v([0-9]+)\. ]]; then
HELM_MAJOR="${BASH_REMATCH[1]}"
if [ "$HELM_MAJOR" -lt 3 ]; then
echo "⚠️ Warning: Helm 3+ is required. Found: $HELM_VERSION"
fi
else
echo "⚠️ Warning: Unable to determine Helm major version from: $HELM_VERSION"
fi
fi
# Check for yamllint (required)
if ! command -v yamllint &> /dev/null; then
echo "❌ yamllint not found (REQUIRED)"
MISSING_TOOLS+=("yamllint")
else
echo "✅ yamllint found: $(yamllint --version)"
fi
# Check for kubeconform (required)
if ! command -v kubeconform &> /dev/null; then
echo "❌ kubeconform not found (REQUIRED)"
MISSING_TOOLS+=("kubeconform")
else
echo "✅ kubeconform found: $(kubeconform -v)"
fi
# Check for kubectl (optional but recommended)
if ! command -v kubectl &> /dev/null; then
echo "⚠️ kubectl not found (OPTIONAL - needed for cluster dry-run)"
OPTIONAL_TOOLS+=("kubectl")
else
echo "✅ kubectl found: $(kubectl version --client --short 2>/dev/null || kubectl version --client)"
fi
# Check for yq (optional but helpful)
if ! command -v yq &> /dev/null; then
echo "⚠️ yq not found (OPTIONAL - helpful for YAML manipulation)"
OPTIONAL_TOOLS+=("yq")
else
echo "✅ yq found: $(yq --version)"
fi
# Check for helm-diff plugin (optional but helpful for upgrades)
if [ "$HELM_AVAILABLE" = true ] && helm plugin list 2>/dev/null | grep -q "diff"; then
echo "✅ helm-diff plugin found"
else
if [ "$HELM_AVAILABLE" = true ]; then
echo "⚠️ helm-diff plugin not found (OPTIONAL - helpful for upgrade validation)"
OPTIONAL_TOOLS+=("helm-diff")
fi
fi
echo
if [ ${#MISSING_TOOLS[@]} -eq 0 ]; then
echo "✅ All required tools are installed!"
if [ ${#OPTIONAL_TOOLS[@]} -gt 0 ]; then
echo
echo "⚠️ Optional tools missing: ${OPTIONAL_TOOLS[*]}"
echo " These tools provide additional functionality but are not required."
fi
exit 0
else
echo "❌ Missing required tools: ${MISSING_TOOLS[*]}"
echo
echo "Installation instructions:"
echo
for tool in "${MISSING_TOOLS[@]}"; do
case $tool in
helm)
echo "📦 helm:"
echo " macOS: brew install helm"
echo " Linux: curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash"
echo " Windows: choco install kubernetes-helm"
echo " Manual: https://helm.sh/docs/intro/install/"
echo
;;
yamllint)
echo "📦 yamllint:"
echo " macOS: brew install yamllint"
echo " Linux: pip install yamllint"
echo " Ubuntu: apt-get install yamllint"
echo " Windows: pip install yamllint"
echo
;;
kubeconform)
echo "📦 kubeconform:"
echo " macOS: brew install kubeconform"
echo " Linux: Download from https://github.com/yannh/kubeconform/releases"
echo " Windows: Download from https://github.com/yannh/kubeconform/releases"
echo " Or use: go install github.com/yannh/kubeconform/cmd/kubeconform@latest"
echo
;;
esac
done
if [ ${#OPTIONAL_TOOLS[@]} -gt 0 ]; then
echo
echo "Optional tools installation:"
echo
for tool in "${OPTIONAL_TOOLS[@]}"; do
case $tool in
kubectl)
echo "📦 kubectl:"
echo " macOS: brew install kubectl"
echo " Linux: https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/"
echo " Windows: choco install kubernetes-cli"
echo
;;
yq)
echo "📦 yq:"
echo " macOS: brew install yq"
echo " Linux: Download from https://github.com/mikefarah/yq/releases"
echo " Windows: choco install yq"
echo
;;
helm-diff)
echo "📦 helm-diff plugin:"
echo " helm plugin install https://github.com/databus23/helm-diff"
echo
;;
esac
done
fi
exit 1
fi
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
DETECT_CRD_WRAPPER="$SKILL_DIR/scripts/detect_crd_wrapper.sh"
STRUCTURE_VALIDATOR="$SKILL_DIR/scripts/validate_chart_structure.sh"
STAGE9_TEST="$SKILL_DIR/test/test_stage9_workload.sh"
CRD_README="$SKILL_DIR/test/test-crd-chart/README.md"
TMP_DIR="$(mktemp -d)"
cleanup() {
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
PASS=0
FAIL=0
pass() {
echo " PASS: $1"
PASS=$((PASS + 1))
}
fail() {
echo " FAIL: $1"
FAIL=$((FAIL + 1))
}
assert_file_contains() {
local label="$1"
local file="$2"
local pattern="$3"
if grep -Eq "$pattern" "$file"; then
pass "$label"
else
fail "$label (missing pattern: $pattern)"
echo " file: $file"
fi
}
assert_file_not_contains() {
local label="$1"
local file="$2"
local pattern="$3"
if grep -Eq "$pattern" "$file"; then
fail "$label (unexpected pattern: $pattern)"
echo " file: $file"
else
pass "$label"
fi
}
assert_exit_code() {
local label="$1"
local expected="$2"
local actual="$3"
if [[ "$actual" -eq "$expected" ]]; then
pass "$label"
else
fail "$label (expected $expected, got $actual)"
fi
}
echo "Running helm-validator regression tests..."
echo ""
echo "[P0] detect_crd.py parse-first template detection"
LITERAL_YAML="$TMP_DIR/literal-braces.yaml"
cat > "$LITERAL_YAML" <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: literal-braces
data:
token: "{{username}}"
EOF
set +e
bash "$DETECT_CRD_WRAPPER" "$LITERAL_YAML" > "$TMP_DIR/literal.json" 2> "$TMP_DIR/literal.err"
rc=$?
set -e
assert_exit_code "valid YAML with literal braces exits zero" 0 "$rc"
assert_file_contains "valid literal braces manifest is parsed as ConfigMap" "$TMP_DIR/literal.json" '"kind":[[:space:]]*"ConfigMap"'
assert_file_not_contains "valid literal braces manifest does not emit unrendered-template error" "$TMP_DIR/literal.err" "unrendered Helm template"
UNRENDERED_YAML="$TMP_DIR/unrendered-template.yaml"
cat > "$UNRENDERED_YAML" <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Values.name }}
data:
token: "example"
EOF
set +e
bash "$DETECT_CRD_WRAPPER" "$UNRENDERED_YAML" > "$TMP_DIR/unrendered.json" 2> "$TMP_DIR/unrendered.err"
rc=$?
set -e
if [[ "$rc" -ne 0 ]]; then
pass "unrendered Helm template exits non-zero"
else
fail "unrendered Helm template exits non-zero (expected non-zero, got 0)"
fi
assert_file_contains "unrendered Helm template returns actionable error" "$TMP_DIR/unrendered.err" "appears to be an unrendered Helm template"
echo ""
echo "[P1] validate_chart_structure.sh parser fallback (no yq)"
CHART_DIR="$TMP_DIR/commented-chart"
mkdir -p "$CHART_DIR/templates" "$TMP_DIR/bin"
cat > "$CHART_DIR/Chart.yaml" <<'EOF'
apiVersion: v2 # inline comment should not affect parsing
name: "commented-chart" # quoted value must parse cleanly
version: "0.1.0" # trailing comment
EOF
cat > "$CHART_DIR/values.yaml" <<'EOF'
replicaCount: 1
EOF
cat > "$CHART_DIR/templates/configmap.yaml" <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: commented-chart
EOF
cat > "$TMP_DIR/bin/yamllint" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
exit 0
EOF
chmod +x "$TMP_DIR/bin/yamllint"
set +e
PATH="$TMP_DIR/bin:/usr/bin:/bin" bash "$STRUCTURE_VALIDATOR" "$CHART_DIR" > "$TMP_DIR/structure.out" 2>&1
rc=$?
set -e
assert_exit_code "chart structure check succeeds without yq" 0 "$rc"
assert_file_not_contains "apiVersion warning is not falsely triggered by inline comment" "$TMP_DIR/structure.out" "apiVersion should be 'v2'"
assert_file_not_contains "apiVersion is not reported as missing" "$TMP_DIR/structure.out" "missing 'apiVersion'"
assert_file_not_contains "name is not reported as missing" "$TMP_DIR/structure.out" "missing 'name'"
assert_file_not_contains "version is not reported as missing" "$TMP_DIR/structure.out" "missing 'version'"
echo ""
echo "[P2] CRD docs URL pinning"
assert_file_not_contains "cert-manager URL is not using releases/latest" "$CRD_README" "releases/latest/download/cert-manager.yaml"
assert_file_contains "cert-manager URL is pinned to releases/download/<version>" "$CRD_README" "releases/download/v[0-9]+\\.[0-9]+\\.[0-9]+/cert-manager\\.yaml"
echo ""
echo "[P1] Stage 9 workload harness"
set +e
bash "$STAGE9_TEST" > "$TMP_DIR/stage9.out" 2>&1
rc=$?
set -e
if [[ "$rc" -eq 0 ]]; then
pass "Stage 9 workload harness passes"
else
fail "Stage 9 workload harness passes (expected 0, got $rc)"
echo " --- stage9 output ---"
sed 's/^/ /' "$TMP_DIR/stage9.out"
echo " --- end stage9 output ---"
fi
echo ""
echo "Regression summary: PASS=$PASS FAIL=$FAIL"
if [[ "$FAIL" -ne 0 ]]; then
exit 1
fi
echo "PASS: helm-validator regression tests"
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
CHART_DIR="$SKILL_DIR/test/test-workload-chart"
PINNED_VALUES="$CHART_DIR/values-pinned-tag.yaml"
TMP_DIR="$(mktemp -d)"
cleanup() {
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
PASS=0
FAIL=0
pass() {
echo " PASS: $1"
PASS=$((PASS + 1))
}
fail() {
echo " FAIL: $1"
FAIL=$((FAIL + 1))
}
assert_contains() {
local label="$1"
local file="$2"
local pattern="$3"
if grep -Eq "$pattern" "$file"; then
pass "$label"
else
fail "$label (missing pattern: $pattern)"
fi
}
assert_not_contains() {
local label="$1"
local file="$2"
local pattern="$3"
if grep -Eq "$pattern" "$file"; then
fail "$label (unexpected pattern: $pattern)"
else
pass "$label"
fi
}
if ! command -v helm >/dev/null 2>&1; then
echo "FAIL: helm is required for test_stage9_workload.sh"
exit 1
fi
echo "Running helm-validator Stage 9 workload regression checks..."
echo ""
DEFAULT_OUT="$TMP_DIR/default"
PINNED_OUT="$TMP_DIR/pinned"
mkdir -p "$DEFAULT_OUT" "$PINNED_OUT"
helm template stage9-default "$CHART_DIR" --output-dir "$DEFAULT_OUT" >/dev/null
helm template stage9-pinned "$CHART_DIR" --values "$PINNED_VALUES" --output-dir "$PINNED_OUT" >/dev/null
DEFAULT_DEPLOYMENT="$(find "$DEFAULT_OUT" -type f -name "deployment.yaml" | head -n 1)"
PINNED_DEPLOYMENT="$(find "$PINNED_OUT" -type f -name "deployment.yaml" | head -n 1)"
if [[ -z "$DEFAULT_DEPLOYMENT" || -z "$PINNED_DEPLOYMENT" ]]; then
echo "FAIL: rendered deployment manifest not found"
exit 1
fi
echo "[default values]"
assert_contains "flags :latest image tag in default fixture" "$DEFAULT_DEPLOYMENT" 'image:[[:space:]]*"?nginx:latest"?'
assert_not_contains "default fixture omits pod/container securityContext" "$DEFAULT_DEPLOYMENT" 'securityContext:'
assert_not_contains "default fixture omits resources block" "$DEFAULT_DEPLOYMENT" 'resources:'
assert_not_contains "default fixture omits livenessProbe" "$DEFAULT_DEPLOYMENT" 'livenessProbe:'
assert_not_contains "default fixture omits readinessProbe" "$DEFAULT_DEPLOYMENT" 'readinessProbe:'
echo ""
echo "[pinned image override]"
assert_not_contains "pinned values remove :latest image tag" "$PINNED_DEPLOYMENT" 'image:[[:space:]]*"?[^"]*:latest"?'
assert_contains "pinned values render explicit pinned tag" "$PINNED_DEPLOYMENT" 'image:[[:space:]]*"?nginx:1\.27\.2"?'
assert_not_contains "other Stage 9 omissions remain for pinned profile" "$PINNED_DEPLOYMENT" 'securityContext:'
echo ""
echo "Stage 9 summary: PASS=$PASS FAIL=$FAIL"
if [[ "$FAIL" -ne 0 ]]; then
exit 1
fi
echo "PASS: Stage 9 workload regression checks"
# Patterns to ignore when building packages.
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
# OS files
.DS_Store
Thumbs.db
# CI/CD
.github/
.gitlab-ci.yml
.travis.yml
.circleci/
Jenkinsfile
# Helm-related
*.lock
requirements.lockapiVersion: v2
name: test-crd-chart
description: A test chart with CRDs
type: application
version: 0.1.0
appVersion: "1.0.0"
{{- if .Values.certManager.enabled }}
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: {{ .Release.Name }}-cert
spec:
secretName: {{ .Release.Name }}-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
{{- range .Values.certManager.certificate.dnsNames }}
- {{ . }}
{{- end }}
{{- end }}{{- if .Values.prometheus.enabled }}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ .Release.Name }}-monitor
spec:
selector:
matchLabels:
app: {{ .Release.Name }}
endpoints:
- port: metrics
interval: 30s
{{- end }}
certManager:
enabled: true
certificate:
dnsNames:
- example.com
prometheus:
enabled: true
# Patterns to ignore when building packages.
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
# OS files
.DS_Store
Thumbs.db
# CI/CD
.github/
.gitlab-ci.yml
.travis.yml
.circleci/
Jenkinsfile
# Helm-related
*.lock
requirements.lock
image:
tag: "1.27.2"
Related skills
How it compares
Pick helm-validator over generic YAML linters when you need Helm-native linting, rendered-manifest schema checks, CRD documentation lookup, and optional cluster dry-run in one workflow.
FAQ
What tools does helm-validator require?
helm-validator expects Helm v3+, yamllint, and kubeconform via scripts/setup_tools.sh, with optional kubectl for Stage 8 cluster dry-run. Missing tools trigger documented fallback paths rather than aborting the full ten-stage report.
Does helm-validator modify Helm chart files automatically?
helm-validator is read-only by default and produces analysis plus proposed remediations. The skill only edits Chart.yaml, values.yaml, or templates when the developer explicitly asks to apply fixes after reviewing the report.
How many validation stages does helm-validator run?
helm-validator executes ten sequential stages covering tool checks, chart structure, helm lint, template rendering, YAML syntax, CRD detection, kubeconform schema validation, optional dry-run, security checks, and a mandatory final summary table.