Mutation Application

Detailed architecture of how mutations are applied to package revisions.

Overview

The PackageVariant controller applies mutations to downstream package revisions to customize them based on the PackageVariant specification. Mutations are systematic transformations applied to package resources after cloning or upgrading from upstream, enabling configuration injection, function pipeline modification, and dynamic resource generation.

High-Level Architecture

┌─────────────────────────────────────────────────────────┐
│              Mutation Application System                │
│                                                         │
│  ┌──────────────────┐      ┌──────────────────┐         │
│  │   Package        │      │      KRM         │         │
│  │   Context        │ ───> │    Functions     │         │
│  │                  │      │                  │         │
│  │  • ConfigMap     │      │  • Prepend       │         │
│  │    Data          │      │  • Naming        │         │
│  │  • Add/Remove    │      │  • Pipeline      │         │
│  └──────────────────┘      └──────────────────┘         │
│           │                         │                   │
│           └────────┬────────────────┘                   │
│                    ↓                                    │
│          ┌──────────────────┐                           │
│          │      Config      │                           │
│          │    Injection     │                           │
│          │                  │                           │
│          │  • Annotation    │                           │
│          │  • Selector      │                           │
│          │  • Field Copy    │                           │
│          └──────────────────┘                           │
└─────────────────────────────────────────────────────────┘

Mutation Orchestration

Mutations are applied in a specific order during package revision processing:

Application Flow

calculateDraftResources()
  Fetch PackageRevisionResources
  Store Original Resources
  Apply Mutations in Order:
  1. ensurePackageContext()
  2. ensureKRMFunctions()
  3. ensureConfigInjection()
  Compare Original vs Modified
  Changed? ──Yes──> Update PackageRevisionResources
        No
  Return Unchanged

Process characteristics:

  • Sequential application: Mutations applied in fixed order
  • Change detection: Compares original and modified resources
  • Idempotent: Safe to apply multiple times
  • Atomic: All mutations succeed or none applied

When mutations applied:

  • After clone draft creation (initial package creation)
  • After upgrade draft creation (upstream version change)
  • After edit draft creation (mutation-only changes)
  • During reconciliation when mutations changed

Package Context Injection

Injects or modifies data in the package-context.yaml ConfigMap:

Injection Process

ensurePackageContext()
  PackageContext specified? ──No──> Skip
       Yes
  Data or RemoveKeys present? ──No──> Skip
       Yes
  Locate package-context.yaml
  Parse as ConfigMap
  Extract data field
  Add/Update Keys from Data
  Remove Keys from RemoveKeys
  Update ConfigMap
  Write to Resources

Process steps:

  1. Check specification: Skip if PackageContext nil or empty
  2. Locate ConfigMap: Find package-context.yaml in package resources
  3. Parse ConfigMap: Extract as KubeObject with validation
  4. Extract data field: Get existing data map from ConfigMap
  5. Apply additions: Add or update keys from PackageContext.Data
  6. Apply removals: Delete keys listed in PackageContext.RemoveKeys
  7. Update ConfigMap: Set modified data field
  8. Write resources: Update package-context.yaml in resources map

Data Manipulation

Adding or updating keys:

  • Keys from PackageContext.Data merged into ConfigMap data
  • Existing keys overwritten with new values
  • New keys added to data map
  • Preserves other existing keys

Removing keys:

  • Keys listed in PackageContext.RemoveKeys deleted from data
  • Non-existent keys ignored (no error)
  • Removal happens after additions (additions take precedence)

Reserved keys:

  • “name” and “package-path” are reserved
  • Cannot be specified in Data or RemoveKeys
  • Validation rejects PackageVariants with these keys
  • These keys auto-generated by other mechanisms

Error Handling

Validation errors:

  • ConfigMap not found in package resources
  • Invalid ConfigMap structure (not a ConfigMap kind)
  • Missing data field in ConfigMap
  • Invalid data field type (not a map)

Error behavior:

  • Errors returned immediately
  • Mutation application stops
  • Draft not updated
  • Reconciliation fails with error condition

KRM Function Injection

Prepends KRM functions to the Kptfile pipeline:

Injection Process

ensureKRMFunctions()
  Parse Kptfile
  Extract pipeline.mutators
  Extract pipeline.validators
  For Each Field (mutators, validators):
    Remove Old PV Functions
    Generate New PV Functions
    Prepend to Field
    Update Kptfile
  Write to Resources

Process steps:

  1. Parse Kptfile: Extract as KubeObject
  2. Extract pipeline: Get pipeline.mutators and pipeline.validators arrays
  3. Remove old functions: Delete functions with PackageVariant naming pattern
  4. Generate new functions: Create functions from PackageVariant spec
  5. Assign unique names: Use naming pattern with position
  6. Prepend functions: Add new functions to start of arrays
  7. Update Kptfile: Set modified pipeline
  8. Write resources: Update Kptfile in resources map

Function Naming Pattern

Naming format:

PackageVariant.{pvName}.{funcName}.{position}

Example:

  • PackageVariant name: “my-variant”
  • Function name: “set-namespace”
  • Position: 0 (first function)
  • Generated name: “PackageVariant.my-variant.set-namespace.0”

Naming characteristics:

  • Prefix: “PackageVariant” identifies controller-managed functions
  • PV name: Links function to specific PackageVariant
  • Function name: Original function name from spec
  • Position: Index in PackageVariant function list
  • Uniqueness: Combination ensures unique names

Identification logic:

  • Split name by dots (must have exactly 4 segments)
  • Check prefix is “PackageVariant”
  • Check PV name matches current PackageVariant
  • Check position is valid integer
  • Used to identify and remove old functions

Function Ordering

Prepending rationale:

New Pipeline Order:
  1. PackageVariant functions (prepended)
  2. User-defined functions (existing)
  3. Other controller functions (existing)

Why prepend:

  • PackageVariant functions run first
  • Ensures base configuration applied before user functions
  • User functions can override if needed
  • Consistent ordering across reconciliations

Function removal:

  • Old PackageVariant functions removed before prepending
  • Prevents duplicate functions
  • Allows function list changes
  • Maintains clean pipeline

Pipeline Cleanup

Empty field handling:

  • If mutators array empty after processing, field removed from pipeline
  • If validators array empty after processing, field removed from pipeline
  • Avoids dangling empty arrays in Kptfile
  • Cleaner YAML output

Empty pipeline handling:

  • If both mutators and validators empty, pipeline field removed
  • Prevents dangling empty pipeline object
  • Kptfile remains valid without pipeline

Config Injection

Injects data from in-cluster Kubernetes resources into package resources:

Injection Process

ensureConfigInjection()
  Parse All Package Files
  Find Injection Points
  • Scan for annotation
  • Create injection point objects
  Validate Injection Points
  • Check duplicates
  • Check annotation values
  Inject Resources
  • Load in-cluster resources
  • Match selectors
  • Copy allowed fields
  Update Modified Files
  Set Kptfile Conditions/Gates

Process steps:

  1. Parse files: Convert all package resources to KubeObjects
  2. Find injection points: Scan for config-injection annotations
  3. Validate: Check for duplicates and invalid annotations
  4. Load in-cluster resources: Query Kubernetes for matching resources
  5. Match selectors: Find in-cluster resource matching injector spec
  6. Inject fields: Copy allowed fields from in-cluster to in-package
  7. Update files: Write modified resources back to package
  8. Set conditions: Update Kptfile with injection status

Injection Point Discovery

Annotation-based discovery:

  • Annotation key: “kpt.dev/config-injection”
  • Annotation values: “required” or “optional”
  • Scanned across all package resources
  • Each annotated resource becomes injection point

Injection point structure:

  • file: Filename containing the resource
  • object: KubeObject to inject into
  • conditionType: Unique condition identifier
  • required: Whether injection must succeed
  • errors: Validation or injection errors
  • injected: Whether injection succeeded
  • injectedName: Name of injected resource

Condition type generation:

Format: config.injection.{Kind}.{Name}
Example: config.injection.ConfigMap.my-config

Selector Matching

Injector specification:

  • Name: Name of in-cluster resource to inject
  • Group: Optional API group filter
  • Version: Optional API version filter
  • Kind: Optional kind filter

Matching process:

For Each Injector:
  Check Group/Version/Kind
  Matches In-Package Object? ──No──> Skip
       Yes
  Search In-Cluster Resources
  Find by Name
  Found? ──Yes──> Inject Resource
        No
  Try Next Injector

Matching characteristics:

  • GVK filters optional (nil means match any)
  • Name matching required (exact match)
  • First matching injector wins
  • Remaining injectors skipped after match

Field Injection

Allowed fields:

  • ConfigMap: “data” field
  • All other kinds: “spec” field
  • Whitelist prevents arbitrary field injection
  • Security by design (limited field access)

Injection process:

injectResource()
  Check Allowed Fields
  For Each Allowed Field:
    Group/Kind Match? ──No──> Continue
       Yes
    Extract Field from In-Cluster
    Set Field in In-Package
    Set Injected Annotation
    Mark Injected

Injected resource annotation:

  • Annotation key: “kpt.dev/injected-resource”
  • Annotation value: Name of in-cluster resource
  • Added to in-package resource
  • Tracks injection source

Kptfile Conditions and Gates

Readiness gates:

  • Added for required injection points
  • Condition type from injection point
  • Prevents package approval until injection succeeds
  • Optional injection points don’t add gates

Status conditions:

  • One condition per injection point
  • Condition type from injection point
  • Status: True (injected) or False (not injected)
  • Reason: “ConfigInjected” or “NoResourceSelected”
  • Message: Injected resource name or error details

Condition management:

For Each Injection Point:
  Required? ──Yes──> Add Readiness Gate
        No
  Set Status Condition
  • Type: injection point condition type
  • Status: True/False based on injection
  • Reason: success or failure reason
  • Message: details or error

Mutation Ordering

Mutations applied in fixed order for consistency:

Order Rationale

1. Package Context (first):

  • Provides base configuration data
  • Other mutations may depend on context
  • ConfigMap must exist before other operations

2. KRM Functions (second):

  • Modifies function pipeline
  • Functions will execute during render
  • Must be set before injection (functions may use injected data)

3. Config Injection (last):

  • Injects cluster-specific configuration
  • May reference package context
  • Functions can process injected data during render

Why this order:

  • Dependencies flow downward
  • Each mutation can build on previous
  • Render task (after mutations) sees complete configuration

Change Detection

The controller detects whether mutations changed package resources:

Detection Process

calculateDraftResources()
  Store Original Resources
  Apply All Mutations
  Compare Original vs Modified
  File Count Changed? ──Yes──> Changed
        No
  For Each File:
    File Deleted? ──Yes──> Changed
        No
    Content Changed? ──Yes──> Changed
        No
  Continue
  All Files Unchanged? ──Yes──> Unchanged

Comparison logic:

  1. File count: Different number of files indicates change
  2. File deletion: File in original but not in modified indicates change
  3. Content comparison: Byte-by-byte comparison of file contents
  4. Kptfile special case: Semantic comparison (not byte-by-byte)

Kptfile semantic comparison:

  • Parse both Kptfiles as structured objects
  • Compare semantically using kptfileutil.Equal
  • Ignores formatting differences (whitespace, indentation)
  • Prevents false positives from YAML rendering differences

Change Handling

If changed:

  • Return modified PackageRevisionResources
  • Return changed=true flag
  • Caller updates PackageRevisionResources via Porch API
  • Triggers render task execution

If unchanged:

  • Return original PackageRevisionResources
  • Return changed=false flag
  • Caller skips update (no API call)
  • No render task execution needed

Implications:

  • Avoids unnecessary API calls
  • Prevents spurious render executions
  • Reduces load on Porch server
  • Improves reconciliation performance

Error Handling

Mutation errors handled at multiple levels:

Mutation-Level Errors

Package context errors:

  • ConfigMap not found
  • Invalid ConfigMap structure
  • Missing or invalid data field
  • Field manipulation errors

KRM function errors:

  • Kptfile not found
  • Invalid Kptfile structure
  • Pipeline manipulation errors
  • Function name generation errors

Config injection errors:

  • File parsing errors
  • Duplicate injection points
  • Invalid annotation values
  • In-cluster resource query errors
  • Field injection errors

Error Propagation

Mutation Error
  Return Error
calculateDraftResources()
  Return Error
ensurePackageVariant()
  Return Error
Reconcile()
  Set Ready=False
  Requeue

Error flow:

  • Mutation errors returned immediately
  • Stops mutation application
  • Propagates to reconciliation loop
  • Sets Ready condition to False
  • Triggers requeue for retry

Error Recovery

Transient errors:

  • In-cluster resource temporarily unavailable
  • Network errors during resource query
  • Automatic retry on next reconciliation

Permanent errors:

  • Invalid PackageVariant specification
  • Missing required resources in package
  • Requires PackageVariant or package fix

Mutation Characteristics

Idempotency

Idempotent operations:

  • Package context: Overwrites existing keys
  • KRM functions: Removes old, adds new
  • Config injection: Replaces field content

Implications:

  • Safe to apply multiple times
  • Produces same result regardless of repetition
  • Reconciliation can retry without side effects

Isolation

Mutation isolation:

  • Each mutation operates independently
  • No shared state between mutations
  • Order enforced by orchestration, not dependencies

Draft isolation:

  • Mutations modify draft workspace only
  • No effect on published packages
  • Changes visible only after draft closure

Validation

Pre-mutation validation:

  • PackageVariant spec validated before mutations
  • Reserved keys checked
  • Injector names validated
  • Prevents invalid mutations

Post-mutation validation:

  • Render task validates modified package
  • Functions can fail on invalid configuration
  • Errors prevent draft closure

Performance Considerations

Optimization Strategies

Change detection:

  • Avoids unnecessary API calls
  • Skips render when unchanged
  • Reduces reconciliation time

Selective file parsing:

  • Only parses files matching resource patterns
  • Skips non-resource files (README, etc.)
  • Reduces parsing overhead

In-memory operations:

  • All mutations in-memory
  • No disk I/O during mutations
  • Fast transformation operations

Scalability

Package size:

  • Mutations scale linearly with package size
  • Large packages take longer to parse and compare
  • Change detection overhead proportional to file count

Injection points:

  • Multiple injection points increase processing time
  • Each injection point queries Kubernetes API
  • Consider limiting injection points per package

Function count:

  • Function manipulation scales with pipeline size
  • Large pipelines take longer to process
  • Prepending is O(n) operation