patch

package
v0.1.0-beta.1 Latest Latest
Warning

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

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

README

Patch - Declarative Resource Patching

The patch package provides a JSONPath-based system for declaratively modifying Kubernetes resources. It supports both TOML and YAML patch file formats with structure-preserving modifications and variable substitution.

Overview

Patches allow you to modify Kubernetes manifests without rewriting them. The system uses JSONPath expressions to target specific fields and applies changes while preserving the original YAML structure (comments, ordering, formatting).

Patch File Formats

TOML Format (.kpatch)
# Target a specific resource by kind and name
[deployment.myapp.spec]
replicas = 3

[deployment.myapp.spec.template.spec.containers.0]
image = "${app.image}:${app.tag}"
resources.requests.cpu = "200m"
resources.requests.memory = "256Mi"

# Append to a list with .-
[deployment.myapp.spec.template.spec.containers.-]
name = "sidecar"
image = "envoy:latest"
YAML Format
target:
  kind: Deployment
  name: myapp
patches:
  - path: spec.replicas
    value: 3
  - path: spec.template.spec.containers[0].image
    value: "nginx:latest"

Quick Start

import "github.com/go-kure/kure/pkg/patch"

// Load patches from file
file, _ := os.Open("patches/customize.kpatch")
specs, err := patch.LoadPatchFile(file)

// Create patchable set with resources and patches
patchSet, err := patch.NewPatchableAppSet(resources, specs)

// Resolve targets and apply
resolved, err := patchSet.Resolve()
for _, r := range resolved {
    err := r.Apply()
}

// Write patched output
err = patchSet.WriteToFile("output.yaml")

Key Features

Variable Substitution

Patches support variable references that resolve against a parameter context:

[deployment.myapp.spec.template.spec.containers.0]
image = "${registry}/${image}:${tag}"
replicas = "${replicas}"
varCtx := &patch.VariableContext{
    Variables: map[string]interface{}{
        "registry": "docker.io",
        "image":    "myapp",
        "tag":      "v1.0.0",
        "replicas": 3,
    },
}
specs, err := patch.LoadPatchFileWithVariables(file, varCtx)
List Selectors

Target specific items in lists using selectors:

# By index
[deployment.myapp.spec.template.spec.containers.0]
image = "updated:latest"

# Append to list
[deployment.myapp.spec.template.spec.containers.-]
name = "new-container"

# By field value (name selector)
[deployment.myapp.spec.template.spec.containers.{name=myapp}]
image = "updated:latest"
Structure Preservation

When using NewPatchableAppSetWithStructure, the original YAML document structure is preserved through patching, maintaining comments, key ordering, and formatting.

Strategic Merge Patch

For broad document-level changes, strategic merge patch (SMP) deep-merges a partial YAML document into the target resource. Known Kubernetes kinds merge lists by key (e.g. containers by name); unknown kinds fall back to JSON merge patch (RFC 7386).

- target: deployment.my-app
  type: strategic
  patch:
    spec:
      template:
        spec:
          containers:
          - name: main
            resources:
              limits:
                cpu: "500m"
          - name: sidecar
            image: envoy:v1.28
// Enable kind-aware merging
lookup, _ := patch.DefaultKindLookup()
patchSet.KindLookup = lookup

// Detect conflicts before applying
resolved, reports, err := patchSet.ResolveWithConflictCheck()

SMP patches are applied before field-level patches. See DESIGN.md for full specification.

API Reference

Loading Patches
Function Description
LoadPatchFile(r) Load with automatic format detection
LoadPatchFileWithVariables(r, ctx) Load with variable substitution
LoadTOMLPatchFile(r, ctx) Load TOML-format patches
LoadYAMLPatchFile(r, ctx) Load YAML-format patches
Applying Patches
Function Description
NewPatchableAppSet(resources, patches) Create patchable set
NewPatchableAppSetWithStructure(docSet, patches) Create with structure preservation
ParsePatchLine(path, value) Parse a single patch operation
Strategic Merge Patch
Function Description
ApplyStrategicMergePatch(resource, patch, lookup) Apply SMP to a single resource
DefaultKindLookup() Create a KindLookup from the built-in scheme
DetectSMPConflicts(patches, lookup, gvk) Check pairwise conflicts among patches
ResolveWithConflictCheck() Resolve patches with conflict detection
  • launcher - Uses patches in kurel package system
  • io - YAML parsing for patch targets

Documentation

Overview

Package patch provides declarative patching of Kubernetes resources using a simple, structured syntax without templates or overlays.

This package enables tools to modify Kubernetes manifests through patches that target specific fields and list items using dot-notation paths with smart selectors.

Quick Start

Load resources and apply patches:

// Load base Kubernetes resources
resources, err := patch.LoadResourcesFromMultiYAML(resourceFile)
if err != nil {
	return err
}

// Load patch specifications
patches, err := patch.LoadPatchFile(patchFile)
if err != nil {
	return err
}

// Create patchable set and apply
set, err := patch.NewPatchableAppSet(resources, patches)
if err != nil {
	return err
}

resolved, err := set.Resolve()
if err != nil {
	return err
}

for _, r := range resolved {
	if err := r.Apply(); err != nil {
		return err
	}
}

Patch Syntax

Both YAML and TOML patch formats are supported with automatic detection:

# YAML format
spec.replicas: 3
spec.containers[name=main].image: nginx:latest
spec.ports[+name=https]: {name: https, port: 443}

# TOML format
[deployment.app]
spec.replicas: 3

[deployment.app.containers.name=main]
image: nginx:latest
resources.requests.cpu: 100m

Strategic Merge Patch

In addition to field-level patches, this package supports Kubernetes strategic merge patch (SMP) semantics. SMP patches are partial YAML documents that are deep-merged into target resources. For known Kubernetes kinds, list items are merged by key (e.g. containers by name). For unknown kinds (CRDs), the package falls back to RFC 7386 JSON merge patch.

SMP patches are specified in YAML with type: strategic:

patches:
  - target: deployment.my-app
    type: strategic
    patch:
      spec:
        template:
          spec:
            containers:
              - name: main
                resources:
                  limits:
                    cpu: "500m"

SMP patches are applied before field-level patches (SMP sets the broad document shape; field patches make precise tweaks on top).

Use PatchableAppSet.ResolveWithConflictCheck to detect conflicting SMP patches targeting the same resource before applying them.

Core Types

PatchableAppSet  - Manages resources and their patches
PatchOp          - Individual field-level patch operation
StrategicPatch   - Strategic merge patch document
KindLookup       - GVK-to-struct resolution for SMP
ConflictReport   - SMP conflict detection results
PathPart         - Structured path component
TOMLHeader       - Parsed TOML section header

Detailed Documentation

For comprehensive information, see the markdown documentation:

  • DESIGN.md - Complete syntax reference and examples
  • PATCH_ENGINE_DESIGN.md - Architecture and implementation details
  • PATH_RESOLUTION.md - Advanced path resolution and type inference
  • ERROR_HANDLING.md - Error handling patterns and debugging

Debugging

Enable detailed logging:

export KURE_DEBUG=1

Index

Constants

This section is empty.

Variables

View Source
var Debug = os.Getenv("KURE_DEBUG") == "1"

Functions

func ApplyPatch

func ApplyPatch(basePath, patchPath string) ([]*unstructured.Unstructured, error)

ApplyPatch loads resources and patch instructions from the provided file paths and returns the patched resources.

func ApplyStrategicMergePatch

func ApplyStrategicMergePatch(
	resource *unstructured.Unstructured,
	patch map[string]interface{},
	lookup KindLookup,
) error

ApplyStrategicMergePatch applies a strategic merge patch to a resource. For known Kubernetes kinds (registered in the lookup scheme), it uses StrategicMergeMapPatch with typed struct tags to enable list-merge-by-key semantics (e.g. containers merged by name). For unknown kinds (CRDs), it falls back to RFC 7386 JSON merge patch. If lookup is nil, fallback is always used.

func CanonicalResourceKey

func CanonicalResourceKey(r *unstructured.Unstructured) string

CanonicalResourceKey returns the unique key for a resource. For namespaced resources: "namespace/kind.name" For cluster-scoped resources: "kind.name"

func GenerateOutputFilename

func GenerateOutputFilename(originalPath, patchPath, outputDir string) string

GenerateOutputFilename creates the output filename based on the pattern <outputDir>/<originalname>-patch-<patchname>.yaml

func InferPatchOp

func InferPatchOp(path string) string

InferPatchOp infers a patch operation based on the path syntax.

func IsTOMLFormat

func IsTOMLFormat(content string) bool

IsTOMLFormat detects if the content appears to be TOML-style patch format

func LoadResourcesFromMultiYAML

func LoadResourcesFromMultiYAML(r io.Reader) ([]*unstructured.Unstructured, error)

func ResolveTargetKey

func ResolveTargetKey(resources []*unstructured.Unstructured, target string) (string, error)

ResolveTargetKey resolves a patch target to its canonical resource key. Accepts short names ("my-app"), kind-qualified names ("deployment.my-app"), and namespace-qualified names ("staging/deployment.my-app"). Returns an error if the target matches no resource or is ambiguous.

func SubstituteVariables

func SubstituteVariables(value string, ctx *VariableContext) (interface{}, error)

SubstituteVariables replaces ${values.key} and ${features.flag} patterns with actual values

Types

type ConflictReport

type ConflictReport struct {
	ResourceName string
	ResourceKind string
	Conflicts    []PatchConflict
}

ConflictReport describes conflicts detected among strategic merge patches targeting the same resource.

func DetectSMPConflicts

func DetectSMPConflicts(
	patches []map[string]interface{},
	lookup KindLookup,
	gvk schema.GroupVersionKind,
) (*ConflictReport, error)

DetectSMPConflicts checks pairwise conflicts among strategic merge patches targeting the same resource. For known kinds, it uses MergingMapsHaveConflicts with PatchMetaFromStruct. For unknown kinds, it performs a simple key-overlap check.

func (*ConflictReport) HasConflicts

func (r *ConflictReport) HasConflicts() bool

HasConflicts returns true if any conflicts were detected.

type KindLookup

type KindLookup interface {
	LookupKind(gvk schema.GroupVersionKind) (runtime.Object, bool)
}

KindLookup resolves a GroupVersionKind to a typed Go struct, enabling strategic merge patch to read struct tags for merge strategy metadata.

func DefaultKindLookup

func DefaultKindLookup() (KindLookup, error)

DefaultKindLookup returns a KindLookup backed by pkg/kubernetes.Scheme, which has all core Kubernetes and registered CRD types.

type PatchConflict

type PatchConflict struct {
	PatchIndexA int
	PatchIndexB int
	Description string
}

PatchConflict describes a single conflict between two patches.

type PatchOp

type PatchOp struct {
	Op         string      `json:"op"`
	Path       string      `json:"path"`
	ParsedPath []PathPart  `json:"patsedpath,omitempty"`
	Selector   string      `json:"selector,omitempty"`
	Value      interface{} `json:"value"`
}

PatchOp represents a single patch operation to apply to an object.

func ParsePatchLine

func ParsePatchLine(key string, value interface{}) (PatchOp, error)

ParsePatchLine converts a YAML patch line of form "path[selector]" into a PatchOp.

func (*PatchOp) NormalizePath

func (p *PatchOp) NormalizePath() error

NormalizePath parses the Path field and stores the result in ParsedPath.

func (*PatchOp) ValidateAgainst

func (p *PatchOp) ValidateAgainst(obj *unstructured.Unstructured) error

ValidateAgainst checks that the patch operation is valid for the given object.

type PatchSpec

type PatchSpec struct {
	Target    string
	Patch     PatchOp         // field-level patch (zero value when Strategic is set)
	Strategic *StrategicPatch // non-nil for strategic merge patches
}

PatchSpec ties a parsed PatchOp to an optional explicit target. For strategic merge patches, Strategic is non-nil and Patch is zero-value.

func LoadPatchFile

func LoadPatchFile(r io.Reader) ([]PatchSpec, error)

func LoadPatchFileWithVariables

func LoadPatchFileWithVariables(r io.Reader, varCtx *VariableContext) ([]PatchSpec, error)

func LoadTOMLPatchFile

func LoadTOMLPatchFile(r io.Reader, varCtx *VariableContext) ([]PatchSpec, error)

func LoadYAMLPatchFile

func LoadYAMLPatchFile(r io.Reader, varCtx *VariableContext) ([]PatchSpec, error)

type PatchableAppSet

type PatchableAppSet struct {
	Resources   []*unstructured.Unstructured
	DocumentSet *YAMLDocumentSet // Preserves original YAML structure
	KindLookup  KindLookup       // Used for strategic merge patches (may be nil)
	Patches     []struct {
		Target    string
		Patch     PatchOp
		Strategic *StrategicPatch
	}
}

PatchableAppSet represents a collection of resources together with the patches that should be applied to them.

func LoadPatchableAppSet

func LoadPatchableAppSet(resourceReaders []io.Reader, patchReader io.Reader) (*PatchableAppSet, error)

func NewPatchableAppSet

func NewPatchableAppSet(resources []*unstructured.Unstructured, patches []PatchSpec) (*PatchableAppSet, error)

NewPatchableAppSet constructs a PatchableAppSet from already loaded resources and parsed patch specifications.

func NewPatchableAppSetWithStructure

func NewPatchableAppSetWithStructure(documentSet *YAMLDocumentSet, patches []PatchSpec) (*PatchableAppSet, error)

NewPatchableAppSetWithStructure constructs a PatchableAppSet with YAML structure preservation.

func (*PatchableAppSet) Resolve

func (s *PatchableAppSet) Resolve() ([]*ResourceWithPatches, error)

Resolve groups patches by their target resource and returns them as ResourceWithPatches objects.

func (*PatchableAppSet) ResolveWithConflictCheck

func (s *PatchableAppSet) ResolveWithConflictCheck() ([]*ResourceWithPatches, []*ConflictReport, error)

ResolveWithConflictCheck resolves patches and additionally checks for conflicts among strategic merge patches targeting the same resource.

func (*PatchableAppSet) WritePatchedFiles

func (s *PatchableAppSet) WritePatchedFiles(originalPath string, patchFiles []string, outputDir string) error

WritePatchedFiles writes separate files for each patch set applied. Debug output is enabled based on the current value of the Debug flag.

func (*PatchableAppSet) WritePatchedFilesWithOptions

func (s *PatchableAppSet) WritePatchedFilesWithOptions(originalPath string, patchFiles []string, outputDir string, debug bool) error

WritePatchedFilesWithOptions writes separate files for each patch set applied with explicit debug control, avoiding mutation of the global Debug flag.

func (*PatchableAppSet) WriteToFile

func (s *PatchableAppSet) WriteToFile(filename string) error

WriteToFile writes the patched resources to a file while preserving structure

type PathPart

type PathPart struct {
	Field      string
	MatchType  string // "", "index", or "key"
	MatchValue string
}

PathPart represents one segment of a parsed patch path.

func ParsePatchPath

func ParsePatchPath(path string) ([]PathPart, error)

ParsePatchPath parses a patch path with selectors into structured parts.

type RawPatchMap

type RawPatchMap map[string]interface{}

type ResourceWithPatches

type ResourceWithPatches struct {
	Name             string
	Base             *unstructured.Unstructured
	Patches          []PatchOp
	StrategicPatches []StrategicPatch // applied before field-level patches
	KindLookup       KindLookup       // may be nil; used for strategic merge
}

ResourceWithPatches ties a base object with the patches that should be applied to it.

func (*ResourceWithPatches) Apply

func (r *ResourceWithPatches) Apply() error

Apply executes all patches on the base object. Strategic merge patches are applied first (setting broad document shape), then field-level patches make precise tweaks on top.

type SchemeKindLookup

type SchemeKindLookup struct {
	Scheme *runtime.Scheme
}

SchemeKindLookup implements KindLookup using a runtime.Scheme.

func NewSchemeKindLookup

func NewSchemeKindLookup(scheme *runtime.Scheme) *SchemeKindLookup

NewSchemeKindLookup returns a KindLookup backed by the given scheme.

func (*SchemeKindLookup) LookupKind

LookupKind returns a zero-value typed object for the given GVK, or false if the GVK is not registered in the scheme.

type Selector

type Selector struct {
	Type      string // "index", "key-value", "bracketed"
	Index     *int
	Key       string
	Value     string
	Bracketed string
}

Selector represents different types of selectors in TOML headers

type StrategicPatch

type StrategicPatch struct {
	Patch map[string]interface{}
}

StrategicPatch represents a partial YAML document for deep merge using Kubernetes strategic merge patch semantics.

type TOMLHeader

type TOMLHeader struct {
	Kind     string
	Name     string
	Sections []string
	Selector *Selector
}

TOMLHeader represents a parsed TOML-style header like [kind.name.section.selector]

func ParseTOMLHeader

func ParseTOMLHeader(header string) (*TOMLHeader, error)

ParseTOMLHeader parses a TOML-style header into structured components Examples:

[deployment.app] → Kind: deployment, Name: app
[deployment.app.containers.name=main] → Kind: deployment, Name: app, Sections: [containers], Selector: {Key: name, Value: main}
[deployment.app.ports.0] → Kind: deployment, Name: app, Sections: [ports], Selector: {Index: 0}
[deployment.app.containers[image.name=main]] → Kind: deployment, Name: app, Sections: [containers], Selector: {Bracketed: image.name=main}

func (*TOMLHeader) ResolveTOMLPath

func (h *TOMLHeader) ResolveTOMLPath() (resourceTarget, fieldPath string, err error)

ResolveTOMLPath converts a TOML header to resource target and field path

func (*TOMLHeader) String

func (h *TOMLHeader) String() string

String returns a string representation of the TOML header

type TargetedPatch

type TargetedPatch struct {
	Target string                 `yaml:"target"`
	Type   string                 `yaml:"type,omitempty"` // "" (field-level) or "strategic"
	Patch  map[string]interface{} `yaml:"patch"`
}

type VariableContext

type VariableContext struct {
	Values   map[string]interface{}
	Features map[string]bool
}

VariableContext holds variables for substitution

type YAMLDocument

type YAMLDocument struct {
	Node     *yaml.Node
	Resource *unstructured.Unstructured
	Original string // Original YAML content with comments
	Order    int    // Original position in file
}

YAMLDocument represents a single YAML document with preserved structure

func (*YAMLDocument) ApplyPatchesToDocument

func (doc *YAMLDocument) ApplyPatchesToDocument(patches []PatchOp) error

ApplyPatchesToDocument applies patches to a YAML document while preserving structure

func (*YAMLDocument) ApplyStrategicPatchesToDocument

func (doc *YAMLDocument) ApplyStrategicPatchesToDocument(patches []StrategicPatch, lookup KindLookup) error

ApplyStrategicPatchesToDocument applies strategic merge patches to a YAML document while preserving structure. The resource is mutated in place via ApplyStrategicMergePatch, then the YAML node is updated to reflect the new state while preserving comments.

func (*YAMLDocument) UpdateDocumentFromResource

func (doc *YAMLDocument) UpdateDocumentFromResource() error

UpdateDocumentFromResource updates a document's YAML node from its resource

type YAMLDocumentSet

type YAMLDocumentSet struct {
	Documents []*YAMLDocument
	Separator string // Document separator (usually "---")
}

YAMLDocumentSet holds multiple YAML documents with preserved order and comments

func LoadResourcesWithStructure

func LoadResourcesWithStructure(r io.Reader) (*YAMLDocumentSet, error)

LoadResourcesWithStructure loads YAML resources while preserving comments and order

func (*YAMLDocumentSet) Copy

func (set *YAMLDocumentSet) Copy() (*YAMLDocumentSet, error)

Copy creates a deep copy of the YAMLDocumentSet

func (*YAMLDocumentSet) FindDocumentByKindAndName

func (set *YAMLDocumentSet) FindDocumentByKindAndName(kind, name string) *YAMLDocument

FindDocumentByKindAndName finds a document by resource kind and name

func (*YAMLDocumentSet) FindDocumentByKindNameAndNamespace

func (set *YAMLDocumentSet) FindDocumentByKindNameAndNamespace(kind, name, namespace string) *YAMLDocument

FindDocumentByKindNameAndNamespace finds a document by kind, name, and namespace. All three must match for a result.

func (*YAMLDocumentSet) FindDocumentByName

func (set *YAMLDocumentSet) FindDocumentByName(name string) *YAMLDocument

FindDocumentByName finds a document by resource name

func (*YAMLDocumentSet) GetResources

func (set *YAMLDocumentSet) GetResources() []*unstructured.Unstructured

GetResources returns the unstructured resources in order

func (*YAMLDocumentSet) WriteToFile

func (set *YAMLDocumentSet) WriteToFile(filename string) error

WriteToFile writes the document set to a file with preserved structure

Jump to

Keyboard shortcuts

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