Files
Ayush Kumar 28892ccc7e Feat: implement valuesFrom support for helmchart component and update… (#7099)
* feat: implement valuesFrom support for helmchart component and update documentation examples

Signed-off-by: Anaswara Suresh <anaswarasuresh2212@gmail.com>

* fix: address cubic review feedback on valuesFrom

Three issues raised by cubic AI review on kubevela#7099:

1. docs/examples/helmchart-valuesfrom/secret-and-inline.yaml —
   Expected-result comments used incorrect paths (resources.cpu /
   resources.mem) and values (500m) that did not match the actual CM
   data. Rewrote the narrative to use the real paths
   (resources.limits.cpu / resources.limits.memory) and bundled the
   Secret inline in the manifest so the example is self-contained and
   the expected output is deterministic.

2. docs/examples/helmchart-valuesfrom/secret-and-inline.yaml —
   The Secret was marked optional: true while the narrative required
   it for the merged output to match. Bundled the Secret inline and
   dropped the optional flag, removing the order/timing ambiguity.
   README.md updated to drop the now-redundant "create the Secret
   first" instruction.

3. pkg/cue/cuex/providers/helm/helm.go:Render —
   After removing the unconditional "default" fallback for
   releaseNamespace, Helm could run with an empty namespace when both
   Context.AppNamespace and Release.Namespace were unset (non-normal
   code paths — direct callers, tests, CLI tooling). Restored the
   "default" fallback at the end of the namespace resolution while
   keeping the Application-namespace plumbing for tenant-scoped
   cross-namespace rejection.

Co-authored-by: Ayush Kumar <ayushshyamkumar888@gmail.com>
Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>
Signed-off-by: Anaswara Suresh <anaswarasuresh2212@gmail.com>

* feat: add valuesFrom fingerprinting for helmchart components to trigger workflow restarts on ConfigMap/Secret changes

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* feat: add valuesFrom fingerprinting for helmchart components to trigger workflow restarts on ConfigMap/Secret changes

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* feat: enhance valuesFrom support for helmchart components with fingerprinting and error handling

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* docs: clarify cross-namespace restrictions for valuesFrom in helmchart examples

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* ci: retrigger checks

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* feat: add publishVersion support to Helm provider for stable release management

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* feat: improve error handling for application retrieval in Helm provider

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* ci: retrigger checks

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* feat: add application publishVersion lookup defense in Helm provider tests

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

---------

Signed-off-by: Anaswara Suresh <anaswarasuresh2212@gmail.com>
Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>
Co-authored-by: Anaswara Suresh <anaswarasuresh2212@gmail.com>
2026-04-28 20:02:58 -07:00

293 lines
10 KiB
CUE

// helmchart.cue - Component definition for Helm charts
import (
"vela/helm"
)
"helmchart": {
type: "component"
annotations: {
"definition.oam.dev/description": "Deploy Helm charts natively in KubeVela without FluxCD"
}
labels: {
"custom.definition.oam.dev/category": "helm"
}
attributes: {
workload: type: "autodetects.core.oam.dev"
status: {
healthPolicy: #"""
_healthCheck: {
_criteria: [] | *parameter.healthStatus
if len(_criteria) > 0 {
_criteriaResults: [ for criterion in _criteria {
_targetKind: criterion.resource.kind
_targetName: criterion.resource.name
_conditionType: criterion.condition.type
_conditionStatus: *"True" | criterion.condition.status
// Search through all outputs for matching resource
_matchingResources: [ for outputKey, resource in context.outputs
if resource.kind == _targetKind &&
(_targetName == _|_ || resource.metadata.name == _targetName) {
resource
}
]
if len(_matchingResources) > 0 {
_resource: _matchingResources[0]
if _resource.status.conditions != _|_ {
// Look for matching condition
_matchingConditions: [ for cond in _resource.status.conditions
if cond.type == _conditionType && cond.status == _conditionStatus {
cond
}
]
result: len(_matchingConditions) > 0
}
if _resource.status.conditions == _|_ {
result: false
}
}
if len(_matchingResources) == 0 {
result: false
}
} ]
_failedCriteria: [ for r in _criteriaResults if r.result == false { r } ]
result: len(_failedCriteria) == 0
}
if parameter.healthStatus == _|_ || len(_criteria) == 0 {
result: true
}
}
isHealth: _healthCheck.result
"""#
customStatus: #"""
if context.status.healthy {
message: "Deployed"
}
if !context.status.healthy {
message: "Deploying"
}
"""#
}
}
}
template: {
output: _
outputs: _
parameter: {
// Chart source configuration
chart: {
// Chart location - automatically detected based on format:
// - OCI: "oci://ghcr.io/org/charts/app"
// - Direct URL: "https://example.com/charts/app-1.0.0.tgz"
// - Repo chart: "postgresql" (requires repoURL to be set)
source: string
// Repository URL for repository-based charts
repoURL?: string
// Version/tag for repository and OCI charts (ignored for direct URLs)
version?: string | *"latest"
// Authentication (optional) - TODO: Not yet implemented
// auth?: {
// // Reference to Secret containing credentials
// secretRef?: {
// name: string
// namespace?: string | *context.namespace
// }
// }
}
// Release configuration (optional - uses context defaults)
release?: {
// Release name (defaults to component name)
name?: string | *context.name
// Target namespace (defaults to Application namespace)
namespace?: string | *context.namespace
}
// +usage=Inline values merged with the highest priority; override everything in valuesFrom.
values?: {...}
// +usage=Additional values sources merged in array order. Later entries override earlier ones on conflict, and inline `values` override everything in valuesFrom. Deep-merges map keys; arrays are replaced (not concatenated); null is preserved. On every reconcile the controller computes a content fingerprint over all referenced sources and folds it into the workflow revision; an external edit to a referenced ConfigMap/Secret triggers a `helm upgrade` on the next reconcile (default resync ~5 min). Sources are read from the control-plane cluster regardless of where the chart is deployed. Suppressed when the `app.oam.dev/publishVersion` annotation is set so explicit pins remain hard.
valuesFrom?: [...{
// +usage=Source kind. Only Secret and ConfigMap are supported; OCIRepository is reserved for a future release.
kind: "Secret" | "ConfigMap"
// +usage=Name of the Secret or ConfigMap.
name: string
// +usage=Namespace of the Secret or ConfigMap. Defaults to the chart release namespace, which itself defaults to the Application's own namespace when release.namespace is unset. Cross-namespace references are rejected to prevent cross-tenant reads via the controller's cluster-wide RBAC.
namespace?: string
// +usage=Key inside .data whose value is parsed as YAML. Defaults to "values.yaml" (FluxCD / Helm convention). Only ConfigMap.data and Secret.data are read; ConfigMap.binaryData is rejected.
key?: string
// +usage=If true, a missing Secret/ConfigMap or missing key is skipped silently. Parse errors and permission errors still fail the render.
optional?: bool | *false
}]
// Health status criteria - defines when the Helm deployment is considered healthy
healthStatus?: [...{
// Resource to check
resource: {
// Resource kind (e.g., "Deployment", "StatefulSet", "Job", "Service")
kind: string
// Optional: specific resource name (if not specified, checks first of kind)
name?: string
}
// Health condition to verify.
// The type must match an actual Kubernetes .status.conditions[].type value.
// Common condition types by resource kind:
// Deployment: "Available", "Progressing"
// StatefulSet: "Available"
// Pod: "Ready", "ContainersReady", "Initialized", "PodScheduled"
// Job: "Complete", "Failed"
// Node: "Ready"
// Note: resources without .status.conditions (e.g., Service, ConfigMap)
// will always evaluate to unhealthy — do not use them as health criteria.
condition: {
type: string
// Expected status (default: "True", use "False" for conditions like Progressing)
status?: "True" | "False"
}
}]
// Rendering options
options?: {
includeCRDs?: bool | *true // Install CRDs from chart
skipTests?: bool | *true // Skip test resources
skipHooks?: bool | *false // Skip hook resources
createNamespace?: bool | *true // Create namespace if it doesn't exist
timeout?: string | *"5m" // Rendering timeout
maxHistory?: int | *10 // Revisions to keep
atomic?: bool | *false // Rollback on failure
wait?: bool | *false // Wait for resources
waitTimeout?: string | *"10m" // Wait timeout
force?: bool | *false // Force resource updates
recreatePods?: bool | *false // Recreate pods on upgrade
cleanupOnFail?: bool | *false // Cleanup on failure
// Cache configuration
cache?: {
// Cache key prefix (defaults to "{context.appName}-{context.name}")
// Examples: "shared", "dev-cluster", "prod-env"
key?: string
// TTL for this specific chart (overrides automatic detection)
// Examples: "24h", "5m", "30s", "0" (disable cache)
ttl?: string
// Or specify different TTLs for immutable vs mutable versions
immutableTTL?: string | *"24h" // TTL for semantic versions (1.2.3, v2.0.0)
mutableTTL?: string | *"5m" // TTL for mutable tags (latest, dev, main)
}
// Post-rendering - Future enhancement
// Planned: CUE-based post-rendering for resource transformation
// Would allow users to write CUE templates to modify rendered resources
// with full access to KubeVela context (appName, namespace, etc.)
// Requires CUE-in-CUE runtime execution capability
// postRender?: {
// template: string // CUE template for transforming resources
// }
}
}
// Set default release configuration
_release: {
if parameter.release != _|_ {
parameter.release
}
if parameter.release == _|_ {
name: context.name
namespace: context.namespace
}
}
// Set default options with cache key
_options: {
if parameter.options != _|_ {
parameter.options
}
if parameter.options == _|_ {
cache: {
key: "\(context.appName)-\(context.name)"
}
}
if parameter.options != _|_ && parameter.options.cache != _|_ && parameter.options.cache.key == _|_ {
cache: {
parameter.options.cache
key: "\(context.appName)-\(context.name)"
}
}
}
// Capture KubeVela runtime context BEFORE the $params block to avoid
// CUE scoping collision: naming a field "context" inside $params would
// cause inner "context.xxx" references to resolve to the field itself
// (self-reference) instead of KubeVela's runtime context object.
_velaContext: {
appName: context.appName
appNamespace: context.namespace
name: context.name
namespace: context.namespace
}
// Render the Helm chart using the provider
_rendered: helm.#Render & {
$params: {
chart: parameter.chart
release: _release
if parameter.values != _|_ {
values: parameter.values
}
if parameter.valuesFrom != _|_ {
valuesFrom: parameter.valuesFrom
}
options: _options
// Pass KubeVela ownership context so the provider can inject labels
"context": _velaContext
}
}
// Primary output: an audit ConfigMap that records release metadata.
// This gives KubeVela a stable, predictable primary resource regardless
// of what the Helm chart contains (chart rendering order is not guaranteed).
output: {
apiVersion: "v1"
kind: "ConfigMap"
metadata: {
name: "\(_release.name)-helm-release"
namespace: _release.namespace
labels: {
"app.oam.dev/name": context.appName
"app.oam.dev/namespace": context.namespace
"app.oam.dev/component": context.name
"helm.oam.dev/chart": parameter.chart.source
}
}
data: {
chartSource: parameter.chart.source
releaseName: _release.name
releaseNamespace: _release.namespace
if parameter.chart.repoURL != _|_ {
repoURL: parameter.chart.repoURL
}
if parameter.chart.version != _|_ {
chartVersion: parameter.chart.version
}
resourceCount: "\(len(_rendered.$returns.resources))"
}
}
// All rendered Helm resources go into outputs with stable keys
if _rendered.$returns.resources != _|_ {
if len(_rendered.$returns.resources) > 0 {
outputs: {
for i, res in _rendered.$returns.resources {
"helm-resource-\(i)": res
}
}
}
}
}