feat: introduce Helm and Kustomization templates for module management - #33
Conversation
iasthc
commented
Mar 4, 2026
- Added HelmChartTemplate and KustomizationTemplate types to define how modules are deployed via Helm and Kustomize.
- Updated Module and ModuleTemplate to support HelmChart and Kustomization specifications, ensuring mutual exclusivity.
- Enhanced ModuleStatus to capture the observed state of Helm releases and Kustomizations.
- Implemented deepcopy functions for new types to ensure compatibility.
- Updated CRD definitions to reflect changes in module management and resource tracking.
- Added HelmChartTemplate and KustomizationTemplate types to define how modules are deployed via Helm and Kustomize. - Updated Module and ModuleTemplate to support HelmChart and Kustomization specifications, ensuring mutual exclusivity. - Enhanced ModuleStatus to capture the observed state of Helm releases and Kustomizations. - Implemented deepcopy functions for new types to ensure compatibility. - Updated CRD definitions to reflect changes in module management and resource tracking.
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a fundamental shift in how modules are managed by directly embedding Helm chart and Kustomization specifications into the Highlights
Changelog
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a significant and valuable refactoring by replacing the dependency on FluxCD CRDs with native Helm and Kustomize management within the operator. The introduction of HelmChartTemplate and KustomizationTemplate with strongly-typed schemas is a great improvement over using runtime.RawExtension, enhancing validation and usability. However, there are significant discrepancies in the generated CRD files. Specifically, several validation patterns are missing in the ModuleTemplate CRD, and a field name mismatch in the Module CRD's InventoryEntry will likely break resource tracking and garbage collection, which could lead to security and reliability issues. Additionally, a confusing comment in the HelmChartTemplate regarding version constraints was identified. Addressing these points will ensure the new API is robust, consistent, secure, and reliable.
| ref: | ||
| description: |- | ||
| Ref specifies the Git reference to check out. | ||
| If not set, the default branch (usually main) is used. | ||
| properties: | ||
| branch: | ||
| description: Branch is the Git branch to check out. | ||
| type: string | ||
| commit: | ||
| description: Commit is the Git commit SHA to check out. | ||
| pattern: ^[a-f0-9]{7,40}$ | ||
| type: string | ||
| semver: | ||
| description: |- | ||
| Semver is a semver range expression used to select the latest | ||
| matching Git tag. | ||
| type: string | ||
| tag: | ||
| description: Tag is the Git tag to check out. | ||
| type: string | ||
| type: object |
There was a problem hiding this comment.
The GitReference type has a CEL validation to ensure at most one of branch, tag, commit, or semver is set. This validation is missing from the generated CRD for the ref field. This means the API server will not enforce this important constraint. It's possible make manifests was not run after adding the validation rule, or there's a configuration issue with controller-gen.
ref:
description: |-
Ref specifies the Git reference to check out.
If not set, the default branch (usually main) is used.
properties:
branch:
description: Branch is the Git branch to check out.
type: string
commit:
description: Commit is the Git commit SHA to check out.
pattern: ^[a-f0-9]{7,40}$
type: string
semver:
description: |-
Semver is a semver range expression used to select the latest
matching Git tag.
type: string
tag:
description: Tag is the Git tag to check out.
type: string
type: object
x-kubernetes-validations:
- message: at most one of branch, tag, commit, or semver may be set
rule: '[has(self.branch) && self.branch != '''', has(self.tag) && self.tag != '''', has(self.commit) && self.commit != '''', has(self.semver) && self.semver != ''''].filter(x, x).size() <= 1'| releaseName: | ||
| description: |- | ||
| ReleaseName overrides the Helm release name. | ||
| Defaults to the Module name if not specified. | ||
| maxLength: 53 | ||
| type: string | ||
| repoURL: | ||
| description: |- | ||
| RepoURL is the URL of the Helm chart repository. | ||
| Supports HTTP/HTTPS Helm repositories and OCI registries (oci://). | ||
| minLength: 1 | ||
| type: string | ||
| secretRef: | ||
| description: |- | ||
| SecretRef references a Secret in the Module's resolved namespace | ||
| containing credentials for the Helm repository. | ||
| Supported keys: username, password (Basic Auth), caFile, certFile, keyFile (TLS). | ||
| properties: | ||
| name: | ||
| description: Name is the name of the Secret. | ||
| minLength: 1 | ||
| type: string | ||
| required: | ||
| - name | ||
| type: object | ||
| timeout: | ||
| description: |- | ||
| Timeout is the maximum duration for any single Helm operation. | ||
| Defaults to 5m if not specified. | ||
| type: string | ||
| upgrade: | ||
| description: Upgrade configures the Helm upgrade strategy. | ||
| properties: | ||
| cleanupOnFail: | ||
| description: CleanupOnFail rolls back changes on upgrade failure. | ||
| type: boolean | ||
| enableRollback: | ||
| description: EnableRollback triggers an automatic rollback | ||
| when an upgrade fails. | ||
| type: boolean | ||
| force: | ||
| description: Force forces resource updates through a replacement | ||
| strategy. | ||
| type: boolean | ||
| maxRetries: | ||
| description: |- | ||
| MaxRetries is the maximum number of retries before marking the release as failed. | ||
| Defaults to 0 (no retries). | ||
| minimum: 0 | ||
| type: integer | ||
| type: object | ||
| values: | ||
| description: |- | ||
| Values holds the default Helm chart values as arbitrary JSON. | ||
| Module.Spec.Values can override these on a per-instance basis. | ||
| type: object | ||
| x-kubernetes-preserve-unknown-fields: true | ||
| version: | ||
| description: |- | ||
| Version is the chart version to install, following semver constraints. | ||
| If empty, the latest version is used. | ||
| type: string | ||
| required: | ||
| - chart | ||
| - interval | ||
| - repoURL | ||
| type: object | ||
| x-kubernetes-preserve-unknown-fields: true | ||
| kustomization: | ||
| description: |- | ||
| Kustomization defines the FluxCD Kustomization spec template. | ||
| The actual schema is composed at runtime by the Schema RPC from the FluxCD Kustomization CRD. | ||
| Mutually exclusive with HelmRelease (enforced via CEL). | ||
| Kustomization defines a Kustomize-based module. | ||
| The operator clones the source, builds the kustomization, and applies | ||
| the manifests using server-side apply. | ||
| Mutually exclusive with HelmChart (enforced via CEL). | ||
| properties: | ||
| force: | ||
| description: |- | ||
| Force instructs the operator to recreate resources that have | ||
| immutable field changes, instead of failing the apply. | ||
| type: boolean | ||
| interval: | ||
| description: Interval at which the operator re-reconciles this | ||
| kustomization. | ||
| type: string | ||
| patches: | ||
| description: |- | ||
| Patches is a list of strategic merge or JSON6902 patches to apply | ||
| on top of the kustomization output before sending to the cluster. | ||
| items: | ||
| description: KustomizePatch defines an inline strategic merge | ||
| or JSON6902 patch. | ||
| properties: | ||
| patch: | ||
| description: Patch is the inline YAML patch content. | ||
| type: string | ||
| target: | ||
| description: |- | ||
| Target selects which resources to apply the patch to. | ||
| If not set, the patch is applied to all matching resources. | ||
| properties: | ||
| annotationSelector: | ||
| description: AnnotationSelector filters resources by | ||
| annotations (e.g. "app=nginx"). | ||
| type: string | ||
| group: | ||
| description: Group is the API group of the target resource. | ||
| type: string | ||
| kind: | ||
| description: Kind is the kind of the target resource. | ||
| type: string | ||
| labelSelector: | ||
| description: LabelSelector filters resources by labels | ||
| (e.g. "app=nginx"). | ||
| type: string | ||
| name: | ||
| description: Name of the target resource. | ||
| type: string | ||
| namespace: | ||
| description: Namespace of the target resource. | ||
| type: string | ||
| version: | ||
| description: Version is the API version of the target | ||
| resource. | ||
| type: string | ||
| type: object | ||
| required: | ||
| - patch | ||
| type: object | ||
| type: array | ||
| path: | ||
| description: |- | ||
| Path is the directory path within the repository where | ||
| kustomization.yaml is located. Defaults to the repository root. | ||
| type: string | ||
| prune: | ||
| description: |- | ||
| Prune enables garbage collection: resources that were previously | ||
| applied but are no longer present in the kustomization output | ||
| will be deleted from the cluster. | ||
| type: boolean | ||
| ref: | ||
| description: |- | ||
| Ref specifies the Git reference to check out. | ||
| If not set, the default branch (usually main) is used. | ||
| properties: | ||
| branch: | ||
| description: Branch is the Git branch to check out. | ||
| type: string | ||
| commit: | ||
| description: Commit is the Git commit SHA to check out. | ||
| pattern: ^[a-f0-9]{7,40}$ | ||
| type: string | ||
| semver: | ||
| description: |- | ||
| Semver is a semver range expression used to select the latest | ||
| matching Git tag. | ||
| type: string | ||
| tag: | ||
| description: Tag is the Git tag to check out. | ||
| type: string | ||
| type: object | ||
| secretRef: | ||
| description: |- | ||
| SecretRef references a Secret containing credentials for the Git | ||
| repository. Supported keys: username + password (HTTPS), identity + | ||
| identity.pub + known_hosts (SSH). | ||
| properties: | ||
| name: | ||
| description: Name is the name of the Secret. | ||
| minLength: 1 | ||
| type: string | ||
| required: | ||
| - name | ||
| type: object | ||
| targetNamespace: | ||
| description: |- | ||
| TargetNamespace overrides the namespace for all resources in the | ||
| kustomization output. If empty, each resource keeps its own namespace. | ||
| maxLength: 63 | ||
| pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?)$ | ||
| type: string | ||
| timeout: | ||
| description: |- | ||
| Timeout is the maximum duration for the build and apply operation. | ||
| Defaults to 5m if not specified. | ||
| type: string | ||
| url: | ||
| description: |- | ||
| URL is the Git repository URL containing the kustomization. | ||
| Supports HTTPS and SSH URLs. | ||
| minLength: 1 | ||
| type: string |
There was a problem hiding this comment.
The ModuleTemplate CRD is missing validation patterns for several critical fields, including repoURL, releaseName, and url. While these patterns are defined in the Go structs using kubebuilder annotations, they are not reflected in the generated CRD. This allows potentially malicious or malformed input to bypass API-level validation, which could lead to injection vulnerabilities if the operator uses these values in sensitive operations (e.g., shell commands for Helm or Git).
| v: | ||
| description: Version is the API version of the resource (e.g. | ||
| "v1", "apps/v1"). | ||
| type: string | ||
| required: | ||
| - id | ||
| - v |
There was a problem hiding this comment.
There is a critical field name mismatch in the InventoryEntry definition within the Module CRD. The Go struct defines the field as Version with a JSON tag of version, but the CRD incorrectly uses the name v. This discrepancy will cause serialization issues and prevent the API version of managed resources from being correctly persisted or retrieved, which will likely break the operator's garbage collection (pruning) logic and could lead to resource leakage (DoS).
version:
description: Version is the API version of the resource (e.g.
"v1", "apps/v1").
type: string
required:
- id
- version| // Version is the exact chart version to install (e.g. "1.2.3"). | ||
| // If empty, the latest version is used. |
There was a problem hiding this comment.
The comment for Version states it must be an "exact chart version", which contradicts the generated CRD which mentions support for "semver constraints". This is confusing for API users. Please clarify if semver constraints are supported and update the comment accordingly for consistency.
| // Version is the exact chart version to install (e.g. "1.2.3"). | |
| // If empty, the latest version is used. | |
| // Version is the chart version to install, which can be a fixed version | |
| // or a semver constraint (e.g., "~1.2.3", "^1.2.3"). | |
| // If empty, the latest version is used. |
There was a problem hiding this comment.
Pull request overview
This PR introduces first-class Helm chart and Kustomize (Git-based) templates for ModuleTemplate, updates Module/ModuleStatus to track the observed state of the underlying deployment, and updates generated artifacts (CRDs + deepcopy) to match the new API surface.
Changes:
- Add typed
HelmChartTemplate/KustomizationTemplate(and related status + helper types) underaddons/v1alpha1. - Replace FluxCD
HelmRelease/Kustomizationraw-extension templates with strongly typed Helm/Kustomize specs and updated status fields (including inventory tracking). - Update generated CRDs and deepcopy implementations to reflect the new fields.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
go.mod / go.sum |
Dependency updates from API + controller-gen regeneration. |
config/crd/bases/addons.otterscale.io_moduletemplates.yaml |
CRD schema updated for helmChart and typed kustomization template fields + CEL exclusivity rule. |
config/crd/bases/addons.otterscale.io_modules.yaml |
CRD schema updated for new status fields (helmRelease, kustomization, inventory) and revised descriptions. |
addons/v1alpha1/moduletemplate_types.go |
API change: helmChart / kustomization are now typed templates and mutually exclusive. |
addons/v1alpha1/module_types.go |
API change: status now stores observed Helm/Kustomize state + inventory (instead of refs). |
addons/v1alpha1/helm_types.go |
New Helm template + status types (repo/chart/version/values/upgrade/etc). |
addons/v1alpha1/kustomization_types.go |
New Kustomization template + status types (git URL/ref/path/patches/etc) + inventory/secret refs. |
addons/v1alpha1/zz_generated.deepcopy.go |
Regenerated deep-copies for the newly introduced types and updated status/spec fields. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| status: | ||
| description: Status is the Helm release status (e.g. deployed, | ||
| failed, pending-upgrade). | ||
| type: string |
There was a problem hiding this comment.
status.helmRelease.status is missing the enum validation in the generated CRD, even though the Go type (HelmReleaseStatus.Status) declares an explicit Enum. Without this, invalid statuses can be persisted and consumers can’t rely on a bounded set of values. Please regenerate the CRD (or update the schema) so the enum is present.
| type: string | |
| type: string | |
| enum: | |
| - unknown | |
| - deployed | |
| - superseded | |
| - failed | |
| - uninstalling | |
| - uninstalled | |
| - pending-install | |
| - pending-upgrade | |
| - pending-rollback |
| description: |- | ||
| ReleaseName overrides the Helm release name. | ||
| Defaults to the Module name if not specified. | ||
| maxLength: 53 |
There was a problem hiding this comment.
spec.helmChart.releaseName is missing the regex pattern validation in the CRD schema. The Go type declares a DNS-label style pattern, and without it users can submit values that Helm will later reject at runtime. Regenerate the CRDs (or add the pattern) so it matches the Go markers.
| maxLength: 53 | |
| maxLength: 53 | |
| pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$' |
| description: |- | ||
| RepoURL is the URL of the Helm chart repository. | ||
| Supports HTTP/HTTPS Helm repositories and OCI registries (oci://). | ||
| minLength: 1 |
There was a problem hiding this comment.
spec.helmChart.repoURL is missing the URL scheme pattern validation in the CRD schema (despite the Go type specifying one). This allows invalid repository URLs to be accepted and only fail later during reconciliation. Please regenerate the CRDs (or add the pattern) so it enforces https?:// or oci:// as intended.
| minLength: 1 | |
| minLength: 1 | |
| pattern: ^(https?://|oci://).* |
| description: |- | ||
| URL is the Git repository URL containing the kustomization. | ||
| Supports HTTPS and SSH URLs. | ||
| minLength: 1 |
There was a problem hiding this comment.
spec.kustomization.url in the CRD schema is missing the URL pattern validation that the Go type declares. Without it, obviously invalid Git URLs will be admitted and only fail during reconciliation. Regenerate the CRDs (or add the pattern) so the published schema matches the Go API.
| minLength: 1 | |
| minLength: 1 | |
| pattern: ^((https?|ssh|git|file)://|git@[-\w.]+:[\w./-]+)$ |
| // Version is the exact chart version to install (e.g. "1.2.3"). | ||
| // If empty, the latest version is used. |
There was a problem hiding this comment.
The Version field docs say this is an exact chart version, but the generated CRD description for spec.helmChart.version says it supports semver constraints. Please align the API documentation (and any intended validation) so users don’t get conflicting guidance about whether ranges like >=1.2.0 are supported.
| // Version is the exact chart version to install (e.g. "1.2.3"). | |
| // If empty, the latest version is used. | |
| // Version is the Helm chart version or semantic version constraint to use | |
| // (e.g. "1.2.3" for an exact version, or ">=1.2.0 <2.0.0" for a range). | |
| // If empty, the latest chart version matching the constraint is used. |
| v: | ||
| description: Version is the API version of the resource (e.g. | ||
| "v1", "apps/v1"). | ||
| type: string | ||
| required: | ||
| - id | ||
| - v |
There was a problem hiding this comment.
The CRD schema for status.inventory uses field name v (and requires it), but the Go type InventoryEntry is json:"version". This mismatch will cause controllers/clients to write version while the CRD only allows v, breaking validation and/or dropping data. Regenerate the CRDs from the Go types (or rename the JSON tag / schema) so the CRD property is version and the required list matches.
| v: | |
| description: Version is the API version of the resource (e.g. | |
| "v1", "apps/v1"). | |
| type: string | |
| required: | |
| - id | |
| - v | |
| version: | |
| description: Version is the API version of the resource (e.g. | |
| "v1", "apps/v1"). | |
| type: string | |
| required: | |
| - id | |
| - version |
- Updated various indirect dependencies in go.mod and go.sum for better compatibility and security. - Modified validation rules in KustomizationTemplate and related CRDs to enhance clarity and enforce constraints. - Added new validation patterns for fields in module templates to ensure proper formatting and requirements.