Two Helm charts composed into one Package. A webapp consumes a Postgres connection URL from a Secret the CloudNativePG operator creates — entirely by convention, no runtime late-binding required, all deterministic at CI time. Adds a list-comprehension overlay to label every rendered resource with the owning team. Demonstrates a test_package.k unit-test file.
02-webapp-postgres/
├── akua.toml declares charts.cnpg and charts.webapp
├── akua.lock digest + signature ledger
├── package.k the Package — two `<chart>.template()` calls (alias-method form) + aggregation
├── test_package.k KCL unit tests for the schema + defaults
├── inputs.yaml sample inputs
└── README.md
- Two sources in one Package. Both are
<chart>.template(...)calls (alias-method form, dispatched via the synthesizedcharts.<name>stub) returning resource lists;resources = [*_pg, *_labeled]aggregates them. - Cross-source wiring by convention. The webapp references the Postgres Secret by its predictable CloudNativePG name (
${appName}-pg-app). No value needs to flow between the two source calls — both derive their values frominput. - List-comprehension overlay.
[r | {metadata.labels |= {"team": ...}} for r in _app]stamps ateamlabel onto everything the app chart emits — same effect a Helm post-renderer would have, expressed in plain KCL after the typed list returns. - Unit tests.
test_package.kasserts schema defaults and validates thatcheck:blocks catch the invariant violations.
akua add # resolve cnpg + webapp charts
akua render --inputs inputs.yaml # render both into ./rendered/
akua test # run test_package.kCloudNativePG (and most mature Kubernetes operators) publishes contracts on resource naming — cluster foo creates Secret foo-app with key uri. That's a runtime contract. The webapp references it by the same convention at render time:
env = [{
name = "DATABASE_URL"
valueFrom.secretKeyRef = {
name = "${input.appName}-pg-app" # CNPG convention
key = "uri"
}
}]If CNPG ever changed its naming convention, this is the one place we'd update — still at CI time, still deterministic. No cluster.get() runtime call ever needed.
- Source A cannot reference Source B's output. Both derive from
input; cross-source late-binding is the RGD case. If you genuinely need it, route that source to aResourceGraphDefinitionoutput and let kro reconcile. See 06-multi-engine/ for the pattern. - No runtime cluster reads from KCL. Determinism is load-bearing (design-notes.md §2.2).
- package-format.md §4 Body — engine calls, postRenderer, aggregation
- package-format.md §8 What's disallowed — cross-source wiring rules
- 03-multi-env-app/ — next example: Package + App + Environment in one workspace