akua / examples / 08-pkg-compose

08-pkg-compose

Package composition via `pkg.render` — an outer Package calls a reusable inner Package twice with different inputs and concatenates the results. Renders end-to-end today (pure KCL; no helm needed).

Package composition via pkg.render — an outer Package calls a reusable inner Package twice with different inputs and concatenates the results. Renders end-to-end today (pure KCL; no helm needed).

What's here

filepurpose
package.kOuter Package; calls pkg.render(pkg.Render { package = "shared", ... }) twice with distinct inputs.
shared/package.kInner Package; emits one ConfigMap parameterized by name + payload.
shared/akua.tomlInner manifest — marks shared/ as an Akua package.
akua.tomlOuter manifest — declares shared as a workspace-local path dep.
inputs.example.yamlPer-component inputs, auto-discovered by akua render.

Render

cargo run -q -p akua-cli -- render --package examples/08-pkg-compose/package.k --out ./rendered

Or, from the example directory:

akua render --out ./rendered

Two ConfigMaps land in ./rendered/ (checked in as reference output):

rendered/
├── 000-configmap-frontend.yaml
└── 001-configmap-backend.yaml

How pkg.render works

pkg.render is a synchronous KCL host plugin: the handler resolves the package alias against the calling Package's akua.toml [dependencies] (no filesystem paths in user code), calls PackageK::load(...).render(inputs) inline, and returns the inner resources list directly to the caller. Because the return is a real list, list-comprehension patches and filter expressions on the result work natively — no post-eval rewrite step.

Reentrancy works because akua's KCL fork copies the plugin handler fn pointer out of its mutex before invoking the callback (see cnap-tech/kcl#akua-wasm32); upstream KCL holds the mutex across the call and would deadlock here.

Cycle detection uses akua-core's thread-local render-stack: a Package referring to itself, directly or transitively (A → B → A), is rejected before infinite recursion. Nested pkg.render calls (A → B → C) recurse through the same handler, with each inner Package's render pushing/popping the stack.

package.k

# Outer Package: composes two ConfigMaps from a reusable nested
# Package via `pkg.render`. Exercises akua's Package-of-Packages
# composition — the inner Package is declared as the `shared` dep in
# akua.toml and loaded + rendered fresh for each `pkg.render` call.
#
# Render:
#
#   akua render --package examples/08-pkg-compose/package.k --out ./rendered
#
# Produces two ConfigMaps: `config-frontend` and `config-backend`,
# each with its own data payload from the outer's inputs.

import akua.ctx
import akua.pkg

schema ComponentInput:
    """Per-component inputs — name + payload passed through to the
    shared ConfigMap schema in `./shared/`."""

    name: str
    data: {str:str} = {}

schema Input:
    frontend: ComponentInput = ComponentInput { name = "config-frontend" }
    backend:  ComponentInput = ComponentInput { name = "config-backend"  }

input: Input = ctx.input()

# Each call loads + renders the `shared` dep synchronously with the
# supplied inputs and returns the inner resources list. `package` is
# the dep alias from akua.toml — no filesystem path in the program.
_frontend = pkg.render(pkg.Render {
    package = "shared"
    inputs  = {
        name    = input.frontend.name
        payload = input.frontend.data
    }
})

_backend = pkg.render(pkg.Render {
    package = "shared"
    inputs  = {
        name    = input.backend.name
        payload = input.backend.data
    }
})

resources = _frontend + _backend

Rendered output

000-configmap-frontend.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: frontend
  labels:
    app.kubernetes.io/name: frontend
    app.kubernetes.io/component: shared
data:
  PUBLIC_API_URL: https://api.example.com
  THEME: dark

001-configmap-backend.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: backend
  labels:
    app.kubernetes.io/name: backend
    app.kubernetes.io/component: shared
data:
  DATABASE_URL: postgres://db.internal:5432/app
  LOG_LEVEL: info

Source: examples/08-pkg-compose/