PackageVariant Controller
Overview
The PackageVariant Controller is a Kubernetes controller that manages individual package variants - maintaining a one-to-one relationship between an upstream package and a downstream package. It continuously synchronizes downstream PackageRevisions with upstream changes and applies configured mutations.
Key characteristics:
- Reconciliation-based: Uses standard Kubernetes controller-runtime reconciliation loop
- Upstream tracking: Monitors upstream PackageRevisions for changes via UpstreamLock comparison
- Draft-driven workflow: All changes create draft PackageRevisions that require approval
- Policy-based ownership: Configurable adoption and deletion policies for existing packages
- Mutation application: Applies package context, pipeline functions, and config injection
- Finalizer-based cleanup: Ensures proper cleanup of owned PackageRevisions on deletion
Implementation Details
Reconciliation State Machine
The PackageVariant controller implements a state machine that determines actions based on the current state of upstream and downstream packages:
PackageVariant Created
↓
Validate Spec
↓
Find Upstream PR
↓
Downstream Exists? ──No──> Create Clone Draft
│ ↓
Yes Apply Mutations
↓ ↓
Check UpstreamLock Return
↓
Up-to-date? ──Yes──> Check Mutations
│ ↓
No Changed? ──No──> Done
↓ │
Create Upgrade Draft Yes
↓ ↓
Apply Mutations Create Edit Draft
↓ ↓
Return Apply Mutations
↓
Return
State transitions:
- No downstream: Create clone draft from upstream
- Upstream changed: Create upgrade draft (three-way merge)
- Mutations changed: Create edit draft from published downstream
- Up-to-date: No action needed
Upstream Tracking Mechanism
UpstreamLock comparison logic:
The controller determines if a downstream package is up-to-date by examining the UpstreamLock stored in the downstream PackageRevision’s status. This lock contains a Git reference that points to the upstream package revision currently used.
Comparison process:
Check Downstream UpstreamLock
↓
UpstreamLock exists? ──No──> Consider up-to-date
│
Yes
↓
Git ref exists? ──No──> Consider up-to-date
│
Yes
↓
Git ref starts with "drafts"? ──Yes──> Need update
│
No
↓
Extract revision number from Git ref
↓
Compare with desired revision
↓
Match? ──Yes──> Up-to-date
│
No
↓
Trigger upgrade draft
UpstreamLock structure:
- Stored in PackageRevision.Status.UpstreamLock
- Contains Git ref (e.g., “refs/tags/v1”, “refs/tags/v2”)
- Revision number extracted from ref path
- Enables automatic version tracking
Implications:
- Controller doesn’t need to fetch upstream content to detect changes
- Git ref comparison is fast and efficient
- Supports automatic upgrades when upstream publishes new versions
Draft Management
Three draft types:
- Clone Draft - Initial package creation:
Create PackageRevision
↓
Spec.Tasks = [Clone]
↓
Clone.Upstream.UpstreamRef = upstream PR name
↓
WorkspaceName = "packagevariant-1"
↓
Lifecycle = Draft
- Upgrade Draft - Upstream version change:
Create PackageRevision
↓
Spec.Tasks = [Upgrade]
↓
Upgrade.OldUpstream = old upstream PR
Upgrade.NewUpstream = new upstream PR
Upgrade.LocalPackageRevision = current downstream PR
Upgrade.Strategy = ResourceMerge
↓
WorkspaceName = "packagevariant-2"
↓
Lifecycle = Draft
- Edit Draft - Mutation changes only:
Create PackageRevision
↓
Spec.Tasks = [Edit]
↓
Edit.Source = current downstream PR
↓
WorkspaceName = "packagevariant-3"
↓
Lifecycle = Draft
Workspace naming:
- Prefix: “packagevariant-”
- Incremental numbering: packagevariant-1, packagevariant-2, etc.
- Scoped per package/repository combination
- Ensures unique workspace names
Draft workflow:
- Controller creates draft PackageRevision via Porch API
- Porch creates mutable workspace
- Controller fetches PackageRevisionResources
- Controller applies mutations (package context, pipeline, injections)
- Controller updates PackageRevisionResources
- Draft remains for human/automation approval
- Controller does NOT auto-publish (separation of concerns)
Adoption and Deletion Policies
Adoption Policy:
AdoptionPolicy: adoptNone (default)
↓
Only manage self-created packages
↓
Check OwnerReference.UID == PackageVariant.UID
↓
Ignore packages without our UID
AdoptionPolicy: adoptExisting
↓
Take ownership of matching packages
↓
Find packages matching downstream repo/package
↓
Add our OwnerReference
↓
Apply our labels/annotations
Deletion Policy:
DeletionPolicy: delete (default)
↓
PackageVariant deleted
↓
For each owned PackageRevision:
↓
Draft/Proposed? ──Yes──> Delete immediately
│
No (Published)
↓
Set Lifecycle = DeletionProposed
↓
Wait for approval
DeletionPolicy: orphan
↓
PackageVariant deleted
↓
For each owned PackageRevision:
↓
Remove our OwnerReference
↓
Leave package in place
Policy rationale:
- adoptNone: Safe default, prevents accidental takeover
- adoptExisting: Enables gradual migration to controller management
- delete: Ensures cleanup of generated packages
- orphan: Preserves packages when controller no longer needed
Reconciliation Overview
The PackageVariant controller implements a continuous synchronization pattern between upstream and downstream packages. The reconciliation process follows standard Kubernetes controller patterns with validation, upstream discovery, and package management.
Key reconciliation characteristics:
- Idempotent: Can be called multiple times safely
- Level-triggered: Works from current state, not events
- Status updates: Deferred to ensure they happen even on errors
- Requeue on errors: Transient errors trigger automatic retry
For detailed reconciliation flows, state machine, validation logic, and error handling, see PackageVariant Reconciliation.
Mutation Application Overview
The controller applies three types of mutations to downstream packages:
1. Package Context Injection:
- Modifies package-context.yaml ConfigMap
- Adds/updates/removes keys based on PackageVariant spec
- Reserved keys (“name”, “package-path”) auto-generated
2. KRM Function Injection:
- Prepends functions to Kptfile pipeline
- Functions named:
PackageVariant.{pvName}.{funcName}.{position} - Ensures PackageVariant functions run before user functions
3. Config Injection:
- Injects data from in-cluster Kubernetes resources
- Enables cluster-specific configuration
- Dynamic configuration based on cluster state
For detailed mutation application flows, ordering, and change detection, see Mutation Application.
Status Conditions
Condition types:
-
Stalled - Whether PackageVariant is making progress:
- Status=True: Validation error or upstream not found
- Status=False: All validation passed, upstream found
- Reason: “ValidationError” or “Valid”
-
Ready - Whether reconciliation succeeded:
- Status=True: Successfully ensured downstream package
- Status=False: Error during reconciliation
- Reason: “NoErrors” or “Error”
DownstreamTargets tracking:
- List of downstream PackageRevisions created/adopted
- Includes Name and RenderStatus
- Updated after each reconciliation
- Provides visibility into managed packages