akua / examples / 02-webapp-postgres

Example 02 — webapp-postgres

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

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.

Layout

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

What's new vs 01

Run

akua add                                 # resolve cnpg + webapp charts
akua render --inputs inputs.yaml         # render both into ./rendered/
akua test                                # run test_package.k

The cross-source convention pattern

CloudNativePG (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.

What's disallowed

See also

package.k

# Example 02 — webapp-postgres
#
# Cross-source wiring. A webapp consumes a Postgres connection URL from a
# Secret created by the CloudNativePG operator — entirely by convention,
# no runtime late-binding required, all deterministic at CI time.
#
# Demonstrates:
#   - two Helm sources in one package
#   - per-source input mapping (`cluster.name`, `ingress.hostname`, etc.)
#   - cross-source references via predictable operator naming
#   - optional postRenderer for cross-cutting mutation (team label)
#
# Render:
#   akua render --inputs inputs.yaml --out ./rendered

import akua.ctx
import charts.cnpg    as cnpg
import charts.webapp  as webapp

schema Input:
    """Customer-facing contract. Six fields; four have defaults."""
    appName:  str
    hostname: str
    replicas: int = 3
    team:     str = "platform"
    database: {
        user: str = "app"
    }

input: Input = ctx.input()

# Each `charts.<name>` import lands a synthesized stub with a
# `template` lambda + typed `Values` schema. The engine
# (`akua.helm`) doesn't appear at the call site — the stub dispatches.
_pg = cnpg.template(cnpg.TemplateOpts {
    values = cnpg.Values {
        cluster = {
            name:      "${input.appName}-pg"
            instances: 3
        }
        bootstrap.initdb = {
            database: input.appName
            owner:    input.database.user
        }
        monitoring.enabled: True
    }
})

# Application. References the conventional secret name above.
# If CNPG's operator changes its naming convention, this is the one
# place we'd update — still at CI time, still deterministic.
_app = webapp.template(webapp.TemplateOpts {
    values = webapp.Values {
        replicaCount         = input.replicas
        ingress.hostname     = input.hostname
        ingress.tls.enabled  = True
        fullnameOverride     = input.appName
        env = [
            {
                name = "DATABASE_URL"
                valueFrom.secretKeyRef = {
                    name = "${input.appName}-pg-app"   # CNPG convention
                    key  = "uri"
                }
            }
        ]
    }
})

# Cross-cutting label applied to the webapp resources — same effect
# as a chart postRenderer, expressed as a list comprehension after
# the alias-method call returns the typed list. Mirrors example 11's
# overlay pattern.
_labeled = [r | {metadata.labels |= {"team": input.team}} for r in _app]

resources = [*_pg, *_labeled]

Source: examples/02-webapp-postgres/