Composite Resources
Show the whole prompt
Read https://intentius.io/chant/guide/composite-resources/.
Find resources in this project that always appear together and repeat their naming, and group each set into a `Composite` from `@intentius/chant` with a `name` prop, as the page shows.
Replace the copies with calls to the composite, and keep every export name that other files import.
Run `npx chant build` and `npx chant lint`, check that the built output has the same resources as before, and open a pull request.
Never deploy to a real cloud account or shared environment (with `chant run`, `chant components` or a platform's own CLI), approve (a pull request review, `chant approve` or `chant run approve`), override a policy denial (`chant approve policy-override`), or merge; never cut or publish a release (`chant release cut --yes` or `chant release publish`), push or delete a `ci/*` tag (`chant ci tick` pushes them), or change the value of an existing flag; never touch `.chant/allowed_signers` or `chant/lifecycle`.Optional: hand this page to your coding agent
Read https://intentius.io/chant/guide/composite-resources/.Find resources in this project that always appear together and repeat their naming, and group each set into a `Composite` from `@intentius/chant` with a `name` prop, as the page shows.Replace the copies with calls to the composite, and keep every export name that other files import.Run `npx chant build` and `npx chant lint`, check that the built output has the same resources as before, and open a pull request.Never deploy to a real cloud account or shared environment (with `chant run`, `chant components` or a platform's own CLI), approve (a pull request review, `chant approve` or `chant run approve`), override a policy denial (`chant approve policy-override`), or merge; never cut or publish a release (`chant release cut --yes` or `chant release publish`), push or delete a `ci/*` tag (`chant ci tick` pushes them), or change the value of an existing flag; never touch `.chant/allowed_signers` or `chant/lifecycle`.A composite groups related resources into one reusable unit. Define them once, then instantiate the composite with props wherever you need them.
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 other resources can reference api.role and api.func.
Referencing Members
Section titled “Referencing Members”Within the factory, you can reference other members directly since they are variables in the same scope (role: role.arn in the definition above).
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:
- Several resources always appear together, such as a function that needs a role and a log destination
- Every resource name should derive from one
nameprop - The references between resources are complex enough to hide 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 where one source call becomes several resources. 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 flatten the composite, so the two options are to parameterize the field or to stop using the composite 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 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, whether the composite is your own or one a lexicon package publishes, 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.
Fields a Tagged Template Builds
Section titled “Fields a Tagged Template Builds”A factory can build a member with a registered tagged template instead of new Type({...}), such as the sql lexicon’s table`CREATE TABLE ${props.name} ... TTL ts + INTERVAL ${props.ttlDays} DAY`. There is no object literal to read field by field, because the tag parses its text into the entity’s fields. The tag reports instead which fields each interpolation landed in, and the build joins that with the parameters each ${...} reads. In the example name comes from name, ttl from ttlDays, and a clause written out in the text, such as the engine, is fixed by the composite. An interpolated sibling, view`... FROM ${events}`, is wiring inside the composite and governs nothing, the same as role: role.arn. A tag whose lexicon does not report its interpolations leaves the member’s fields unknown.
Kubernetes Composites
Section titled “Kubernetes Composites”The Kubernetes lexicon ships 27 production-ready composites across four categories.
Generic Composites
Section titled “Generic Composites”These 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. It is 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) can be referenced 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