| name | plugin-helm |
| description | Guidelines for maintaining the Kubebuilder Helm plugin (helm/v2-alpha) in pkg/plugins/optional/helm/v2alpha/. Use when working on Helm plugin code, templates, or chart generation. |
| license | Apache-2.0 |
| metadata | {"author":"The Kubernetes Authors"} |
Plugin Purpose
Transform kustomize YAML → Helm chart for operator distribution.
Default: Reads dist/install.yaml, writes to dist/chart/
Flags: --manifests=<input> and/or --output-dir=<output>
Development Workflow
After code changes in pkg/plugins/optional/helm/v2alpha/:
make install - Build and install updated binary
make generate-charts - Regenerate all sample charts in testdata
make verify-helm - Run yamllint + helm lint + kube-linter
- Test installation in real cluster if changing generation logic
Never commit without running make verify-helm.
Values.yaml Rules
Comment Syntax (Strict)
## = Documentation/description
# = Commented-out spec value
- Always blank
## line between description and spec
Uncomment Policy
Uncomment a value only if:
- Extracted from kustomize - The value exists in the source manifests
- Standard Helm convention - Common fields users expect uncommented (replicas, image, resource names)
Keep commented if:
- Optional Kubernetes feature - Not currently used in the operator (imagePullSecrets, priorityClassName)
- Advanced configuration - Not needed for basic usage (topology spread, pod disruption budget)
- User customization fields - Name overrides, custom labels that users should explicitly set
Template Conditional Pattern (Critical)
All values.yaml fields MUST be conditional in templates. If a user removes a field from values.yaml, the chart must still work.
Required patterns:
Use {{- with }} for optional blocks:
{{- with .Values.manager.affinity }}
affinity: {{ toYaml . | nindent 10 }}
{{- end }}
Use {{- if }} with fallback for required fields:
resources:
{{- if .Values.manager.resources }}
{{- toYaml .Values.manager.resources | nindent 10 }}
{{- else }}
{}
{{- end }}
Never: Direct value reference without conditionals
affinity: {{ toYaml .Values.manager.affinity | nindent 10 }}
When to use | default vs conditionals:
When adding new values.yaml fields, always add conditionals in templates.
Comment Content Rules (values.yaml)
- One to two lines max - Be concise
- Focus on user action - What they can configure, not how it works internally
- Include examples - Show correct YAML structure for commented values
- Use plain English - Follow Kubernetes Documentation Style Guide
- End with blank
## line
Good:
Bad:
Code Style
Go Code Comments
- Be concise - Explain WHY, not WHAT (code should be self-documenting)
- No unnecessary comments - Well-named functions/variables > comments
- Plain text only - No arrows, emojis, or decorative symbols
- Follow Go conventions - See Effective Go
Good:
func TemplatePorts(yamlContent string, resource *unstructured.Unstructured) string {
Bad:
func TemplatePorts(yamlContent string, resource *unstructured.Unstructured) string {
Code Generation Logic
The plugin uses two different Machinery approaches:
Dynamic Templates (from kustomize)
Location: pkg/plugins/optional/helm/v2alpha/scaffolds/internal/kustomize/
All Kubernetes resources from dist/install.yaml are converted using the templater:
- Parses kustomize YAML → Applies Helm templating → Outputs to
templates/
- Uses
DynamicTemplate wrapper (pre-rendered content, not Go templates)
- Always overwrites (
IfExistsAction = OverwriteFile)
- Examples:
manager/manager.yaml, rbac/*.yaml, webhook/*.yaml
Key file: dynamic_template.go - Wraps pre-rendered templates for Machinery
Boilerplate Templates (fixed files)
Location: pkg/plugins/optional/helm/v2alpha/scaffolds/internal/templates/
Static chart files use standard Machinery Go templates:
Chart.yaml - Never overwrites (IfExistsAction = SkipFile)
values.yaml, NOTES.txt, _helpers.tpl, .helmignore - Respect --force flag
- Generated from Go templates, not kustomize output
Important: Never delete the templates/ directory. Let Machinery overwrite files in place.
Values.yaml Generation
When modifying what appears in values.yaml:
- Check
Extraction struct for available extracted data
- Use conditionals to only render if value exists
- Maintain comment/uncomment logic based on rules above
- Test with multiple project types (with/without webhooks, metrics, etc.)
- Follow Kubernetes Documentation Style Guide for all user-facing comments
Critical Design Decisions
CRD Placement: templates/crd/ not crds/
Decision: CRDs are placed in templates/crd/ instead of the standard crds/ directory.
Rationale: Helm's crds/ directory never upgrades CRDs on helm upgrade. For Kubernetes operators, CRD updates are critical (new API fields, validation changes). Placing CRDs in templates/crd/ ensures they upgrade with the chart.
Trade-off: Cannot mix CRDs and Custom Resources in the same chart (Helm installation order limitation).
Action: Preserve this placement. Do not move CRDs to crds/ directory.
File Preservation
The plugin uses Kubebuilder's Machinery framework for file management:
Never overwrite: Chart.yaml (user-managed version info)
Overwrite only with --force: values.yaml, NOTES.txt, _helpers.tpl, .helmignore, .github/workflows/test-chart.yml, network-policy/allow-metrics-traffic.yaml, network-policy/allow-webhook-traffic.yaml
Always overwrite: All templates/ resources (manager, rbac, webhook, etc.)
Important: Use Machinery's IfExistsAction to control overwrite behavior. Never delete the templates/ directory or individual files - let Machinery overwrite them in place. This preserves file timestamps and avoids breaking user workflows (e.g., open editors, file watchers).
Documentation Updates
Plugin documentation must stay synchronized with code changes.
When modifying plugin behavior:
- Update
docs/book/src/plugins/available/helm-v2-alpha.md to reflect changes
- Follow Kubebuilder tutorial-based documentation style:
- Be concise
- Example-driven with code snippets showing actual usage
- Clear section structure: What - Why/When - Usage - Examples (if required)
- Follow Kubernetes Documentation Style Guide
- Add specific error scenarios only for known, reproducible errors with fixes
Common Tasks
Adding a New values.yaml Field
- Check if value exists in
Extraction struct
- Add extraction logic in the extractor
- Add rendering logic in values generation
- Follow comment rules above
- Add conditional usage in template (use
{{- with }} or {{- if }} - see Template Conditional Pattern)
- Run
make install && make generate-charts && make verify-helm
Changing Chart Structure
- Modify chart scaffolder orchestration
- Update template generators if needed
- Regenerate all samples with
make generate-charts
- Update plugin documentation if user-visible
Fixing Template Generation
- Locate template in the templates directory
- Modify template logic
- Test with
make install and run plugin on test project
- Run
make generate-charts to update all samples
- Check diffs carefully - ensure no unintended changes
- Run
make verify-helm
Testing
Test Structure (Ginkgo/Gomega BDD)
All tests use Ginkgo v2 + Gomega for behavior-driven development:
var _ = Describe("Feature Name", func() {
Context("Scenario description", func() {
It("should do specific behavior", func() {
Expect(result).To(Equal(expected))
})
})
})
- Unit tests (
*_test.go): Fast, isolated component tests
- Integration tests (
//go:build integration): End-to-end chart generation
Running Tests
make test-unit
make test-integration
make verify-helm
Test Coverage Requirements
When adding features, ensure tests cover:
- Extraction scenarios: Does it extract the new field from kustomize YAML?
- Generation scenarios: Does it render correctly in values.yaml?
- Template scenarios: Does the template handle missing/present values?
- Edge cases:
- Field present vs absent in kustomize
- Field removed from values.yaml by user
- Different data types (string, int, bool, map, array)
- Empty values vs nil vs not present
BDD pattern example:
Describe("Webhook port extraction", func() {
Context("when webhook is present in kustomize", func() {
It("should extract port and add to values.yaml", func() { ... })
It("should use port conditionally in template", func() { ... })
})
Context("when webhook is not present", func() {
It("should not add webhook section to values.yaml", func() { ... })
It("should not render webhook port in template", func() { ... })
})
Context("when user removes webhook from values.yaml", func() {
It("should not break template rendering", func() { ... })
})
})
Testing Checklist
Before submitting PR:
References
See references/REFERENCE.md for:
- Documentation links
- Helm best practices
- Kubernetes operator resources
- Sample chart locations