@intentius/chant-lexicon-k8s 0.0.18 → 0.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/integrity.json +9 -4
  2. package/dist/manifest.json +1 -1
  3. package/dist/skills/chant-k8s-aks.md +146 -0
  4. package/{src/skills/kubernetes-patterns.md → dist/skills/chant-k8s-deployment-strategies.md} +1 -1
  5. package/dist/skills/chant-k8s-eks.md +156 -0
  6. package/dist/skills/chant-k8s-gke.md +246 -0
  7. package/{src/skills/kubernetes-security.md → dist/skills/chant-k8s-security.md} +1 -1
  8. package/dist/skills/chant-k8s.md +66 -3
  9. package/package.json +20 -2
  10. package/src/composites/adot-collector.ts +34 -22
  11. package/src/composites/agic-ingress.ts +14 -6
  12. package/src/composites/aks-external-dns-agent.ts +29 -18
  13. package/src/composites/alb-ingress.ts +14 -6
  14. package/src/composites/autoscaled-service.ts +25 -20
  15. package/src/composites/azure-disk-storage-class.ts +14 -6
  16. package/src/composites/azure-file-storage-class.ts +14 -6
  17. package/src/composites/azure-monitor-collector.ts +34 -22
  18. package/src/composites/batch-job.ts +25 -17
  19. package/src/composites/cockroachdb-cluster.ts +148 -58
  20. package/src/composites/composites.test.ts +369 -363
  21. package/src/composites/config-connector-context.ts +15 -8
  22. package/src/composites/configured-app.ts +21 -15
  23. package/src/composites/cron-workload.ts +25 -20
  24. package/src/composites/ebs-storage-class.ts +14 -6
  25. package/src/composites/efs-storage-class.ts +14 -6
  26. package/src/composites/external-dns-agent.ts +26 -20
  27. package/src/composites/filestore-storage-class.ts +14 -6
  28. package/src/composites/fluent-bit-agent.ts +30 -24
  29. package/src/composites/gce-ingress.ts +14 -6
  30. package/src/composites/gce-pd-storage-class.ts +14 -6
  31. package/src/composites/gke-external-dns-agent.ts +34 -21
  32. package/src/composites/gke-fluent-bit-agent.ts +34 -22
  33. package/src/composites/gke-gateway.ts +19 -12
  34. package/src/composites/gke-otel-collector.ts +34 -22
  35. package/src/composites/irsa-service-account.ts +22 -14
  36. package/src/composites/metrics-server.ts +41 -26
  37. package/src/composites/monitored-service.ts +26 -19
  38. package/src/composites/namespace-env.ts +26 -17
  39. package/src/composites/network-isolated-app.ts +21 -16
  40. package/src/composites/node-agent.ts +33 -22
  41. package/src/composites/secure-ingress.ts +19 -11
  42. package/src/composites/sidecar-app.ts +17 -12
  43. package/src/composites/stateful-app.ts +21 -12
  44. package/src/composites/web-app.ts +25 -21
  45. package/src/composites/worker-pool.ts +40 -26
  46. package/src/composites/workload-identity-sa.ts +22 -14
  47. package/src/composites/workload-identity-service-account.ts +22 -16
  48. package/src/plugin.ts +130 -614
  49. package/src/serializer.ts +3 -0
  50. package/src/skills/chant-k8s-deployment-strategies.md +183 -0
  51. package/src/skills/chant-k8s-gke.md +55 -0
  52. package/src/skills/chant-k8s-patterns.md +245 -0
  53. package/src/skills/chant-k8s-security.md +237 -0
  54. package/src/skills/chant-k8s.md +305 -0
@@ -8,7 +8,7 @@ user-invocable: true
8
8
 
9
9
  ## How chant and Kubernetes relate
10
10
 
11
- chant is a **synthesis-only** tool — it compiles TypeScript source files into Kubernetes YAML manifests. chant does NOT call the Kubernetes API. Your job as an agent is to bridge the two:
11
+ chant is a **synthesis compiler** — it compiles TypeScript source files into Kubernetes YAML manifests. `chant build` does not call the Kubernetes API; synthesis is pure and deterministic. The optional `chant state snapshot` command queries the Kubernetes API to capture deployment metadata (pod names, status, UIDs) for observability. Your job as an agent is to bridge synthesis and deployment:
12
12
 
13
13
  - Use **chant** for: build, lint, diff (local YAML comparison)
14
14
  - Use **kubectl / k8s API** for: apply, rollback, monitoring, troubleshooting
@@ -152,7 +152,7 @@ kubectl apply -f manifests.yaml --dry-run=client
152
152
 
153
153
  Composites are higher-level functions that produce multiple coordinated K8s resources from a single call. They return plain prop objects — not class instances.
154
154
 
155
- ### Decision Tree
155
+ ### Decision Tree — Core Composites
156
156
 
157
157
  | Need | Composite | Resources |
158
158
  |------|-----------|-----------|
@@ -169,6 +169,13 @@ Composites are higher-level functions that produce multiple coordinated K8s reso
169
169
  | Namespace with quotas and isolation | **NamespaceEnv** | Namespace + ResourceQuota + LimitRange + NetworkPolicy |
170
170
  | Per-node agent (custom) | **NodeAgent** | DaemonSet + RBAC + optional ConfigMap |
171
171
  | Multi-host TLS Ingress (cert-manager) | **SecureIngress** | Ingress + optional Certificate |
172
+ | HPA metrics provider | **MetricsServer** | Deployment + Service + RBAC + APIService |
173
+ | CockroachDB cluster | **CockroachDbCluster** | StatefulSet + Services + PVCs + RBAC + optional CertificateRequests |
174
+
175
+ ### Decision Tree — AWS (EKS) Composites
176
+
177
+ | Need | Composite | Resources |
178
+ |------|-----------|-----------|
172
179
  | EKS IRSA ServiceAccount | **IrsaServiceAccount** | ServiceAccount + optional RBAC |
173
180
  | AWS ALB Ingress | **AlbIngress** | Ingress with ALB annotations |
174
181
  | EBS StorageClass | **EbsStorageClass** | StorageClass (ebs.csi.aws.com) |
@@ -177,6 +184,30 @@ Composites are higher-level functions that produce multiple coordinated K8s reso
177
184
  | ExternalDNS for Route53 | **ExternalDnsAgent** | Deployment + IRSA SA + ClusterRole |
178
185
  | ADOT for CloudWatch/X-Ray | **AdotCollector** | DaemonSet + RBAC + ConfigMap |
179
186
 
187
+ ### Decision Tree — GCP (GKE) Composites
188
+
189
+ | Need | Composite | Resources |
190
+ |------|-----------|-----------|
191
+ | GKE Gateway API Ingress | **GkeGateway** | Gateway + HTTPRoute + optional HealthCheckPolicy |
192
+ | GKE Ingress (classic) | **GceIngress** | Ingress with GCE annotations + optional BackendConfig |
193
+ | GKE Workload Identity SA | **WorkloadIdentityServiceAccount** | ServiceAccount + optional RBAC (GCP IAM binding annotation) |
194
+ | GCE Persistent Disk StorageClass | **GcePdStorageClass** | StorageClass (pd.csi.storage.gke.io) |
195
+ | Filestore StorageClass | **FilestoreStorageClass** | StorageClass (filestore.csi.storage.gke.io) |
196
+ | GKE Fluent Bit agent | **GkeFluentBitAgent** | DaemonSet + RBAC + ConfigMap (Cloud Logging) |
197
+ | GKE ExternalDNS agent | **GkeExternalDnsAgent** | Deployment + WI SA + ClusterRole (Cloud DNS) |
198
+ | GKE OpenTelemetry Collector | **GkeOtelCollector** | DaemonSet + RBAC + ConfigMap (Cloud Trace/Monitoring) |
199
+ | Config Connector context | **ConfigConnectorContext** | ConfigConnectorContext + Namespace + RBAC |
200
+
201
+ ### Decision Tree — Azure (AKS) Composites
202
+
203
+ | Need | Composite | Resources |
204
+ |------|-----------|-----------|
205
+ | AKS AGIC Ingress | **AgicIngress** | Ingress with AGIC annotations + optional BackendConfig |
206
+ | Azure Disk StorageClass | **AzureDiskStorageClass** | StorageClass (disk.csi.azure.com) |
207
+ | Azure File StorageClass | **AzureFileStorageClass** | StorageClass (file.csi.azure.com) |
208
+ | AKS ExternalDNS agent | **AksExternalDnsAgent** | Deployment + WI SA + ClusterRole (Azure DNS) |
209
+ | Azure Monitor Collector | **AzureMonitorCollector** | DaemonSet + RBAC + ConfigMap (Azure Monitor) |
210
+
180
211
  ### Hardening options (available on Deployment-based composites)
181
212
 
182
213
  - `minAvailable` — creates a PodDisruptionBudget (WebApp, StatefulApp, WorkerPool; AutoscaledService always has one)
@@ -199,14 +230,46 @@ Composites are higher-level functions that produce multiple coordinated K8s reso
199
230
  | Pod stuck in Pending | Insufficient CPU/memory on nodes | Scale up cluster or reduce resource requests |
200
231
  | Pod stuck in Pending | PVC not bound | Check StorageClass exists, PV available |
201
232
  | Pod stuck in Pending | Node selector/affinity mismatch | Verify node labels match selectors |
233
+ | Pod stuck in Pending | Too many pods on node | Check `kubectl describe node` for Allocatable vs Allocated |
202
234
  | Pod stuck in ContainerCreating | ConfigMap/Secret not found | Ensure referenced ConfigMaps/Secrets exist |
235
+ | Pod stuck in ContainerCreating | Volume mount timeout | Check CSI driver logs, storage provider status |
203
236
  | Service returns 503 | No ready endpoints | Check pod readiness probes, selector match |
237
+ | Service returns 503 | Endpoints exist but pod failing probes | Increase `initialDelaySeconds`, check probe path |
204
238
  | Ingress returns 404 | Backend service not found | Check Ingress rules, service name/port |
205
- | HPA not scaling | Metrics server not installed | Install metrics-server |
239
+ | Ingress returns 502 | Backend pods not ready | Check pod readiness, container port matches Service targetPort |
240
+ | HPA not scaling | Metrics server not installed | Install metrics-server or MetricsServer composite |
206
241
  | HPA not scaling | Resource requests not set | Add CPU/memory requests to containers |
242
+ | HPA stuck at max | Target utilization too low | Raise `targetCPUUtilizationPercentage`, add nodes |
207
243
  | CronJob not running | Invalid cron expression | Validate cron syntax (5-field format) |
244
+ | CronJob not running | `concurrencyPolicy: Forbid` with long jobs | Increase job deadline or switch to `Replace` |
208
245
  | NetworkPolicy blocking | Default deny applied | Add explicit allow rules for required traffic |
209
246
  | RBAC permission denied | Missing Role/RoleBinding | Check ServiceAccount bindings and verb permissions |
247
+ | RBAC permission denied | ClusterRole vs namespaced Role | Use ClusterRoleBinding for cluster-scoped resources |
248
+ | PDB preventing eviction | minAvailable too high | Lower minAvailable or increase replicas |
249
+ | StatefulSet stuck on update | Pod ordinal blocked | Check PVC status for ordinal, delete stuck pod |
250
+
251
+ ## Troubleshooting decision tree
252
+
253
+ ```
254
+ Pod not running?
255
+ ├─ Pending
256
+ │ ├─ "Insufficient cpu/memory" → scale cluster or lower requests
257
+ │ ├─ "no nodes match" → fix nodeSelector / tolerations
258
+ │ └─ "unbound PVC" → check StorageClass, provision PV
259
+ ├─ ContainerCreating
260
+ │ ├─ "secret not found" → create missing Secret
261
+ │ └─ "timeout waiting for volume" → check CSI driver
262
+ ├─ CrashLoopBackOff
263
+ │ ├─ OOMKilled → increase memory limit
264
+ │ ├─ exit code 1 → check app logs (`kubectl logs --previous`)
265
+ │ └─ exit code 137 → SIGKILL — liveness probe too aggressive
266
+ ├─ ImagePullBackOff
267
+ │ ├─ 401 Unauthorized → check imagePullSecrets
268
+ │ └─ manifest unknown → verify image:tag exists in registry
269
+ └─ Running but not Ready
270
+ ├─ readinessProbe failing → check probe path/port
271
+ └─ startup probe failing → increase `failureThreshold * periodSeconds`
272
+ ```
210
273
 
211
274
  ## Quick reference
212
275
 
package/package.json CHANGED
@@ -1,7 +1,25 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-k8s",
3
- "version": "0.0.18",
3
+ "version": "0.0.24",
4
+ "description": "Kubernetes lexicon for chant — declarative IaC in TypeScript",
4
5
  "license": "Apache-2.0",
6
+ "homepage": "https://intentius.io/chant",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/intentius/chant.git",
10
+ "directory": "lexicons/k8s"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/intentius/chant/issues"
14
+ },
15
+ "keywords": [
16
+ "infrastructure-as-code",
17
+ "iac",
18
+ "typescript",
19
+ "kubernetes",
20
+ "k8s",
21
+ "chant"
22
+ ],
5
23
  "type": "module",
6
24
  "files": [
7
25
  "src/",
@@ -25,7 +43,7 @@
25
43
  "prepack": "bun run generate && bun run bundle && bun run validate"
26
44
  },
27
45
  "dependencies": {
28
- "@intentius/chant": "0.0.18"
46
+ "@intentius/chant": "0.0.22"
29
47
  },
30
48
  "devDependencies": {
31
49
  "typescript": "^5.9.3"
@@ -5,6 +5,9 @@
5
5
  * with pre-configured pipelines for AWS observability.
6
6
  */
7
7
 
8
+ import { Composite, mergeDefaults } from "@intentius/chant";
9
+ import { DaemonSet, ServiceAccount, ClusterRole, ClusterRoleBinding, ConfigMap } from "../generated";
10
+
8
11
  export interface AdotCollectorProps {
9
12
  /** AWS region. */
10
13
  region: string;
@@ -30,14 +33,22 @@ export interface AdotCollectorProps {
30
33
  memoryLimit?: string;
31
34
  /** IAM Role ARN for IRSA (adds eks.amazonaws.com/role-arn annotation to ServiceAccount). */
32
35
  iamRoleArn?: string;
36
+ /** Per-member defaults for fine-grained overrides. */
37
+ defaults?: {
38
+ daemonSet?: Partial<Record<string, unknown>>;
39
+ serviceAccount?: Partial<Record<string, unknown>>;
40
+ clusterRole?: Partial<Record<string, unknown>>;
41
+ clusterRoleBinding?: Partial<Record<string, unknown>>;
42
+ configMap?: Partial<Record<string, unknown>>;
43
+ };
33
44
  }
34
45
 
35
46
  export interface AdotCollectorResult {
36
- daemonSet: Record<string, unknown>;
37
- serviceAccount: Record<string, unknown>;
38
- clusterRole: Record<string, unknown>;
39
- clusterRoleBinding: Record<string, unknown>;
40
- configMap: Record<string, unknown>;
47
+ daemonSet: InstanceType<typeof DaemonSet>;
48
+ serviceAccount: InstanceType<typeof ServiceAccount>;
49
+ clusterRole: InstanceType<typeof ClusterRole>;
50
+ clusterRoleBinding: InstanceType<typeof ClusterRoleBinding>;
51
+ configMap: InstanceType<typeof ConfigMap>;
41
52
  }
42
53
 
43
54
  /**
@@ -56,7 +67,7 @@ export interface AdotCollectorResult {
56
67
  * });
57
68
  * ```
58
69
  */
59
- export function AdotCollector(props: AdotCollectorProps): AdotCollectorResult {
70
+ export const AdotCollector = Composite<AdotCollectorProps>((props) => {
60
71
  const {
61
72
  region,
62
73
  clusterName,
@@ -70,6 +81,7 @@ export function AdotCollector(props: AdotCollectorProps): AdotCollectorResult {
70
81
  cpuLimit = "500m",
71
82
  memoryLimit = "512Mi",
72
83
  iamRoleArn,
84
+ defaults: defs,
73
85
  } = props;
74
86
 
75
87
  const saName = `${name}-sa`;
@@ -159,7 +171,7 @@ service:
159
171
  },
160
172
  };
161
173
 
162
- const daemonSetProps: Record<string, unknown> = {
174
+ const daemonSet = new DaemonSet(mergeDefaults({
163
175
  metadata: {
164
176
  name,
165
177
  namespace,
@@ -179,18 +191,18 @@ service:
179
191
  },
180
192
  },
181
193
  },
182
- };
194
+ }, defs?.daemonSet));
183
195
 
184
- const serviceAccountProps: Record<string, unknown> = {
196
+ const serviceAccount = new ServiceAccount(mergeDefaults({
185
197
  metadata: {
186
198
  name: saName,
187
199
  namespace,
188
200
  labels: { ...commonLabels, "app.kubernetes.io/component": "agent" },
189
201
  ...(iamRoleArn ? { annotations: { "eks.amazonaws.com/role-arn": iamRoleArn } } : {}),
190
202
  },
191
- };
203
+ }, defs?.serviceAccount));
192
204
 
193
- const clusterRoleProps: Record<string, unknown> = {
205
+ const clusterRole = new ClusterRole(mergeDefaults({
194
206
  metadata: {
195
207
  name: clusterRoleName,
196
208
  labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
@@ -203,9 +215,9 @@ service:
203
215
  { apiGroups: [""], resources: ["nodes/stats", "configmaps", "events"], verbs: ["create", "get"] },
204
216
  { apiGroups: [""], resources: ["configmaps"], verbs: ["get", "update", "create"], resourceNames: ["otel-container-insight-clusterleader"] },
205
217
  ],
206
- };
218
+ }, defs?.clusterRole));
207
219
 
208
- const clusterRoleBindingProps: Record<string, unknown> = {
220
+ const clusterRoleBinding = new ClusterRoleBinding(mergeDefaults({
209
221
  metadata: {
210
222
  name: bindingName,
211
223
  labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
@@ -222,9 +234,9 @@ service:
222
234
  namespace,
223
235
  },
224
236
  ],
225
- };
237
+ }, defs?.clusterRoleBinding));
226
238
 
227
- const configMapProps: Record<string, unknown> = {
239
+ const configMap = new ConfigMap(mergeDefaults({
228
240
  metadata: {
229
241
  name: configMapName,
230
242
  namespace,
@@ -233,13 +245,13 @@ service:
233
245
  data: {
234
246
  "config.yaml": adotConfig,
235
247
  },
236
- };
248
+ }, defs?.configMap));
237
249
 
238
250
  return {
239
- daemonSet: daemonSetProps,
240
- serviceAccount: serviceAccountProps,
241
- clusterRole: clusterRoleProps,
242
- clusterRoleBinding: clusterRoleBindingProps,
243
- configMap: configMapProps,
251
+ daemonSet,
252
+ serviceAccount,
253
+ clusterRole,
254
+ clusterRoleBinding,
255
+ configMap,
244
256
  };
245
- }
257
+ }, "AdotCollector");
@@ -5,6 +5,9 @@
5
5
  * WAF policy, backend path prefix, and cookie-based affinity.
6
6
  */
7
7
 
8
+ import { Composite, mergeDefaults } from "@intentius/chant";
9
+ import { Ingress } from "../generated";
10
+
8
11
  export interface AgicIngressHost {
9
12
  /** Hostname (e.g., "api.example.com"). */
10
13
  hostname: string;
@@ -40,10 +43,14 @@ export interface AgicIngressProps {
40
43
  labels?: Record<string, string>;
41
44
  /** Namespace for all resources. */
42
45
  namespace?: string;
46
+ /** Per-member defaults for fine-grained overrides. */
47
+ defaults?: {
48
+ ingress?: Partial<Record<string, unknown>>;
49
+ };
43
50
  }
44
51
 
45
52
  export interface AgicIngressResult {
46
- ingress: Record<string, unknown>;
53
+ ingress: InstanceType<typeof Ingress>;
47
54
  }
48
55
 
49
56
  /**
@@ -68,7 +75,7 @@ export interface AgicIngressResult {
68
75
  * });
69
76
  * ```
70
77
  */
71
- export function AgicIngress(props: AgicIngressProps): AgicIngressResult {
78
+ export const AgicIngress = Composite<AgicIngressProps>((props) => {
72
79
  const {
73
80
  name,
74
81
  hosts,
@@ -81,6 +88,7 @@ export function AgicIngress(props: AgicIngressProps): AgicIngressResult {
81
88
  annotations: extraAnnotations = {},
82
89
  labels: extraLabels = {},
83
90
  namespace,
91
+ defaults: defs,
84
92
  } = props;
85
93
 
86
94
  const commonLabels: Record<string, string> = {
@@ -132,7 +140,7 @@ export function AgicIngress(props: AgicIngressProps): AgicIngressResult {
132
140
  },
133
141
  }));
134
142
 
135
- const ingressProps: Record<string, unknown> = {
143
+ const ingress = new Ingress(mergeDefaults({
136
144
  metadata: {
137
145
  name,
138
146
  ...(namespace && { namespace }),
@@ -142,7 +150,7 @@ export function AgicIngress(props: AgicIngressProps): AgicIngressResult {
142
150
  spec: {
143
151
  rules: ingressRules,
144
152
  },
145
- };
153
+ }, defs?.ingress));
146
154
 
147
- return { ingress: ingressProps };
148
- }
155
+ return { ingress };
156
+ }, "AgicIngress");
@@ -5,6 +5,9 @@
5
5
  * instead of IRSA for Azure DNS management.
6
6
  */
7
7
 
8
+ import { Composite, mergeDefaults } from "@intentius/chant";
9
+ import { Deployment, ServiceAccount, ClusterRole, ClusterRoleBinding } from "../generated";
10
+
8
11
  export interface AksExternalDnsAgentProps {
9
12
  /** Azure managed identity client ID for Workload Identity. */
10
13
  clientId: string;
@@ -28,13 +31,20 @@ export interface AksExternalDnsAgentProps {
28
31
  namespace?: string;
29
32
  /** Additional labels. */
30
33
  labels?: Record<string, string>;
34
+ /** Per-member defaults for fine-grained overrides. */
35
+ defaults?: {
36
+ deployment?: Partial<Record<string, unknown>>;
37
+ serviceAccount?: Partial<Record<string, unknown>>;
38
+ clusterRole?: Partial<Record<string, unknown>>;
39
+ clusterRoleBinding?: Partial<Record<string, unknown>>;
40
+ };
31
41
  }
32
42
 
33
43
  export interface AksExternalDnsAgentResult {
34
- deployment: Record<string, unknown>;
35
- serviceAccount: Record<string, unknown>;
36
- clusterRole: Record<string, unknown>;
37
- clusterRoleBinding: Record<string, unknown>;
44
+ deployment: InstanceType<typeof Deployment>;
45
+ serviceAccount: InstanceType<typeof ServiceAccount>;
46
+ clusterRole: InstanceType<typeof ClusterRole>;
47
+ clusterRoleBinding: InstanceType<typeof ClusterRoleBinding>;
38
48
  }
39
49
 
40
50
  /**
@@ -56,7 +66,7 @@ export interface AksExternalDnsAgentResult {
56
66
  * });
57
67
  * ```
58
68
  */
59
- export function AksExternalDnsAgent(props: AksExternalDnsAgentProps): AksExternalDnsAgentResult {
69
+ export const AksExternalDnsAgent = Composite<AksExternalDnsAgentProps>((props) => {
60
70
  const {
61
71
  clientId,
62
72
  resourceGroup,
@@ -69,6 +79,7 @@ export function AksExternalDnsAgent(props: AksExternalDnsAgentProps): AksExterna
69
79
  image = "registry.k8s.io/external-dns/external-dns:v0.14.0",
70
80
  namespace = "kube-system",
71
81
  labels: extraLabels = {},
82
+ defaults: defs,
72
83
  } = props;
73
84
 
74
85
  const saName = `${name}-sa`;
@@ -98,7 +109,7 @@ export function AksExternalDnsAgent(props: AksExternalDnsAgentProps): AksExterna
98
109
  args.push(`--txt-owner-id=${txtOwnerId}`);
99
110
  }
100
111
 
101
- const deploymentProps: Record<string, unknown> = {
112
+ const deployment = new Deployment(mergeDefaults({
102
113
  metadata: {
103
114
  name,
104
115
  namespace,
@@ -142,9 +153,9 @@ export function AksExternalDnsAgent(props: AksExternalDnsAgentProps): AksExterna
142
153
  },
143
154
  },
144
155
  },
145
- };
156
+ }, defs?.deployment));
146
157
 
147
- const serviceAccountProps: Record<string, unknown> = {
158
+ const serviceAccount = new ServiceAccount(mergeDefaults({
148
159
  metadata: {
149
160
  name: saName,
150
161
  namespace,
@@ -157,9 +168,9 @@ export function AksExternalDnsAgent(props: AksExternalDnsAgentProps): AksExterna
157
168
  "azure.workload.identity/client-id": clientId,
158
169
  },
159
170
  },
160
- };
171
+ }, defs?.serviceAccount));
161
172
 
162
- const clusterRoleProps: Record<string, unknown> = {
173
+ const clusterRole = new ClusterRole(mergeDefaults({
163
174
  metadata: {
164
175
  name: clusterRoleName,
165
176
  labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
@@ -169,9 +180,9 @@ export function AksExternalDnsAgent(props: AksExternalDnsAgentProps): AksExterna
169
180
  { apiGroups: ["extensions", "networking.k8s.io"], resources: ["ingresses"], verbs: ["get", "watch", "list"] },
170
181
  { apiGroups: [""], resources: ["nodes"], verbs: ["list", "watch"] },
171
182
  ],
172
- };
183
+ }, defs?.clusterRole));
173
184
 
174
- const clusterRoleBindingProps: Record<string, unknown> = {
185
+ const clusterRoleBinding = new ClusterRoleBinding(mergeDefaults({
175
186
  metadata: {
176
187
  name: bindingName,
177
188
  labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
@@ -188,12 +199,12 @@ export function AksExternalDnsAgent(props: AksExternalDnsAgentProps): AksExterna
188
199
  namespace,
189
200
  },
190
201
  ],
191
- };
202
+ }, defs?.clusterRoleBinding));
192
203
 
193
204
  return {
194
- deployment: deploymentProps,
195
- serviceAccount: serviceAccountProps,
196
- clusterRole: clusterRoleProps,
197
- clusterRoleBinding: clusterRoleBindingProps,
205
+ deployment,
206
+ serviceAccount,
207
+ clusterRole,
208
+ clusterRoleBinding,
198
209
  };
199
- }
210
+ }, "AksExternalDnsAgent");
@@ -5,6 +5,9 @@
5
5
  * (shared ALB), SSL redirect, subnets, security groups.
6
6
  */
7
7
 
8
+ import { Composite, mergeDefaults } from "@intentius/chant";
9
+ import { Ingress } from "../generated";
10
+
8
11
  export interface AlbIngressHost {
9
12
  /** Hostname (e.g., "api.example.com"). */
10
13
  hostname: string;
@@ -43,10 +46,14 @@ export interface AlbIngressProps {
43
46
  labels?: Record<string, string>;
44
47
  /** Namespace for all resources. */
45
48
  namespace?: string;
49
+ /** Per-member defaults for fine-grained overrides. */
50
+ defaults?: {
51
+ ingress?: Partial<Record<string, unknown>>;
52
+ };
46
53
  }
47
54
 
48
55
  export interface AlbIngressResult {
49
- ingress: Record<string, unknown>;
56
+ ingress: InstanceType<typeof Ingress>;
50
57
  }
51
58
 
52
59
  /**
@@ -72,7 +79,7 @@ export interface AlbIngressResult {
72
79
  * });
73
80
  * ```
74
81
  */
75
- export function AlbIngress(props: AlbIngressProps): AlbIngressResult {
82
+ export const AlbIngress = Composite<AlbIngressProps>((props) => {
76
83
  const {
77
84
  name,
78
85
  hosts,
@@ -86,6 +93,7 @@ export function AlbIngress(props: AlbIngressProps): AlbIngressResult {
86
93
  annotations: extraAnnotations = {},
87
94
  labels: extraLabels = {},
88
95
  namespace,
96
+ defaults: defs,
89
97
  } = props;
90
98
 
91
99
  const commonLabels: Record<string, string> = {
@@ -135,7 +143,7 @@ export function AlbIngress(props: AlbIngressProps): AlbIngressResult {
135
143
  },
136
144
  }));
137
145
 
138
- const ingressProps: Record<string, unknown> = {
146
+ const ingress = new Ingress(mergeDefaults({
139
147
  metadata: {
140
148
  name,
141
149
  ...(namespace && { namespace }),
@@ -146,7 +154,7 @@ export function AlbIngress(props: AlbIngressProps): AlbIngressResult {
146
154
  ingressClassName: "alb",
147
155
  rules: ingressRules,
148
156
  },
149
- };
157
+ }, defs?.ingress));
150
158
 
151
- return { ingress: ingressProps };
152
- }
159
+ return { ingress };
160
+ }, "AlbIngress");
@@ -6,6 +6,8 @@
6
6
  * PDB selectors match pod labels, and resource requests are set.
7
7
  */
8
8
 
9
+ import { Composite, mergeDefaults } from "@intentius/chant";
10
+ import { Deployment, Service, HorizontalPodAutoscaler, PodDisruptionBudget } from "../generated";
9
11
  import type { ContainerSecurityContext } from "./security-context";
10
12
 
11
13
  export interface AutoscaledServiceProps {
@@ -69,13 +71,20 @@ export interface AutoscaledServiceProps {
69
71
  volumeMounts?: Array<Record<string, unknown>>;
70
72
  /** Convenience: auto-generate emptyDir volumes + mounts for writable temp dirs (e.g. ["/tmp", "/var/cache/nginx"]). */
71
73
  tmpDirs?: string[];
74
+ /** Per-member defaults for fine-grained overrides. */
75
+ defaults?: {
76
+ deployment?: Partial<Record<string, unknown>>;
77
+ service?: Partial<Record<string, unknown>>;
78
+ hpa?: Partial<Record<string, unknown>>;
79
+ pdb?: Partial<Record<string, unknown>>;
80
+ };
72
81
  }
73
82
 
74
83
  export interface AutoscaledServiceResult {
75
- deployment: Record<string, unknown>;
76
- service: Record<string, unknown>;
77
- hpa: Record<string, unknown>;
78
- pdb: Record<string, unknown>;
84
+ deployment: InstanceType<typeof Deployment>;
85
+ service: InstanceType<typeof Service>;
86
+ hpa: InstanceType<typeof HorizontalPodAutoscaler>;
87
+ pdb: InstanceType<typeof PodDisruptionBudget>;
79
88
  }
80
89
 
81
90
  /**
@@ -95,7 +104,7 @@ export interface AutoscaledServiceResult {
95
104
  * });
96
105
  * ```
97
106
  */
98
- export function AutoscaledService(props: AutoscaledServiceProps): AutoscaledServiceResult {
107
+ export const AutoscaledService = Composite<AutoscaledServiceProps>((props) => {
99
108
  const {
100
109
  name,
101
110
  image,
@@ -123,6 +132,7 @@ export function AutoscaledService(props: AutoscaledServiceProps): AutoscaledServ
123
132
  volumes: explicitVolumes = [],
124
133
  volumeMounts: explicitMounts = [],
125
134
  tmpDirs = [],
135
+ defaults: defs,
126
136
  } = props;
127
137
 
128
138
  const commonLabels: Record<string, string> = {
@@ -164,7 +174,7 @@ export function AutoscaledService(props: AutoscaledServiceProps): AutoscaledServ
164
174
  : {}),
165
175
  };
166
176
 
167
- const deploymentProps: Record<string, unknown> = {
177
+ const deployment = new Deployment(mergeDefaults({
168
178
  metadata: {
169
179
  name,
170
180
  ...(namespace && { namespace }),
@@ -213,9 +223,9 @@ export function AutoscaledService(props: AutoscaledServiceProps): AutoscaledServ
213
223
  },
214
224
  },
215
225
  },
216
- };
226
+ }, defs?.deployment));
217
227
 
218
- const serviceProps: Record<string, unknown> = {
228
+ const service = new Service(mergeDefaults({
219
229
  metadata: {
220
230
  name,
221
231
  ...(namespace && { namespace }),
@@ -226,7 +236,7 @@ export function AutoscaledService(props: AutoscaledServiceProps): AutoscaledServ
226
236
  ports: [{ port: 80, targetPort: port, protocol: "TCP", name: "http" }],
227
237
  type: "ClusterIP",
228
238
  },
229
- };
239
+ }, defs?.service));
230
240
 
231
241
  const metrics: Array<Record<string, unknown>> = [
232
242
  {
@@ -248,7 +258,7 @@ export function AutoscaledService(props: AutoscaledServiceProps): AutoscaledServ
248
258
  });
249
259
  }
250
260
 
251
- const hpaProps: Record<string, unknown> = {
261
+ const hpa = new HorizontalPodAutoscaler(mergeDefaults({
252
262
  metadata: {
253
263
  name,
254
264
  ...(namespace && { namespace }),
@@ -264,9 +274,9 @@ export function AutoscaledService(props: AutoscaledServiceProps): AutoscaledServ
264
274
  maxReplicas,
265
275
  metrics,
266
276
  },
267
- };
277
+ }, defs?.hpa));
268
278
 
269
- const pdbProps: Record<string, unknown> = {
279
+ const pdb = new PodDisruptionBudget(mergeDefaults({
270
280
  metadata: {
271
281
  name,
272
282
  ...(namespace && { namespace }),
@@ -276,12 +286,7 @@ export function AutoscaledService(props: AutoscaledServiceProps): AutoscaledServ
276
286
  minAvailable,
277
287
  selector: { matchLabels: { "app.kubernetes.io/name": name } },
278
288
  },
279
- };
289
+ }, defs?.pdb));
280
290
 
281
- return {
282
- deployment: deploymentProps,
283
- service: serviceProps,
284
- hpa: hpaProps,
285
- pdb: pdbProps,
286
- };
287
- }
291
+ return { deployment, service, hpa, pdb };
292
+ }, "AutoscaledService");