Subpackage Management#
A subpackage is any kpt package nested within a parent package directory, identified by its own Kptfile. The ecosystem supports two distinct subpackage types that drive how clone, upgrade, and rendering operations behave :
| Type | Kptfile has upstream? | Can be independently upgraded? |
|---|---|---|
| Independent | Yes | Yes — has its own upstream ref |
| Dependent | No | No — inherits parent's upstream implicitly |
Determining the type at runtime: the Subpackages() function in pkg/lib/pkg/pkg.go classifies subpackages by checking kf.Upstream != nil (Remote matcher) or kf.Upstream == nil (Local matcher). The canonical type definitions (Upstream, Locator/UpstreamLock) live in api/kptfile/v1/types.go.
A package's unique identifier is its relative path from the top-level package root (e.g., wordpress/backend/mysql). Package names must match metadata.name in the Kptfile, which kpt auto-updates when forking .
Clone and Upgrade Operations#
kpt CLI (local filesystem)#
kpt pkg get <upstream-url> <subdir> fetches an upstream package into a subdirectory, creating an independent subpackage with its own upstream/upstreamLock in its Kptfile. To make it dependent, manually delete those sections .
kpt pkg update <pkg>@<ref> performs a 3-way merge (original upstream commit → new upstream commit → local edits) for independent packages and their descendants. Available strategies are resource-merge (default), fast-forward, force-delete-replace, and copy-merge .
Porch API & porchctl (server-side)#
Porch introduced independent subpackage support across a series of PRs:
- API —
SubpackageDirfield : AddedsubpackageDirtoPackageCloneTaskSpecandPackageUpgradeTaskSpecin bothv1alpha1and internal types. A valid path is a non-empty relative path with no leading/,./, or..segments. - API —
SubpackageOperationonPackageRevisionSpec: Thev1alpha2API extendedPackageRevisionSpecwithSubpackageOperation *SubpackageOperation(mutually exclusivecloneFromorupgradefields) andPackageRevisionStatuswithLastSubpackageOperationto prevent re-execution. - Server implementation : Added
IsValidSubpackageDir()validation (rejects uppercase, underscores, absolute paths,..traversal) and enforced task-list constraints — subpackage dir must not appear in the first task, at most 2 tasks allowed. porchctlCLI : Added--subpackage-dirflag toporchctl rpkg cloneandporchctl rpkg upgrade.
# Clone an upstream blueprint into a subdirectory of a draft
porchctl rpkg clone upstream-repo.blueprint.v1 deployment.my-app.v2 \
--subpackage-dir=components/networking --namespace=default
# Upgrade the independent subpackage to a newer revision
porchctl rpkg upgrade deployment.my-app.v2 \
--subpackage-dir=components/networking --revision=3
Constraints for Porch subpackage operations:
- Parent package revision must be in Draft state.
- For clone:
subpackageDirmust not already exist. - For upgrade:
subpackageDirmust exist and contain a validKptfilewith upstream info. --workspaceand--repositorymust not be specified alongside--subpackage-dir.
Subpackage naming: Porch derives metadata.name from subpackageDir by replacing / with . (e.g., ran/south/region-1a → ran.south.region-1a). The result must satisfy Kubernetes DNS subdomain rules .
Rendering Strategies#
The kpt fn render command hydrates the entire package tree. The Renderer.Execute() entry point in pkg/lib/kptops/render_executor.go selects the traversal strategy based on an annotation on the root Kptfile.
Depth-First Post-Order (default)#
The default hydrate() function processes the package tree bottom-up (leaf subpackages first). For each package it:
- Recursively hydrates all direct subpackages.
- Concatenates local resources with the hydrated output of every subpackage.
- Runs the local mutators then validators on that combined input.
This means a parent package's pipeline sees all descendant resources, enabling labels, namespaces, or policies set at the root to propagate downward.
Breadth-First Top-Down (BFS)#
Setting kpt.dev/bfs-rendering: "true" in metadata.annotations of the root Kptfile switches to hydrateBfsOrder() .
BFS rendering is a two-phase process:
discoverAndLoadPackages()— BFS traversal builds the full tree and loads each package's local resources.executePipelinesWithScopedVisibility()— Pipelines run top-down; each package's pipeline input is scoped to itself and its descendants only (siblings and ancestors are not visible).
Use BFS when a parent pipeline must inject resources that subpackage pipelines then process, or when sibling isolation is required.
Annotation Reference#
| Annotation | Effect |
|---|---|
kpt.dev/bfs-rendering: "true" | Switch to BFS top-down rendering |
kpt.dev/save-on-render-failure: "true" | Persist partially-rendered resources on pipeline failure for debugging |
Render Status#
After every in-place render, the root Kptfile's status.conditions is updated with a Rendered condition (RenderSuccess / RenderFailed) and a detailed RenderStatus with per-step results . Status is not written for out-of-place modes (-o stdout, -o unwrap, -o <dir>).
Key Files and Entry Points#
| File | Purpose |
|---|---|
api/kptfile/v1/types.go | Core type definitions: KptFile, Upstream, Locator/UpstreamLock, Subpackage, Pipeline, BFSRenderAnnotation |
pkg/lib/pkg/pkg.go | Pkg struct; DirectSubpackages(), Subpackages() (local vs. remote matcher), LocalResources() |
pkg/lib/kptops/render_executor.go | Renderer.Execute(), hydrate() (DFS), hydrateBfsOrder() (BFS), updateRenderStatus() |
api/porch/v1alpha1/types.go | Porch task types: PackageCloneTaskSpec.SubpackageDir, PackageUpgradeTaskSpec.SubpackageDir |
api/porch/v1alpha1/util.go | GetSubpackageDir(), IsValidSubpackageDir() validation |
api/porch/v1alpha2/packagerevision_types.go | SubpackageOperation on PackageRevisionSpec; LastSubpackageOperation on PackageRevisionStatus |
pkg/cli/commands/rpkg/clone/command.go | porchctl rpkg clone --subpackage-dir |
pkg/cli/commands/rpkg/upgrade/command.go | porchctl rpkg upgrade --subpackage-dir |
Further reading:
- kpt Package Concepts — dependent vs. independent packages,
kpt pkg get/update - Porch subpackage concepts doc
- Porch working-with-subpackages tutorial