@intentius/chant-lexicon-k8s 0.0.22 → 0.1.0

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 +65 -2
  9. package/package.json +5 -4
  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 +164 -58
  20. package/src/composites/composites.test.ts +371 -365
  21. package/src/composites/config-connector-context.ts +18 -11
  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 +40 -614
  49. package/src/serializer.ts +7 -0
  50. package/src/skills/chant-k8s-deployment-strategies.md +183 -0
  51. package/src/skills/chant-k8s-gke.md +56 -1
  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
@@ -5,6 +5,8 @@
5
5
  * Creates fine-grained ingress/egress policies for a single application.
6
6
  */
7
7
 
8
+ import { Composite, mergeDefaults } from "@intentius/chant";
9
+ import { Deployment, Service, NetworkPolicy } from "../generated";
8
10
  import type { ContainerSecurityContext } from "./security-context";
9
11
 
10
12
  export interface NetworkPolicyPeer {
@@ -48,12 +50,18 @@ export interface NetworkIsolatedAppProps {
48
50
  env?: Array<{ name: string; value: string }>;
49
51
  /** Container security context (supports PSS restricted fields). */
50
52
  securityContext?: ContainerSecurityContext;
53
+ /** Per-member defaults for fine-grained overrides. */
54
+ defaults?: {
55
+ deployment?: Partial<Record<string, unknown>>;
56
+ service?: Partial<Record<string, unknown>>;
57
+ networkPolicy?: Partial<Record<string, unknown>>;
58
+ };
51
59
  }
52
60
 
53
61
  export interface NetworkIsolatedAppResult {
54
- deployment: Record<string, unknown>;
55
- service: Record<string, unknown>;
56
- networkPolicy: Record<string, unknown>;
62
+ deployment: InstanceType<typeof Deployment>;
63
+ service: InstanceType<typeof Service>;
64
+ networkPolicy: InstanceType<typeof NetworkPolicy>;
57
65
  }
58
66
 
59
67
  /**
@@ -77,7 +85,7 @@ export interface NetworkIsolatedAppResult {
77
85
  * });
78
86
  * ```
79
87
  */
80
- export function NetworkIsolatedApp(props: NetworkIsolatedAppProps): NetworkIsolatedAppResult {
88
+ export const NetworkIsolatedApp = Composite<NetworkIsolatedAppProps>((props) => {
81
89
  const {
82
90
  name,
83
91
  image,
@@ -93,6 +101,7 @@ export function NetworkIsolatedApp(props: NetworkIsolatedAppProps): NetworkIsola
93
101
  namespace,
94
102
  env,
95
103
  securityContext,
104
+ defaults: defs,
96
105
  } = props;
97
106
 
98
107
  const commonLabels: Record<string, string> = {
@@ -101,7 +110,7 @@ export function NetworkIsolatedApp(props: NetworkIsolatedAppProps): NetworkIsola
101
110
  ...extraLabels,
102
111
  };
103
112
 
104
- const deploymentProps: Record<string, unknown> = {
113
+ const deployment = new Deployment(mergeDefaults({
105
114
  metadata: {
106
115
  name,
107
116
  ...(namespace && { namespace }),
@@ -129,9 +138,9 @@ export function NetworkIsolatedApp(props: NetworkIsolatedAppProps): NetworkIsola
129
138
  },
130
139
  },
131
140
  },
132
- };
141
+ }, defs?.deployment));
133
142
 
134
- const serviceProps: Record<string, unknown> = {
143
+ const service = new Service(mergeDefaults({
135
144
  metadata: {
136
145
  name,
137
146
  ...(namespace && { namespace }),
@@ -142,7 +151,7 @@ export function NetworkIsolatedApp(props: NetworkIsolatedAppProps): NetworkIsola
142
151
  ports: [{ port: 80, targetPort: port, protocol: "TCP", name: "http" }],
143
152
  type: "ClusterIP",
144
153
  },
145
- };
154
+ }, defs?.service));
146
155
 
147
156
  // Build network policy
148
157
  const policyTypes: string[] = [];
@@ -185,18 +194,14 @@ export function NetworkIsolatedApp(props: NetworkIsolatedAppProps): NetworkIsola
185
194
  policySpec.policyTypes = policyTypes;
186
195
  }
187
196
 
188
- const networkPolicyProps: Record<string, unknown> = {
197
+ const networkPolicy = new NetworkPolicy(mergeDefaults({
189
198
  metadata: {
190
199
  name: `${name}-policy`,
191
200
  ...(namespace && { namespace }),
192
201
  labels: { ...commonLabels, "app.kubernetes.io/component": "network-policy" },
193
202
  },
194
203
  spec: policySpec,
195
- };
204
+ }, defs?.networkPolicy));
196
205
 
197
- return {
198
- deployment: deploymentProps,
199
- service: serviceProps,
200
- networkPolicy: networkPolicyProps,
201
- };
202
- }
206
+ return { deployment, service, networkPolicy };
207
+ }, "NetworkIsolatedApp");
@@ -5,6 +5,8 @@
5
5
  * security scanners) that need cluster-wide RBAC and tolerations.
6
6
  */
7
7
 
8
+ import { Composite, mergeDefaults } from "@intentius/chant";
9
+ import { DaemonSet, ServiceAccount, ClusterRole, ClusterRoleBinding, ConfigMap } from "../generated";
8
10
  import type { ContainerSecurityContext } from "./security-context";
9
11
 
10
12
  export interface NodeAgentProps {
@@ -47,14 +49,22 @@ export interface NodeAgentProps {
47
49
  env?: Array<{ name: string; value: string }>;
48
50
  /** Container security context (supports PSS restricted fields). */
49
51
  securityContext?: ContainerSecurityContext;
52
+ /** Per-member defaults for fine-grained overrides. */
53
+ defaults?: {
54
+ daemonSet?: Partial<Record<string, unknown>>;
55
+ serviceAccount?: Partial<Record<string, unknown>>;
56
+ clusterRole?: Partial<Record<string, unknown>>;
57
+ clusterRoleBinding?: Partial<Record<string, unknown>>;
58
+ configMap?: Partial<Record<string, unknown>>;
59
+ };
50
60
  }
51
61
 
52
62
  export interface NodeAgentResult {
53
- daemonSet: Record<string, unknown>;
54
- serviceAccount: Record<string, unknown>;
55
- clusterRole: Record<string, unknown>;
56
- clusterRoleBinding: Record<string, unknown>;
57
- configMap?: Record<string, unknown>;
63
+ daemonSet: InstanceType<typeof DaemonSet>;
64
+ serviceAccount: InstanceType<typeof ServiceAccount>;
65
+ clusterRole: InstanceType<typeof ClusterRole>;
66
+ clusterRoleBinding: InstanceType<typeof ClusterRoleBinding>;
67
+ configMap?: InstanceType<typeof ConfigMap>;
58
68
  }
59
69
 
60
70
  /**
@@ -75,7 +85,7 @@ export interface NodeAgentResult {
75
85
  * });
76
86
  * ```
77
87
  */
78
- export function NodeAgent(props: NodeAgentProps): NodeAgentResult {
88
+ export const NodeAgent = Composite<NodeAgentProps>((props) => {
79
89
  const {
80
90
  name,
81
91
  image,
@@ -92,6 +102,7 @@ export function NodeAgent(props: NodeAgentProps): NodeAgentResult {
92
102
  labels: extraLabels = {},
93
103
  env,
94
104
  securityContext,
105
+ defaults: defs,
95
106
  } = props;
96
107
 
97
108
  const saName = `${name}-sa`;
@@ -156,7 +167,7 @@ export function NodeAgent(props: NodeAgentProps): NodeAgentResult {
156
167
  }),
157
168
  };
158
169
 
159
- const daemonSetProps: Record<string, unknown> = {
170
+ const daemonSet = new DaemonSet(mergeDefaults({
160
171
  metadata: {
161
172
  name,
162
173
  ...(namespace && { namespace }),
@@ -169,27 +180,27 @@ export function NodeAgent(props: NodeAgentProps): NodeAgentResult {
169
180
  spec: podSpec,
170
181
  },
171
182
  },
172
- };
183
+ }, defs?.daemonSet));
173
184
 
174
- const serviceAccountProps: Record<string, unknown> = {
185
+ const serviceAccount = new ServiceAccount(mergeDefaults({
175
186
  metadata: {
176
187
  name: saName,
177
188
  ...(namespace && { namespace }),
178
189
  labels: { ...commonLabels, "app.kubernetes.io/component": "agent" },
179
190
  },
180
- };
191
+ }, defs?.serviceAccount));
181
192
 
182
193
  // ClusterRole — cluster-scoped, no namespace
183
- const clusterRoleProps: Record<string, unknown> = {
194
+ const clusterRole = new ClusterRole(mergeDefaults({
184
195
  metadata: {
185
196
  name: clusterRoleName,
186
197
  labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
187
198
  },
188
199
  rules: rbacRules,
189
- };
200
+ }, defs?.clusterRole));
190
201
 
191
202
  // ClusterRoleBinding — cluster-scoped, no namespace
192
- const clusterRoleBindingProps: Record<string, unknown> = {
203
+ const clusterRoleBinding = new ClusterRoleBinding(mergeDefaults({
193
204
  metadata: {
194
205
  name: bindingName,
195
206
  labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
@@ -206,25 +217,25 @@ export function NodeAgent(props: NodeAgentProps): NodeAgentResult {
206
217
  ...(namespace && { namespace }),
207
218
  },
208
219
  ],
209
- };
220
+ }, defs?.clusterRoleBinding));
210
221
 
211
- const result: NodeAgentResult = {
212
- daemonSet: daemonSetProps,
213
- serviceAccount: serviceAccountProps,
214
- clusterRole: clusterRoleProps,
215
- clusterRoleBinding: clusterRoleBindingProps,
222
+ const result: Record<string, any> = {
223
+ daemonSet,
224
+ serviceAccount,
225
+ clusterRole,
226
+ clusterRoleBinding,
216
227
  };
217
228
 
218
229
  if (config) {
219
- result.configMap = {
230
+ result.configMap = new ConfigMap(mergeDefaults({
220
231
  metadata: {
221
232
  name: configMapName,
222
233
  ...(namespace && { namespace }),
223
234
  labels: { ...commonLabels, "app.kubernetes.io/component": "config" },
224
235
  },
225
236
  data: config,
226
- };
237
+ }, defs?.configMap));
227
238
  }
228
239
 
229
240
  return result;
230
- }
241
+ }, "NodeAgent");
@@ -5,6 +5,9 @@
5
5
  * multiple hosts and paths (unlike the single-path Ingress in WebApp).
6
6
  */
7
7
 
8
+ import { Composite, mergeDefaults } from "@intentius/chant";
9
+ import { Ingress } from "../generated";
10
+
8
11
  export interface SecureIngressHost {
9
12
  /** Hostname (e.g., "api.example.com"). */
10
13
  hostname: string;
@@ -32,11 +35,16 @@ export interface SecureIngressProps {
32
35
  labels?: Record<string, string>;
33
36
  /** Namespace for all resources. */
34
37
  namespace?: string;
38
+ /** Per-member defaults for fine-grained overrides. */
39
+ defaults?: {
40
+ ingress?: Partial<Record<string, unknown>>;
41
+ certificate?: Partial<Record<string, unknown>>;
42
+ };
35
43
  }
36
44
 
37
45
  export interface SecureIngressResult {
38
- ingress: Record<string, unknown>;
39
- certificate?: Record<string, unknown>;
46
+ ingress: InstanceType<typeof Ingress>;
47
+ certificate?: InstanceType<typeof Ingress>; // CRD — use Ingress as proxy Declarable
40
48
  }
41
49
 
42
50
  /**
@@ -60,7 +68,7 @@ export interface SecureIngressResult {
60
68
  * });
61
69
  * ```
62
70
  */
63
- export function SecureIngress(props: SecureIngressProps): SecureIngressResult {
71
+ export const SecureIngress = Composite<SecureIngressProps>((props) => {
64
72
  const {
65
73
  name,
66
74
  hosts,
@@ -69,6 +77,7 @@ export function SecureIngress(props: SecureIngressProps): SecureIngressResult {
69
77
  annotations: extraAnnotations = {},
70
78
  labels: extraLabels = {},
71
79
  namespace,
80
+ defaults: defs,
72
81
  } = props;
73
82
 
74
83
  const commonLabels: Record<string, string> = {
@@ -102,7 +111,7 @@ export function SecureIngress(props: SecureIngressProps): SecureIngressResult {
102
111
 
103
112
  const hasAnnotations = Object.keys(ingressAnnotations).length > 0;
104
113
 
105
- const ingressProps: Record<string, unknown> = {
114
+ const ingress = new Ingress(mergeDefaults({
106
115
  metadata: {
107
116
  name,
108
117
  ...(namespace && { namespace }),
@@ -121,14 +130,13 @@ export function SecureIngress(props: SecureIngressProps): SecureIngressResult {
121
130
  ],
122
131
  }),
123
132
  },
124
- };
133
+ }, defs?.ingress));
125
134
 
126
- const result: SecureIngressResult = {
127
- ingress: ingressProps,
128
- };
135
+ const result: Record<string, any> = { ingress };
129
136
 
130
137
  if (clusterIssuer) {
131
- result.certificate = {
138
+ // Certificate is a CRD — use Ingress constructor as a generic Declarable wrapper
139
+ result.certificate = new Ingress(mergeDefaults({
132
140
  metadata: {
133
141
  name: secretName,
134
142
  ...(namespace && { namespace }),
@@ -142,8 +150,8 @@ export function SecureIngress(props: SecureIngressProps): SecureIngressResult {
142
150
  },
143
151
  dnsNames: allHostnames,
144
152
  },
145
- };
153
+ }, defs?.certificate));
146
154
  }
147
155
 
148
156
  return result;
149
- }
157
+ }, "SecureIngress");
@@ -5,6 +5,8 @@
5
5
  * DB migration init). Supports shared volumes between containers.
6
6
  */
7
7
 
8
+ import { Composite, mergeDefaults } from "@intentius/chant";
9
+ import { Deployment, Service } from "../generated";
8
10
  import type { ContainerSecurityContext } from "./security-context";
9
11
 
10
12
  export interface SidecarContainer {
@@ -74,11 +76,16 @@ export interface SidecarAppProps {
74
76
  env?: Array<{ name: string; value: string }>;
75
77
  /** Container security context for the primary container (supports PSS restricted fields). */
76
78
  securityContext?: ContainerSecurityContext;
79
+ /** Per-member defaults for fine-grained overrides. */
80
+ defaults?: {
81
+ deployment?: Partial<Record<string, unknown>>;
82
+ service?: Partial<Record<string, unknown>>;
83
+ };
77
84
  }
78
85
 
79
86
  export interface SidecarAppResult {
80
- deployment: Record<string, unknown>;
81
- service: Record<string, unknown>;
87
+ deployment: InstanceType<typeof Deployment>;
88
+ service: InstanceType<typeof Service>;
82
89
  }
83
90
 
84
91
  /**
@@ -103,7 +110,7 @@ export interface SidecarAppResult {
103
110
  * });
104
111
  * ```
105
112
  */
106
- export function SidecarApp(props: SidecarAppProps): SidecarAppResult {
113
+ export const SidecarApp = Composite<SidecarAppProps>((props) => {
107
114
  const {
108
115
  name,
109
116
  image,
@@ -120,6 +127,7 @@ export function SidecarApp(props: SidecarAppProps): SidecarAppResult {
120
127
  namespace,
121
128
  env,
122
129
  securityContext,
130
+ defaults: defs,
123
131
  } = props;
124
132
 
125
133
  const commonLabels: Record<string, string> = {
@@ -171,7 +179,7 @@ export function SidecarApp(props: SidecarAppProps): SidecarAppResult {
171
179
  ...(volumes && volumes.length > 0 && { volumes }),
172
180
  };
173
181
 
174
- const deploymentProps: Record<string, unknown> = {
182
+ const deployment = new Deployment(mergeDefaults({
175
183
  metadata: {
176
184
  name,
177
185
  ...(namespace && { namespace }),
@@ -185,9 +193,9 @@ export function SidecarApp(props: SidecarAppProps): SidecarAppResult {
185
193
  spec: podSpec,
186
194
  },
187
195
  },
188
- };
196
+ }, defs?.deployment));
189
197
 
190
- const serviceProps: Record<string, unknown> = {
198
+ const service = new Service(mergeDefaults({
191
199
  metadata: {
192
200
  name,
193
201
  ...(namespace && { namespace }),
@@ -198,10 +206,7 @@ export function SidecarApp(props: SidecarAppProps): SidecarAppResult {
198
206
  ports: [{ port: 80, targetPort: port, protocol: "TCP", name: "http" }],
199
207
  type: "ClusterIP",
200
208
  },
201
- };
209
+ }, defs?.service));
202
210
 
203
- return {
204
- deployment: deploymentProps,
205
- service: serviceProps,
206
- };
207
- }
211
+ return { deployment, service };
212
+ }, "SidecarApp");
@@ -5,6 +5,8 @@
5
5
  * like databases, caches, and message queues.
6
6
  */
7
7
 
8
+ import { Composite, mergeDefaults } from "@intentius/chant";
9
+ import { StatefulSet, Service, PodDisruptionBudget } from "../generated";
8
10
  import type { ContainerSecurityContext } from "./security-context";
9
11
 
10
12
  export interface StatefulAppProps {
@@ -47,12 +49,18 @@ export interface StatefulAppProps {
47
49
  namespace?: string;
48
50
  /** Environment variables for the container. */
49
51
  env?: Array<{ name: string; value: string }>;
52
+ /** Per-member defaults for fine-grained overrides. */
53
+ defaults?: {
54
+ statefulSet?: Partial<Record<string, unknown>>;
55
+ service?: Partial<Record<string, unknown>>;
56
+ pdb?: Partial<Record<string, unknown>>;
57
+ };
50
58
  }
51
59
 
52
60
  export interface StatefulAppResult {
53
- statefulSet: Record<string, unknown>;
54
- service: Record<string, unknown>;
55
- pdb?: Record<string, unknown>;
61
+ statefulSet: InstanceType<typeof StatefulSet>;
62
+ service: InstanceType<typeof Service>;
63
+ pdb?: InstanceType<typeof PodDisruptionBudget>;
56
64
  }
57
65
 
58
66
  /**
@@ -72,7 +80,7 @@ export interface StatefulAppResult {
72
80
  * });
73
81
  * ```
74
82
  */
75
- export function StatefulApp(props: StatefulAppProps): StatefulAppResult {
83
+ export const StatefulApp = Composite<StatefulAppProps>((props) => {
76
84
  const {
77
85
  name,
78
86
  image,
@@ -91,6 +99,7 @@ export function StatefulApp(props: StatefulAppProps): StatefulAppResult {
91
99
  memoryLimit = "1Gi",
92
100
  namespace,
93
101
  env,
102
+ defaults: defs,
94
103
  } = props;
95
104
 
96
105
  const commonLabels: Record<string, string> = {
@@ -125,7 +134,7 @@ export function StatefulApp(props: StatefulAppProps): StatefulAppResult {
125
134
  ...(priorityClassName && { priorityClassName }),
126
135
  };
127
136
 
128
- const statefulSetProps: Record<string, unknown> = {
137
+ const statefulSet = new StatefulSet(mergeDefaults({
129
138
  metadata: {
130
139
  name,
131
140
  ...(namespace && { namespace }),
@@ -150,10 +159,10 @@ export function StatefulApp(props: StatefulAppProps): StatefulAppResult {
150
159
  },
151
160
  ],
152
161
  },
153
- };
162
+ }, defs?.statefulSet));
154
163
 
155
164
  // Headless service (clusterIP: None) for StatefulSet DNS
156
- const serviceProps: Record<string, unknown> = {
165
+ const service = new Service(mergeDefaults({
157
166
  metadata: {
158
167
  name,
159
168
  ...(namespace && { namespace }),
@@ -164,12 +173,12 @@ export function StatefulApp(props: StatefulAppProps): StatefulAppResult {
164
173
  ports: [{ port, targetPort: port, protocol: "TCP", name: "app" }],
165
174
  clusterIP: "None",
166
175
  },
167
- };
176
+ }, defs?.service));
168
177
 
169
- const result: StatefulAppResult = { statefulSet: statefulSetProps, service: serviceProps };
178
+ const result: Record<string, any> = { statefulSet, service };
170
179
 
171
180
  if (minAvailable !== undefined) {
172
- result.pdb = {
181
+ result.pdb = new PodDisruptionBudget(mergeDefaults({
173
182
  metadata: {
174
183
  name,
175
184
  ...(namespace && { namespace }),
@@ -179,8 +188,8 @@ export function StatefulApp(props: StatefulAppProps): StatefulAppResult {
179
188
  minAvailable,
180
189
  selector: { matchLabels: { "app.kubernetes.io/name": name } },
181
190
  },
182
- };
191
+ }, defs?.pdb));
183
192
  }
184
193
 
185
194
  return result;
186
- }
195
+ }, "StatefulApp");
@@ -5,6 +5,8 @@
5
5
  * with common defaults (health probes, resource limits, labels).
6
6
  */
7
7
 
8
+ import { Composite, mergeDefaults } from "@intentius/chant";
9
+ import { Deployment, Service, Ingress, PodDisruptionBudget } from "../generated";
8
10
  import type { ContainerSecurityContext } from "./security-context";
9
11
 
10
12
  export interface WebAppProps {
@@ -56,13 +58,20 @@ export interface WebAppProps {
56
58
  namespace?: string;
57
59
  /** Environment variables for the container. */
58
60
  env?: Array<{ name: string; value: string }>;
61
+ /** Per-member defaults for fine-grained overrides. */
62
+ defaults?: {
63
+ deployment?: Partial<Record<string, unknown>>;
64
+ service?: Partial<Record<string, unknown>>;
65
+ ingress?: Partial<Record<string, unknown>>;
66
+ pdb?: Partial<Record<string, unknown>>;
67
+ };
59
68
  }
60
69
 
61
70
  export interface WebAppResult {
62
- deployment: Record<string, unknown>;
63
- service: Record<string, unknown>;
64
- ingress?: Record<string, unknown>;
65
- pdb?: Record<string, unknown>;
71
+ deployment: InstanceType<typeof Deployment>;
72
+ service: InstanceType<typeof Service>;
73
+ ingress?: InstanceType<typeof Ingress>;
74
+ pdb?: InstanceType<typeof PodDisruptionBudget>;
66
75
  }
67
76
 
68
77
  /**
@@ -84,7 +93,7 @@ export interface WebAppResult {
84
93
  * export { deployment, service, ingress };
85
94
  * ```
86
95
  */
87
- export function WebApp(props: WebAppProps): WebAppResult {
96
+ export const WebApp = Composite<WebAppProps>((props) => {
88
97
  const {
89
98
  name,
90
99
  image,
@@ -102,6 +111,7 @@ export function WebApp(props: WebAppProps): WebAppResult {
102
111
  terminationGracePeriodSeconds,
103
112
  priorityClassName,
104
113
  minAvailable,
114
+ defaults: defs,
105
115
  } = props;
106
116
 
107
117
  const commonLabels: Record<string, string> = {
@@ -146,9 +156,7 @@ export function WebApp(props: WebAppProps): WebAppResult {
146
156
  ...(priorityClassName && { priorityClassName }),
147
157
  };
148
158
 
149
- // We return plain objects that users pass to constructors.
150
- // The actual resource instantiation happens in user code with the generated classes.
151
- const deploymentProps: Record<string, unknown> = {
159
+ const deployment = new Deployment(mergeDefaults({
152
160
  metadata: {
153
161
  name,
154
162
  ...(namespace && { namespace }),
@@ -162,9 +170,9 @@ export function WebApp(props: WebAppProps): WebAppResult {
162
170
  spec: podSpec,
163
171
  },
164
172
  },
165
- };
173
+ }, defs?.deployment));
166
174
 
167
- const serviceProps: Record<string, unknown> = {
175
+ const service = new Service(mergeDefaults({
168
176
  metadata: {
169
177
  name,
170
178
  ...(namespace && { namespace }),
@@ -175,12 +183,9 @@ export function WebApp(props: WebAppProps): WebAppResult {
175
183
  ports: [{ port: 80, targetPort: port, protocol: "TCP", name: "http" }],
176
184
  type: "ClusterIP",
177
185
  },
178
- };
186
+ }, defs?.service));
179
187
 
180
- const result: WebAppResult = {
181
- deployment: deploymentProps,
182
- service: serviceProps,
183
- };
188
+ const result: Record<string, any> = { deployment, service };
184
189
 
185
190
  if (props.ingressHost) {
186
191
  // Build paths — use ingressPaths if provided, otherwise default single "/"
@@ -205,7 +210,7 @@ export function WebApp(props: WebAppProps): WebAppResult {
205
210
  },
206
211
  ];
207
212
 
208
- const ingressProps: Record<string, unknown> = {
213
+ result.ingress = new Ingress(mergeDefaults({
209
214
  metadata: {
210
215
  name,
211
216
  ...(namespace && { namespace }),
@@ -227,12 +232,11 @@ export function WebApp(props: WebAppProps): WebAppResult {
227
232
  ],
228
233
  }),
229
234
  },
230
- };
231
- result.ingress = ingressProps;
235
+ }, defs?.ingress));
232
236
  }
233
237
 
234
238
  if (minAvailable !== undefined) {
235
- result.pdb = {
239
+ result.pdb = new PodDisruptionBudget(mergeDefaults({
236
240
  metadata: {
237
241
  name,
238
242
  ...(namespace && { namespace }),
@@ -242,8 +246,8 @@ export function WebApp(props: WebAppProps): WebAppResult {
242
246
  minAvailable,
243
247
  selector: { matchLabels: { "app.kubernetes.io/name": name } },
244
248
  },
245
- };
249
+ }, defs?.pdb));
246
250
  }
247
251
 
248
252
  return result;
249
- }
253
+ }, "WebApp");