Composite Resources
A composite groups related resources into a single reusable unit. Instead of defining multiple related resources separately every time, you define them once as a composite and instantiate it with props.
Defining a Composite
Section titled “Defining a Composite”Use Composite<Props> from @intentius/chant. The factory function receives props and returns a record of named resources:
import { Sub, Role, Function } from "@intentius/chant-lexicon-aws";import { Composite } from "@intentius/chant";
interface LambdaServiceProps { name: string | ReturnType<typeof Sub>; handler: string; runtime?: string; timeout?: number;}
export const LambdaService = Composite<LambdaServiceProps>((props) => { const role = new Role({ AssumeRolePolicyDocument: { Version: "2012-10-17", Statement: [{ Effect: "Allow", Principal: { Service: "lambda.amazonaws.com" }, Action: "sts:AssumeRole", }], }, });
const func = new Function({ FunctionName: props.name, Handler: props.handler, Runtime: props.runtime ?? "nodejs20.x", Role: role.Arn, Timeout: props.timeout ?? 30, Code: { ZipFile: "exports.handler = async () => ({});" }, });
return { role, func };}, "LambdaService");This defines a LambdaService composite that creates two resources: a role and a function.
Using a Composite
Section titled “Using a Composite”Instantiate a composite by calling it with props:
import { Sub, AWS } from "@intentius/chant-lexicon-aws";import { LambdaService } from "./composite-definition";
export const api = LambdaService({ name: Sub`${AWS.StackName}-api`, handler: "index.handler",});This produces all underlying resources with names derived from the props. The composite instance exposes its members directly, so api.role and api.func are accessible for cross-references.
Referencing Members
Section titled “Referencing Members”Within the factory, you can reference other members directly since they’re just variables in the same scope (as seen in the definition above — role: role.arn).
Outside the composite, access members via the instance:
export const monitor = new _.Alarm({ alarmName: "api-errors", dimensions: [{ name: "FunctionName", value: api.func.functionName }],});When to Use Composites
Section titled “When to Use Composites”Composites are useful when:
- Multiple resources always appear together — a function always needs a role and log destination
- You want consistent naming — derive all resource names from a single
nameprop - Cross-resource wiring is complex — encapsulate the references inside the composite
For sharing default property values across resources of the same type, see Presets instead.
Some lexicons also support child projects — subdirectories that build to separate output files. The parent references a child project via a lexicon-specific function (e.g. AWS nestedStack()), and cross-stack values are explicitly exported with stackOutput(). See Multi-Stack Projects for an overview.
What a Composite Records About Its Fields
Section titled “What a Composite Records About Its Fields”A composite is the one thing chant emits that is not one to one with its source. Two typed arguments become five resources, so a drift report comparing live properties against declared ones sees flat resources and no longer sees the factory that produced them.
The build records the missing half. For every property an expansion emits, chant build stores where the value came from, beside the build output and never inside it, so the record cannot reach the document an applier writes. There are four answers:
- a parameter of the factory, with the parameter path that flowed into the field
- a literal the factory fixes, which no argument at the call site can move
- a field the author declared directly, outside any expansion
- unknown, when the build expanded a call without interpreting its factory
chant lifecycle diff reads the record on the return leg. A drifted field that traces to a parameter is reported as a one line change to that parameter, naming the file, the call and the parameter path, leaving the source structure intact. A drifted field the factory fixes is refused by name instead. Closing it would mean flattening the abstraction away, and the two honest options are to parameterize the field or to stop using the abstraction at that call site. Both are choices for a person to make.
Hardcoding a Field Gives Up Its Provenance
Section titled “Hardcoding a Field Gives Up Its Provenance”Whatever a factory writes as a literal becomes a field the return leg can only refuse:
export const WebService = Composite<WebServiceProps>((props) => { const deployment = new Deployment({ replicas: 3, // fixed: the return leg will refuse to change it image: props.image, // parameterized: a drift here proposes `image` }); return { deployment };}, "WebService");That is a fair trade for a value that genuinely should not vary per caller, such as an API version or a label chant itself owns. It is a poor one for a value an operator might need to change under pressure, because the only ways to move it are to edit the factory for every caller or to abandon it for one of them. Prefer a parameter with a default when a caller might ever need the field to differ.
Two other shapes lose provenance the same way. A field wired to a sibling member inside the factory reads as fixed. role: role.arn is the usual case, and no argument at the call site moves it, so proposing a parameter change would be wrong.
A factory outside the interpretable subset gives up provenance for every field it emits, not only the hardcoded ones. chant build --fold interprets a body instead of calling it only while that body stays inside the evaluated subset, so a factory with an if or an array transform is invoked and the build records unknown for its properties. Unknown is a legal answer, and the return leg handles it by falling back to editing the declared value and saying that is what it did. It is still less than the factory could have given.
Kubernetes Composites
Section titled “Kubernetes Composites”The Kubernetes lexicon ships 27 production-ready composites across four categories. Here’s a quick overview:
Generic Composites
Section titled “Generic Composites”Platform-agnostic patterns that work on any conformant cluster:
| Composite | What it creates |
|---|---|
| WebApp | Deployment + Service + optional Ingress |
| AutoscaledService | Deployment + Service + HPA + PDB with probes |
| CronWorkload | CronJob with RBAC |
| WorkerPool | Queue worker Deployment with RBAC and autoscaling |
| NamespaceEnv | Namespace with ResourceQuota, LimitRange, and NetworkPolicy |
| NodeAgent | DaemonSet with cluster-wide RBAC and host mounts |
Cloud-Specific Composites
Section titled “Cloud-Specific Composites”Each cloud provider has composites that integrate Kubernetes with its native services:
| Pattern | EKS | AKS | GKE |
|---|---|---|---|
| Workload identity | IrsaServiceAccount | AksWorkloadIdentityServiceAccount | WorkloadIdentityServiceAccount |
| Ingress | AlbIngress | AgicIngress | GceIngress / GkeGateway |
| Block storage | EbsStorageClass | AzureDiskStorageClass | GcePdStorageClass |
| Shared storage | EfsStorageClass | AzureFileStorageClass | FilestoreStorageClass |
| DNS | ExternalDnsAgent | AksExternalDnsAgent | GkeExternalDnsAgent |
| Observability | FluentBitAgent / AdotCollector | AzureMonitorCollector | GkeFluentBitAgent / GkeOtelCollector |
Example: AutoscaledService
Section titled “Example: AutoscaledService”AutoscaledService is the most common composite — a production HTTP service with horizontal autoscaling, disruption budgets, and health probes:
import { AutoscaledService } from "@intentius/chant-lexicon-k8s";
const { deployment, service, hpa, pdb } = AutoscaledService({ name: "api", image: "api:2.0", port: 8080, maxReplicas: 10, minReplicas: 3, targetCPUPercent: 60, targetMemoryPercent: 80, cpuRequest: "200m", memoryRequest: "256Mi", cpuLimit: "1", memoryLimit: "1Gi", livenessPath: "/healthz", readinessPath: "/readyz", topologySpread: true, namespace: "production",});All four members (deployment, service, hpa, pdb) are available for cross-references just like any other resource.
Composite Reference Pages
Section titled “Composite Reference Pages”- Generic Composites — WebApp, AutoscaledService, CronWorkload, WorkerPool, NamespaceEnv, NodeAgent
- EKS Composites — IRSA, ALB Ingress, EBS/EFS storage, CloudWatch, ADOT, ExternalDNS
- AKS Composites — Workload Identity, AGIC Ingress, Azure Disk/Files, Azure Monitor, ExternalDNS
- GKE Composites — Workload Identity, GCE Ingress, Gateway API, PD/Filestore, Cloud Logging, Config Connector