transform

package
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 16, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package transform - ast.go implements Go template AST analysis for fast mode. Instead of sentinel rendering (O(N+1) renders), it parses template files once to find .Values.* references and matches rendered field values against known Helm values to produce FieldMappings in a single render pass.

Package transform - engine.go orchestrates the full transformation pipeline that converts parsed Kubernetes resources and Helm values into a KRO ResourceGraphDefinition.

Package transform - fieldpath.go provides utilities for navigating and mutating nested map[string]interface{} structures using dot-separated field paths with optional array index notation (e.g., "spec.containers[0].image").

Package transform implements the core transformation pipeline that converts parsed Kubernetes resources and Helm values into KRO-compatible artifacts.

Package transform - jsonschema.go provides utilities to extract type information from a Helm chart's values.schema.json, enriching the schema beyond what Go runtime types can infer from values.yaml alone.

Package transform - params.go defines parameter mapping types and CEL expression generation for sentinel-based parameter detection.

Package transform - readyconditions.go provides loading and merging of user-supplied custom readiness conditions from a YAML file. The file format maps Kubernetes resource kinds to readiness expressions:

Deployment:
  - "${self.status.availableReplicas == self.spec.replicas}"
Service:
  - "${self.spec.clusterIP != \"\"}"
Job:
  - "${self.status.succeeded > 0}"

Package transform - schema.go implements schema extraction from Helm values.

Package transform - sentinel.go implements sentinel value injection and diffing for parameter detection. It replaces Helm values with unique markers, re-renders, and diffs resources to determine which fields are controlled by which values.

Index

Constants

View Source
const SentinelPrefix = "__CHART2KRO_SENTINEL_"

SentinelPrefix is the prefix used for string sentinel values.

View Source
const SentinelSuffix = "__"

SentinelSuffix is the suffix used for string sentinel values.

Variables

This section is empty.

Functions

func AnalyzeTemplates

func AnalyzeTemplates(templateFiles map[string]string) (map[string]bool, error)

AnalyzeTemplates parses Go template files and extracts all .Values.* paths referenced in template actions. This returns the set of referenced value paths without requiring sentinel rendering.

templateFiles is a map of template filename to template content (e.g., from chart.Chart.Templates). Only files with .yaml/.yml/.tpl extensions are parsed.

func ApplyFieldMappings

func ApplyFieldMappings(
	resources []*k8s.Resource,
	resourceIDs map[*k8s.Resource]string,
	mappings []FieldMapping,
)

ApplyFieldMappings applies field mappings to resource templates, replacing hardcoded values with CEL expressions (e.g., ${schema.spec.replicaCount}). For substring matches (string interpolation), it uses the full sentinel-rendered value to produce a composite CEL expression.

func ApplySchemaOverrides

func ApplySchemaOverrides(fields []*SchemaField, overrides map[string]SchemaOverride)

ApplySchemaOverrides mutates the extracted schema fields in-place, replacing inferred types and defaults with user-specified overrides.

func AssignResourceIDs

func AssignResourceIDs(resources []*k8s.Resource, overrides map[string]string) (map[*k8s.Resource]string, error)

AssignResourceIDs assigns stable, human-readable IDs to each resource. When multiple resources share the same kind, a disambiguating name segment is appended (e.g., "service-main", "service-headless"). The overrides map allows manual ID assignment by qualified name ("Kind/name"). Returns an error if two resources would receive the same ID after sanitization.

func BatchSentinelizeIndependent

func BatchSentinelizeIndependent(values map[string]interface{}) []map[string]interface{}

BatchSentinelizeIndependent analyses the values tree and groups structurally independent leaf values that can be sentinelized together in a single rendering pass.

Values are independent when they reside under different top-level keys. This is a conservative heuristic that avoids sentinel collision while allowing batching of unrelated subtrees.

This utility is useful as a fallback strategy when full sentinelization via SentinelizeAll causes template rendering failures. By batching into smaller groups, each render pass handles fewer sentinel values, reducing the chance of template type-mismatch errors.

The current pipeline uses SentinelizeAll for single-pass rendering, which is optimal for most charts. Use this function when implementing custom multi-pass sentinel strategies for very large or complex charts.

func BuildCELExpression

func BuildCELExpression(mapping FieldMapping, sentinelRendered string) string

BuildCELExpression generates a CEL expression for a field mapping. For exact matches: ${schema.spec.<path>} For substring matches: "${schema.spec.<path1>}...<literal>...${schema.spec.<path2>}"

func BuildInterpolatedCELFromSentinel

func BuildInterpolatedCELFromSentinel(sentinelRendered string) string

BuildInterpolatedCELFromSentinel parses a sentinel-rendered string with one or more sentinels embedded, and produces a CEL interpolation expression.

func BuildSimpleSchema

func BuildSimpleSchema(fields []*SchemaField) map[string]interface{}

BuildSimpleSchema converts a list of SchemaFields into a map[string]interface{} suitable for YAML serialization, using KRO SimpleSchema syntax.

func CompoundIncludeWhen

func CompoundIncludeWhen(conditions []IncludeCondition) string

CompoundIncludeWhen generates a compound CEL expression from multiple conditions. Conditions are joined with "&&" and enclosed in a single ${...} expression.

Examples:

[{Path: "a"}]               → "${schema.spec.a}"
[{Path: "a"}, {Path: "b"}] → "${schema.spec.a && schema.spec.b}"
[{Path: "a", Operator: "!=", Value: "\"\""}] → "${schema.spec.a != \"\"}"

func ExtractSentinelsFromString

func ExtractSentinelsFromString(s string) []string

ExtractSentinelsFromString extracts all sentinel paths from a string that may contain multiple interpolated sentinels.

func IncludeWhenExpression

func IncludeWhenExpression(path ...string) string

IncludeWhenExpression generates a conditional inclusion expression. e.g., IncludeWhenExpression("spec", "monitoring", "enabled") => "${schema.spec.monitoring.enabled}".

func Interpolate

func Interpolate(parts ...string) string

Interpolate wraps multiple expressions in a string interpolation. e.g., Interpolate("${schema.spec.image}", ":", "${schema.spec.tag}") => "${schema.spec.image}:${schema.spec.tag}".

func LoadCustomReadyConditions

func LoadCustomReadyConditions(path string) (map[string][]string, error)

LoadCustomReadyConditions reads a YAML file containing custom readiness conditions keyed by Kind name. Returns a map of Kind → raw CEL condition strings. The caller can use ResolveReadyWhen to apply these overrides.

func MapToSimpleSchemaType

func MapToSimpleSchemaType(jsonSchemaType string) string

MapToSimpleSchemaType converts a JSON Schema type string to a KRO SimpleSchema type string. JSON Schema "integer" maps to "integer", "number" to "number", "boolean" to "boolean", and everything else (including "string") to "string".

func ResolveReadyWhen

func ResolveReadyWhen(gvk schema.GroupVersionKind, custom map[string][]string) []string

ResolveReadyWhen returns readiness conditions for a resource GVK. Custom conditions take precedence over defaults when the Kind matches.

func ResourceRef

func ResourceRef(resourceID string, path ...string) string

ResourceRef generates a CEL expression referencing another resource's field. e.g., ResourceRef("deployment", "status", "availableReplicas") => "${deployment.status.availableReplicas}".

func ResourceRefWithOptional

func ResourceRefWithOptional(resourceID string, segments ...PathSegment) string

ResourceRefWithOptional generates a CEL expression using optional "?" accessors for potentially absent status fields. e.g., ResourceRefWithOptional("svc", PathSegment{"status", false}, PathSegment{"loadBalancer", false},

PathSegment{"ingress[0]", true}, PathSegment{"hostname", true})

=> "${svc.status.loadBalancer.?ingress[0].?hostname}"

func SchemaRef

func SchemaRef(path ...string) string

SchemaRef generates a CEL expression referencing a schema field. e.g., SchemaRef("spec", "replicas") => "${schema.spec.replicas}". Returns an error-indicative string if path is empty.

func SelfRef

func SelfRef(path ...string) string

SelfRef generates a CEL expression referencing the resource's own field. e.g., SelfRef("status", "availableReplicas") => "${self.status.availableReplicas}".

func SentinelForString

func SentinelForString(path string) string

SentinelForString returns the sentinel string for a given path.

func SentinelizeAll

func SentinelizeAll(values map[string]interface{}) map[string]interface{}

SentinelizeAll creates a deep copy of values with ALL leaf values replaced by their sentinel equivalents. This enables detecting multi-value string interpolation patterns (e.g., "${image.repo}:${image.tag}") in a single render.

func ToCamelCase

func ToCamelCase(s string) string

ToCamelCase converts a dot-separated or hyphenated path to camelCase.

func ToPascalCase

func ToPascalCase(s string) string

ToPascalCase converts a string to PascalCase.

func ValidateExpression added in v0.0.2

func ValidateExpression(expr string) error

ValidateExpression checks that a KRO CEL expression string has balanced ${...} delimiters and is non-empty. It does NOT compile or type-check the inner CEL — that is KRO's responsibility at apply time.

Types

type CycleError

type CycleError struct {
	Cycles [][]string
}

CycleError is returned when the dependency graph contains cycles.

func (*CycleError) Error

func (e *CycleError) Error() string

type DependencyGraph

type DependencyGraph struct {
	// contains filtered or unexported fields
}

DependencyGraph represents the DAG of resource dependencies.

func BuildDependencyGraph

func BuildDependencyGraph(resources map[*k8s.Resource]string) *DependencyGraph

BuildDependencyGraph analyzes resources and constructs a dependency graph. It detects label selector matches, name references, SA references, and volume refs.

func NewDependencyGraph

func NewDependencyGraph() *DependencyGraph

NewDependencyGraph creates an empty dependency graph.

func (*DependencyGraph) AddEdge

func (g *DependencyGraph) AddEdge(source, target string)

AddEdge adds a dependency: source depends on target. Both source and target must be registered nodes.

func (*DependencyGraph) AddNode

func (g *DependencyGraph) AddNode(id string, r *k8s.Resource)

AddNode adds a resource to the graph.

func (*DependencyGraph) DependenciesOf

func (g *DependencyGraph) DependenciesOf(id string) []string

DependenciesOf returns the IDs that the given node depends on.

func (*DependencyGraph) DetectCycles

func (g *DependencyGraph) DetectCycles() [][]string

DetectCycles returns all unique cycles in the graph using DFS. Cycles are deduplicated by normalizing: each cycle is rotated so that its lexicographically smallest node comes first, then stored as a canonical string key to avoid reporting the same cycle from different starting points.

func (*DependencyGraph) EdgeCount added in v0.0.2

func (g *DependencyGraph) EdgeCount() int

EdgeCount returns the total number of dependency edges in the graph.

func (*DependencyGraph) Nodes

func (g *DependencyGraph) Nodes() []string

Nodes returns all node IDs.

func (*DependencyGraph) Resource

func (g *DependencyGraph) Resource(id string) *k8s.Resource

Resource returns the resource for the given ID.

func (*DependencyGraph) TopologicalSort

func (g *DependencyGraph) TopologicalSort() ([]string, error)

TopologicalSort returns the nodes in topological order using Kahn's algorithm. Ties are broken alphabetically for deterministic output. Returns an error if the graph contains a cycle.

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine orchestrates the full transformation pipeline.

func NewEngine

func NewEngine(config EngineConfig) *Engine

NewEngine creates a new transformation engine.

func (*Engine) Transform

func (e *Engine) Transform(
	ctx context.Context,
	resources []*k8s.Resource,
	values map[string]interface{},
) (*Result, error)

Transform runs the full transformation pipeline: 1. Assign resource IDs 2. Apply field mappings to resource templates (CEL expression injection) 3. Extract schema from values (with optional pruning) 4. Build dependency graph 5. Generate status projections via transformer registry

type EngineConfig

type EngineConfig struct {
	// IncludeAllValues includes all values in the schema, even unreferenced ones.
	IncludeAllValues bool

	// FlatSchema uses flat camelCase field names instead of nested objects.
	FlatSchema bool

	// ResourceIDOverrides allows manual ID assignment by qualified name.
	ResourceIDOverrides map[string]string

	// FieldMappings are pre-computed sentinel-based parameter mappings.
	// When non-nil, the engine applies these to resource templates,
	// replacing hardcoded values with CEL expressions.
	FieldMappings []FieldMapping

	// ReferencedPaths is the set of Helm value paths detected as referenced.
	// When non-nil and IncludeAllValues is false, only these paths are
	// included in the schema.
	ReferencedPaths map[string]bool

	// JSONSchemaBytes is the raw values.schema.json from the chart.
	// When non-nil, enriches schema type inference with explicit JSON Schema types.
	JSONSchemaBytes []byte

	// SchemaOverrides override inferred schema field types and defaults.
	// Keys are dotted Helm value paths (e.g., "replicaCount", "image.tag").
	SchemaOverrides map[string]SchemaOverride

	// TransformerRegistry is an optional pluggable transformer registry.
	// When non-nil, the engine dispatches per-resource transformation
	// through the registry to produce readiness conditions and status
	// projections. When nil, DefaultStatusProjections is used.
	TransformerRegistry TransformerRegistry
}

EngineConfig configures the transformation engine.

type FieldMapping

type FieldMapping struct {
	// ValuesPath is the dot-separated Helm values path (e.g., "image.tag").
	ValuesPath string

	// ResourceID is the ID of the affected resource.
	ResourceID string

	// FieldPath is the dot-separated field path within the resource
	// (e.g., "spec.template.spec.containers[0].image").
	FieldPath string

	// MatchType indicates how the sentinel was found.
	MatchType MatchType

	// SentinelRendered holds the sentinel-rendered field value for substring matches.
	// Used by BuildCELExpression to produce interpolated CEL expressions.
	SentinelRendered string
}

FieldMapping represents a mapping from a Helm values path to affected resource fields.

func DiffAllResources

func DiffAllResources(
	baseline []*k8s.Resource,
	sentinelRendered []*k8s.Resource,
	resourceIDs map[*k8s.Resource]string,
) []FieldMapping

DiffAllResources compares baseline resources against full-sentinel-rendered resources to detect all field mappings in a single pass. Resources are matched by GVK+name identity rather than positional index, making the diff robust against structural changes caused by sentinel values (e.g., conditional blocks).

func MatchFieldsByValue

func MatchFieldsByValue(
	resources []*k8s.Resource,
	resourceIDs map[*k8s.Resource]string,
	values map[string]interface{},
	referencedPaths map[string]bool,
) []FieldMapping

MatchFieldsByValue walks rendered resources and matches field values against known Helm values to produce FieldMappings without sentinel rendering. This is the field-mapping phase for fast mode.

It flattens Helm values to a map of path→value, then walks each resource's fields. If a field value matches a known value exactly, a FieldMapping with MatchExact is created. If a field value contains known string values as substrings, a FieldMapping with MatchSubstring is created.

Only paths present in referencedPaths (from AST analysis) are considered.

func ParallelDiffAllResources

func ParallelDiffAllResources(
	baseline []*k8s.Resource,
	sentinelRendered []*k8s.Resource,
	resourceIDs map[*k8s.Resource]string,
	cfg ParallelDiffConfig,
) []FieldMapping

ParallelDiffAllResources is the concurrent version of DiffAllResources. It distributes resource diffing across a bounded worker pool and collects results without data races.

type IncludeCondition

type IncludeCondition struct {
	// Path is the dot-separated Helm values path (e.g., "monitoring.enabled").
	Path string
	// Operator is the comparison operator (defaults to implicit truthiness if empty).
	Operator string
	// Value is the comparison value (e.g., "\"\""). Empty for truthiness checks.
	Value string
}

IncludeCondition represents a single condition for compound includeWhen.

type JSONSchemaInfo

type JSONSchemaInfo struct {
	// Type is the JSON Schema type (string, integer, number, boolean, array, object).
	Type string
	// Format is an optional format hint (e.g., "int64", "email", "uri").
	Format string
	// Description is the human-readable description from the schema.
	Description string
	// Enum lists allowed values, if specified.
	Enum []interface{}
	// Minimum is the minimum numeric value, if specified.
	Minimum *float64
	// Maximum is the maximum numeric value, if specified.
	Maximum *float64
}

JSONSchemaInfo holds type metadata extracted from a JSON Schema property.

type JSONSchemaResolver

type JSONSchemaResolver struct {
	// contains filtered or unexported fields
}

JSONSchemaResolver resolves type information from a parsed values.schema.json. The resolver walks the JSON Schema tree to find type, format, enum, and description metadata for a given dot-separated Helm values path.

func NewJSONSchemaResolver

func NewJSONSchemaResolver(schemaBytes []byte) (*JSONSchemaResolver, error)

NewJSONSchemaResolver parses raw values.schema.json bytes and returns a resolver. Returns nil (no error) when schemaBytes is empty, allowing callers to skip JSON Schema enrichment without branching.

func (*JSONSchemaResolver) Resolve

func (r *JSONSchemaResolver) Resolve(path string) *JSONSchemaInfo

Resolve looks up a dot-separated Helm values path in the JSON Schema and returns the type info. Returns nil when the path is not found.

type MatchType

type MatchType int

MatchType indicates how a sentinel value was matched.

const (
	// MatchExact means the entire field value was the sentinel.
	MatchExact MatchType = iota

	// MatchSubstring means the sentinel appeared as part of the field value.
	// This indicates string interpolation.
	MatchSubstring
)

func (MatchType) String

func (m MatchType) String() string

String returns the string representation of a MatchType.

type ParallelDiffConfig

type ParallelDiffConfig struct {
	// Workers is the maximum number of goroutines.
	// Defaults to GOMAXPROCS if zero.
	Workers int
}

ParallelDiffConfig controls parallel sentinel diff behaviour.

type PathSegment

type PathSegment struct {
	// Name is the field name (e.g., "status", "loadBalancer", "ingress[0]").
	Name string
	// Optional marks this segment with a "?" accessor for potentially absent fields.
	Optional bool
}

PathSegment represents a segment in a resource reference path, with an optional flag indicating the field may be absent.

type ReadyWhenCondition

type ReadyWhenCondition struct {
	// Key is the status field path to check.
	Key string
	// Operator is the comparison operator (==, !=, >, <, etc.).
	Operator string
	// Value is the expected value (can be another field reference or literal).
	Value string
}

ReadyWhenCondition represents a readiness condition for a KRO resource.

func DefaultReadyWhen

func DefaultReadyWhen(gvk schema.GroupVersionKind) []ReadyWhenCondition

DefaultReadyWhen returns default readiness conditions for a given GVK.

func (ReadyWhenCondition) String

func (c ReadyWhenCondition) String() string

String returns the CEL expression for a readiness condition.

type Result

type Result struct {
	// Resources is the list of parsed Kubernetes resources.
	Resources []*k8s.Resource

	// ResourceIDs maps each resource to its assigned ID.
	ResourceIDs map[*k8s.Resource]string

	// SchemaFields are the extracted schema fields.
	SchemaFields []*SchemaField

	// StatusFields are the generated status projections.
	StatusFields []StatusField

	// DependencyGraph is the constructed dependency graph.
	DependencyGraph *DependencyGraph

	// FieldMappings are the detected parameter mappings.
	FieldMappings []FieldMapping
}

Result holds all artifacts produced by the transformation pipeline.

type SchemaExtractor

type SchemaExtractor struct {
	// IncludeAll includes all values even if not referenced in templates.
	IncludeAll bool

	// FlatMode uses flat camelCase field names instead of nested objects.
	FlatMode bool

	// JSONSchema is an optional resolver for values.schema.json type info.
	// When non-nil, it enriches inferred types with explicit JSON Schema types.
	JSONSchema *JSONSchemaResolver
}

SchemaExtractor extracts KRO SimpleSchema from Helm values.

func NewSchemaExtractor

func NewSchemaExtractor(includeAll, flatMode bool, jsonSchema *JSONSchemaResolver) *SchemaExtractor

NewSchemaExtractor creates a SchemaExtractor with the given options.

func (*SchemaExtractor) Extract

func (e *SchemaExtractor) Extract(values map[string]interface{}, referencedPaths map[string]bool) []*SchemaField

Extract walks the values tree and produces a list of SchemaFields. referencedPaths is the set of Helm value paths actually used in templates. If nil or IncludeAll is set, all values are included.

type SchemaField

type SchemaField struct {
	// Name is the field name (camelCase).
	Name string

	// Path is the dot-separated Helm values path (e.g., "image.repository").
	Path string

	// Type is the KRO SimpleSchema type (string, integer, number, boolean).
	Type string

	// Default is the default value as a string (e.g., "\"nginx\"", "3", "true").
	Default string

	// Children holds nested fields for object types.
	Children []*SchemaField
}

SchemaField represents a single field in the KRO SimpleSchema.

func (*SchemaField) IsObject

func (f *SchemaField) IsObject() bool

IsObject returns true if this field has children (nested object).

func (*SchemaField) SimpleSchemaString

func (f *SchemaField) SimpleSchemaString() string

SimpleSchemaString returns the KRO SimpleSchema representation. e.g., "string | default=\"nginx\"" or "integer | default=3".

type SchemaOverride

type SchemaOverride struct {
	// Type overrides the inferred type (string, integer, number, boolean).
	Type string

	// Default overrides the default value.
	Default string
}

SchemaOverride allows overriding the inferred type and default of a schema field.

type StatusField

type StatusField struct {
	// Name is the name of the field in the RGD status.
	Name string
	// CELExpression is the CEL expression to extract the value.
	CELExpression string
}

StatusField represents a field to project from a resource's status.

func DefaultStatusProjections

func DefaultStatusProjections(gvk schema.GroupVersionKind, resourceID string) []StatusField

DefaultStatusProjections returns default status fields to project for a given GVK. Fields that may be absent on newly created resources use optional "?" accessors.

type TransformerOutput

type TransformerOutput struct {
	// ReadyWhen are the readiness conditions (raw CEL-style strings).
	ReadyWhen []string

	// StatusFields are the status projections for this resource.
	StatusFields []StatusField

	// IncludeWhen is an optional conditional inclusion CEL expression.
	IncludeWhen string
}

TransformerOutput holds the results from a transformer's per-resource transformation. This is the engine-facing type that decouples the engine from the transformer package.

type TransformerRegistry

type TransformerRegistry interface {
	// TransformResource applies the matching transformer to a resource.
	TransformResource(
		ctx context.Context,
		resource *k8s.Resource,
		resourceID string,
		fieldMappings []FieldMapping,
		values map[string]interface{},
	) (*TransformerOutput, error)
}

TransformerRegistry is the interface the engine uses to dispatch per-resource transformation. The concrete implementation lives in internal/transform/transformer to avoid circular imports.

Directories

Path Synopsis
Package transformer defines the Transformer interface and Registry for per-resource-kind transformation logic.
Package transformer defines the Transformer interface and Registry for per-resource-kind transformation logic.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL