@intentius/chant-lexicon-k8s 0.0.13 → 0.0.15

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 +20 -15
  2. package/dist/manifest.json +1 -1
  3. package/dist/rules/wk8204.ts +33 -1
  4. package/dist/rules/wk8304.ts +70 -0
  5. package/dist/rules/wk8305.ts +115 -0
  6. package/dist/rules/wk8306.ts +50 -0
  7. package/dist/skills/chant-k8s-eks.md +156 -0
  8. package/dist/skills/chant-k8s-patterns.md +245 -0
  9. package/dist/skills/chant-k8s.md +36 -227
  10. package/package.json +27 -24
  11. package/src/codegen/docs.ts +5 -5
  12. package/src/composites/adot-collector.ts +245 -0
  13. package/src/composites/agic-ingress.ts +149 -0
  14. package/src/composites/alb-ingress.ts +152 -0
  15. package/src/composites/autoscaled-service.ts +51 -0
  16. package/src/composites/azure-disk-storage-class.ts +82 -0
  17. package/src/composites/azure-file-storage-class.ts +77 -0
  18. package/src/composites/azure-monitor-collector.ts +232 -0
  19. package/src/composites/batch-job.ts +221 -0
  20. package/src/composites/composites.test.ts +1584 -0
  21. package/src/composites/config-connector-context.ts +62 -0
  22. package/src/composites/configured-app.ts +224 -0
  23. package/src/composites/cron-workload.ts +6 -0
  24. package/src/composites/ebs-storage-class.ts +96 -0
  25. package/src/composites/efs-storage-class.ts +77 -0
  26. package/src/composites/external-dns-agent.ts +174 -0
  27. package/src/composites/filestore-storage-class.ts +79 -0
  28. package/src/composites/fluent-bit-agent.ts +220 -0
  29. package/src/composites/gce-pd-storage-class.ts +85 -0
  30. package/src/composites/gke-gateway.ts +143 -0
  31. package/src/composites/index.ts +47 -0
  32. package/src/composites/irsa-service-account.ts +114 -0
  33. package/src/composites/metrics-server.ts +224 -0
  34. package/src/composites/monitored-service.ts +221 -0
  35. package/src/composites/network-isolated-app.ts +202 -0
  36. package/src/composites/node-agent.ts +6 -0
  37. package/src/composites/secure-ingress.ts +149 -0
  38. package/src/composites/security-context.ts +10 -0
  39. package/src/composites/sidecar-app.ts +207 -0
  40. package/src/composites/stateful-app.ts +67 -15
  41. package/src/composites/web-app.ts +104 -35
  42. package/src/composites/worker-pool.ts +38 -4
  43. package/src/composites/workload-identity-sa.ts +118 -0
  44. package/src/composites/workload-identity-service-account.ts +116 -0
  45. package/src/index.ts +24 -2
  46. package/src/lint/post-synth/post-synth.test.ts +362 -1
  47. package/src/lint/post-synth/wk8204.ts +33 -1
  48. package/src/lint/post-synth/wk8304.ts +70 -0
  49. package/src/lint/post-synth/wk8305.ts +115 -0
  50. package/src/lint/post-synth/wk8306.ts +50 -0
  51. package/src/plugin.test.ts +2 -2
  52. package/src/plugin.ts +556 -242
  53. package/src/serializer.test.ts +120 -0
  54. package/src/serializer.ts +16 -4
@@ -0,0 +1,149 @@
1
+ /**
2
+ * SecureIngress composite — Ingress + optional cert-manager Certificate.
3
+ *
4
+ * A standalone Ingress with TLS via cert-manager, supporting
5
+ * multiple hosts and paths (unlike the single-path Ingress in WebApp).
6
+ */
7
+
8
+ export interface SecureIngressHost {
9
+ /** Hostname (e.g., "api.example.com"). */
10
+ hostname: string;
11
+ /** Path rules for this host. */
12
+ paths: Array<{
13
+ path: string;
14
+ pathType?: string;
15
+ serviceName: string;
16
+ servicePort: number;
17
+ }>;
18
+ }
19
+
20
+ export interface SecureIngressProps {
21
+ /** Ingress name — used in metadata and labels. */
22
+ name: string;
23
+ /** Host definitions with paths. */
24
+ hosts: SecureIngressHost[];
25
+ /** cert-manager ClusterIssuer name — if set, creates a Certificate resource. */
26
+ clusterIssuer?: string;
27
+ /** Ingress class name (e.g., "nginx"). */
28
+ ingressClassName?: string;
29
+ /** Additional annotations on the Ingress. */
30
+ annotations?: Record<string, string>;
31
+ /** Additional labels to apply to all resources. */
32
+ labels?: Record<string, string>;
33
+ /** Namespace for all resources. */
34
+ namespace?: string;
35
+ }
36
+
37
+ export interface SecureIngressResult {
38
+ ingress: Record<string, unknown>;
39
+ certificate?: Record<string, unknown>;
40
+ }
41
+
42
+ /**
43
+ * Create a SecureIngress composite — returns prop objects for
44
+ * an Ingress and optional cert-manager Certificate.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * import { SecureIngress } from "@intentius/chant-lexicon-k8s";
49
+ *
50
+ * const { ingress, certificate } = SecureIngress({
51
+ * name: "api-ingress",
52
+ * hosts: [
53
+ * {
54
+ * hostname: "api.example.com",
55
+ * paths: [{ path: "/", serviceName: "api", servicePort: 80 }],
56
+ * },
57
+ * ],
58
+ * clusterIssuer: "letsencrypt-prod",
59
+ * ingressClassName: "nginx",
60
+ * });
61
+ * ```
62
+ */
63
+ export function SecureIngress(props: SecureIngressProps): SecureIngressResult {
64
+ const {
65
+ name,
66
+ hosts,
67
+ clusterIssuer,
68
+ ingressClassName,
69
+ annotations: extraAnnotations = {},
70
+ labels: extraLabels = {},
71
+ namespace,
72
+ } = props;
73
+
74
+ const commonLabels: Record<string, string> = {
75
+ "app.kubernetes.io/name": name,
76
+ "app.kubernetes.io/managed-by": "chant",
77
+ ...extraLabels,
78
+ };
79
+
80
+ const allHostnames = hosts.map((h) => h.hostname);
81
+ const secretName = `${name}-tls`;
82
+
83
+ const ingressAnnotations: Record<string, string> = {
84
+ ...extraAnnotations,
85
+ };
86
+ if (clusterIssuer) {
87
+ ingressAnnotations["cert-manager.io/cluster-issuer"] = clusterIssuer;
88
+ }
89
+
90
+ const ingressRules = hosts.map((host) => ({
91
+ host: host.hostname,
92
+ http: {
93
+ paths: host.paths.map((p) => ({
94
+ path: p.path,
95
+ pathType: p.pathType ?? "Prefix",
96
+ backend: {
97
+ service: { name: p.serviceName, port: { number: p.servicePort } },
98
+ },
99
+ })),
100
+ },
101
+ }));
102
+
103
+ const hasAnnotations = Object.keys(ingressAnnotations).length > 0;
104
+
105
+ const ingressProps: Record<string, unknown> = {
106
+ metadata: {
107
+ name,
108
+ ...(namespace && { namespace }),
109
+ labels: { ...commonLabels, "app.kubernetes.io/component": "ingress" },
110
+ ...(hasAnnotations && { annotations: ingressAnnotations }),
111
+ },
112
+ spec: {
113
+ ...(ingressClassName && { ingressClassName }),
114
+ rules: ingressRules,
115
+ ...(clusterIssuer && {
116
+ tls: [
117
+ {
118
+ hosts: allHostnames,
119
+ secretName,
120
+ },
121
+ ],
122
+ }),
123
+ },
124
+ };
125
+
126
+ const result: SecureIngressResult = {
127
+ ingress: ingressProps,
128
+ };
129
+
130
+ if (clusterIssuer) {
131
+ result.certificate = {
132
+ metadata: {
133
+ name: secretName,
134
+ ...(namespace && { namespace }),
135
+ labels: { ...commonLabels, "app.kubernetes.io/component": "certificate" },
136
+ },
137
+ spec: {
138
+ secretName,
139
+ issuerRef: {
140
+ name: clusterIssuer,
141
+ kind: "ClusterIssuer",
142
+ },
143
+ dnsNames: allHostnames,
144
+ },
145
+ };
146
+ }
147
+
148
+ return result;
149
+ }
@@ -0,0 +1,10 @@
1
+ /** Container-level security context — covers PSS restricted requirements. */
2
+ export interface ContainerSecurityContext {
3
+ runAsNonRoot?: boolean;
4
+ readOnlyRootFilesystem?: boolean;
5
+ runAsUser?: number;
6
+ runAsGroup?: number;
7
+ allowPrivilegeEscalation?: boolean;
8
+ capabilities?: { add?: string[]; drop?: string[] };
9
+ seccompProfile?: { type: string; localhostProfile?: string };
10
+ }
@@ -0,0 +1,207 @@
1
+ /**
2
+ * SidecarApp composite — multi-container Deployment + Service.
3
+ *
4
+ * Sidecar and init container patterns (envoy proxy, log forwarder,
5
+ * DB migration init). Supports shared volumes between containers.
6
+ */
7
+
8
+ import type { ContainerSecurityContext } from "./security-context";
9
+
10
+ export interface SidecarContainer {
11
+ /** Sidecar container name. */
12
+ name: string;
13
+ /** Container image. */
14
+ image: string;
15
+ /** Container ports. */
16
+ ports?: Array<{ containerPort: number; name?: string }>;
17
+ /** Resource limits and requests. */
18
+ resources?: {
19
+ limits?: { cpu?: string; memory?: string };
20
+ requests?: { cpu?: string; memory?: string };
21
+ };
22
+ /** Environment variables. */
23
+ env?: Array<{ name: string; value: string }>;
24
+ }
25
+
26
+ export interface InitContainer {
27
+ /** Init container name. */
28
+ name: string;
29
+ /** Container image. */
30
+ image: string;
31
+ /** Command to run. */
32
+ command?: string[];
33
+ /** Arguments to the command. */
34
+ args?: string[];
35
+ }
36
+
37
+ export interface SharedVolume {
38
+ /** Volume name. */
39
+ name: string;
40
+ /** Use emptyDir (default). */
41
+ emptyDir?: Record<string, unknown>;
42
+ /** Use a ConfigMap by name. */
43
+ configMapName?: string;
44
+ }
45
+
46
+ export interface SidecarAppProps {
47
+ /** Application name — used in metadata and labels. */
48
+ name: string;
49
+ /** Primary container image. */
50
+ image: string;
51
+ /** Primary container port (default: 80). */
52
+ port?: number;
53
+ /** Number of replicas (default: 2). */
54
+ replicas?: number;
55
+ /** Sidecar containers. */
56
+ sidecars: SidecarContainer[];
57
+ /** Init containers (run before main containers). */
58
+ initContainers?: InitContainer[];
59
+ /** Shared volumes between containers. */
60
+ sharedVolumes?: SharedVolume[];
61
+ /** Additional labels to apply to all resources. */
62
+ labels?: Record<string, string>;
63
+ /** CPU limit for the primary container (default: "500m"). */
64
+ cpuLimit?: string;
65
+ /** Memory limit for the primary container (default: "256Mi"). */
66
+ memoryLimit?: string;
67
+ /** CPU request for the primary container (default: "100m"). */
68
+ cpuRequest?: string;
69
+ /** Memory request for the primary container (default: "128Mi"). */
70
+ memoryRequest?: string;
71
+ /** Namespace for all resources. */
72
+ namespace?: string;
73
+ /** Environment variables for the primary container. */
74
+ env?: Array<{ name: string; value: string }>;
75
+ /** Container security context for the primary container (supports PSS restricted fields). */
76
+ securityContext?: ContainerSecurityContext;
77
+ }
78
+
79
+ export interface SidecarAppResult {
80
+ deployment: Record<string, unknown>;
81
+ service: Record<string, unknown>;
82
+ }
83
+
84
+ /**
85
+ * Create a SidecarApp composite — returns prop objects for
86
+ * a multi-container Deployment and Service.
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * import { SidecarApp } from "@intentius/chant-lexicon-k8s";
91
+ *
92
+ * const { deployment, service } = SidecarApp({
93
+ * name: "api",
94
+ * image: "api:1.0",
95
+ * port: 8080,
96
+ * sidecars: [
97
+ * { name: "envoy", image: "envoyproxy/envoy:v1.28", ports: [{ containerPort: 9901 }] },
98
+ * ],
99
+ * initContainers: [
100
+ * { name: "migrate", image: "api:1.0", command: ["python", "manage.py", "migrate"] },
101
+ * ],
102
+ * sharedVolumes: [{ name: "tmp", emptyDir: {} }],
103
+ * });
104
+ * ```
105
+ */
106
+ export function SidecarApp(props: SidecarAppProps): SidecarAppResult {
107
+ const {
108
+ name,
109
+ image,
110
+ port = 80,
111
+ replicas = 2,
112
+ sidecars,
113
+ initContainers,
114
+ sharedVolumes,
115
+ labels: extraLabels = {},
116
+ cpuLimit = "500m",
117
+ memoryLimit = "256Mi",
118
+ cpuRequest = "100m",
119
+ memoryRequest = "128Mi",
120
+ namespace,
121
+ env,
122
+ securityContext,
123
+ } = props;
124
+
125
+ const commonLabels: Record<string, string> = {
126
+ "app.kubernetes.io/name": name,
127
+ "app.kubernetes.io/managed-by": "chant",
128
+ ...extraLabels,
129
+ };
130
+
131
+ // Primary container
132
+ const primaryContainer: Record<string, unknown> = {
133
+ name,
134
+ image,
135
+ ports: [{ containerPort: port, name: "http" }],
136
+ resources: {
137
+ limits: { cpu: cpuLimit, memory: memoryLimit },
138
+ requests: { cpu: cpuRequest, memory: memoryRequest },
139
+ },
140
+ ...(env && { env }),
141
+ ...(securityContext && { securityContext }),
142
+ };
143
+
144
+ // Sidecar containers
145
+ const sidecarContainers = sidecars.map((sc) => ({
146
+ name: sc.name,
147
+ image: sc.image,
148
+ ...(sc.ports && { ports: sc.ports }),
149
+ ...(sc.resources && { resources: sc.resources }),
150
+ ...(sc.env && { env: sc.env }),
151
+ }));
152
+
153
+ // Build volumes from sharedVolumes
154
+ const volumes: Array<Record<string, unknown>> | undefined = sharedVolumes?.map((sv) => {
155
+ if (sv.configMapName) {
156
+ return { name: sv.name, configMap: { name: sv.configMapName } };
157
+ }
158
+ return { name: sv.name, emptyDir: sv.emptyDir ?? {} };
159
+ });
160
+
161
+ const podSpec: Record<string, unknown> = {
162
+ containers: [primaryContainer, ...sidecarContainers],
163
+ ...(initContainers && {
164
+ initContainers: initContainers.map((ic) => ({
165
+ name: ic.name,
166
+ image: ic.image,
167
+ ...(ic.command && { command: ic.command }),
168
+ ...(ic.args && { args: ic.args }),
169
+ })),
170
+ }),
171
+ ...(volumes && volumes.length > 0 && { volumes }),
172
+ };
173
+
174
+ const deploymentProps: Record<string, unknown> = {
175
+ metadata: {
176
+ name,
177
+ ...(namespace && { namespace }),
178
+ labels: { ...commonLabels, "app.kubernetes.io/component": "server" },
179
+ },
180
+ spec: {
181
+ replicas,
182
+ selector: { matchLabels: { "app.kubernetes.io/name": name } },
183
+ template: {
184
+ metadata: { labels: { "app.kubernetes.io/name": name, ...extraLabels } },
185
+ spec: podSpec,
186
+ },
187
+ },
188
+ };
189
+
190
+ const serviceProps: Record<string, unknown> = {
191
+ metadata: {
192
+ name,
193
+ ...(namespace && { namespace }),
194
+ labels: { ...commonLabels, "app.kubernetes.io/component": "server" },
195
+ },
196
+ spec: {
197
+ selector: { "app.kubernetes.io/name": name },
198
+ ports: [{ port: 80, targetPort: port, protocol: "TCP", name: "http" }],
199
+ type: "ClusterIP",
200
+ },
201
+ };
202
+
203
+ return {
204
+ deployment: deploymentProps,
205
+ service: serviceProps,
206
+ };
207
+ }
@@ -5,6 +5,8 @@
5
5
  * like databases, caches, and message queues.
6
6
  */
7
7
 
8
+ import type { ContainerSecurityContext } from "./security-context";
9
+
8
10
  export interface StatefulAppProps {
9
11
  /** Application name — used in metadata and labels. */
10
12
  name: string;
@@ -20,6 +22,21 @@ export interface StatefulAppProps {
20
22
  storageClassName?: string;
21
23
  /** Volume mount path inside the container (default: "/data"). */
22
24
  mountPath?: string;
25
+ /** PodDisruptionBudget minAvailable — if set, creates a PDB. */
26
+ minAvailable?: number | string;
27
+ /** Init containers (e.g., schema migrations, permission setup). */
28
+ initContainers?: Array<{
29
+ name: string;
30
+ image: string;
31
+ command?: string[];
32
+ args?: string[];
33
+ }>;
34
+ /** Container security context (supports PSS restricted fields). */
35
+ securityContext?: ContainerSecurityContext;
36
+ /** Termination grace period in seconds. */
37
+ terminationGracePeriodSeconds?: number;
38
+ /** Priority class name for pod scheduling. */
39
+ priorityClassName?: string;
23
40
  /** Additional labels to apply to all resources. */
24
41
  labels?: Record<string, string>;
25
42
  /** CPU limit (e.g., "1"). */
@@ -35,6 +52,7 @@ export interface StatefulAppProps {
35
52
  export interface StatefulAppResult {
36
53
  statefulSet: Record<string, unknown>;
37
54
  service: Record<string, unknown>;
55
+ pdb?: Record<string, unknown>;
38
56
  }
39
57
 
40
58
  /**
@@ -63,6 +81,11 @@ export function StatefulApp(props: StatefulAppProps): StatefulAppResult {
63
81
  storageSize = "10Gi",
64
82
  storageClassName,
65
83
  mountPath = "/data",
84
+ minAvailable,
85
+ initContainers,
86
+ securityContext,
87
+ terminationGracePeriodSeconds,
88
+ priorityClassName,
66
89
  labels: extraLabels = {},
67
90
  cpuLimit = "1",
68
91
  memoryLimit = "1Gi",
@@ -76,6 +99,32 @@ export function StatefulApp(props: StatefulAppProps): StatefulAppResult {
76
99
  ...extraLabels,
77
100
  };
78
101
 
102
+ const container: Record<string, unknown> = {
103
+ name,
104
+ image,
105
+ ports: [{ containerPort: port, name: "app" }],
106
+ resources: {
107
+ limits: { cpu: cpuLimit, memory: memoryLimit },
108
+ },
109
+ volumeMounts: [{ name: "data", mountPath }],
110
+ ...(env && { env }),
111
+ ...(securityContext && { securityContext }),
112
+ };
113
+
114
+ const podSpec: Record<string, unknown> = {
115
+ containers: [container],
116
+ ...(initContainers && {
117
+ initContainers: initContainers.map((ic) => ({
118
+ name: ic.name,
119
+ image: ic.image,
120
+ ...(ic.command && { command: ic.command }),
121
+ ...(ic.args && { args: ic.args }),
122
+ })),
123
+ }),
124
+ ...(terminationGracePeriodSeconds !== undefined && { terminationGracePeriodSeconds }),
125
+ ...(priorityClassName && { priorityClassName }),
126
+ };
127
+
79
128
  const statefulSetProps: Record<string, unknown> = {
80
129
  metadata: {
81
130
  name,
@@ -88,20 +137,7 @@ export function StatefulApp(props: StatefulAppProps): StatefulAppResult {
88
137
  selector: { matchLabels: { "app.kubernetes.io/name": name } },
89
138
  template: {
90
139
  metadata: { labels: { "app.kubernetes.io/name": name, ...extraLabels } },
91
- spec: {
92
- containers: [
93
- {
94
- name,
95
- image,
96
- ports: [{ containerPort: port, name: "app" }],
97
- resources: {
98
- limits: { cpu: cpuLimit, memory: memoryLimit },
99
- },
100
- volumeMounts: [{ name: "data", mountPath }],
101
- ...(env && { env }),
102
- },
103
- ],
104
- },
140
+ spec: podSpec,
105
141
  },
106
142
  volumeClaimTemplates: [
107
143
  {
@@ -130,5 +166,21 @@ export function StatefulApp(props: StatefulAppProps): StatefulAppResult {
130
166
  },
131
167
  };
132
168
 
133
- return { statefulSet: statefulSetProps, service: serviceProps };
169
+ const result: StatefulAppResult = { statefulSet: statefulSetProps, service: serviceProps };
170
+
171
+ if (minAvailable !== undefined) {
172
+ result.pdb = {
173
+ metadata: {
174
+ name,
175
+ ...(namespace && { namespace }),
176
+ labels: { ...commonLabels, "app.kubernetes.io/component": "disruption-budget" },
177
+ },
178
+ spec: {
179
+ minAvailable,
180
+ selector: { matchLabels: { "app.kubernetes.io/name": name } },
181
+ },
182
+ };
183
+ }
184
+
185
+ return result;
134
186
  }
@@ -5,6 +5,8 @@
5
5
  * with common defaults (health probes, resource limits, labels).
6
6
  */
7
7
 
8
+ import type { ContainerSecurityContext } from "./security-context";
9
+
8
10
  export interface WebAppProps {
9
11
  /** Application name — used in metadata and labels. */
10
12
  name: string;
@@ -18,6 +20,28 @@ export interface WebAppProps {
18
20
  ingressHost?: string;
19
21
  /** Ingress TLS secret name — if set, enables TLS on the Ingress. */
20
22
  ingressTlsSecret?: string;
23
+ /** Multi-path ingress rules — overrides the default single "/" path. */
24
+ ingressPaths?: Array<{
25
+ path: string;
26
+ pathType?: string;
27
+ serviceName?: string;
28
+ servicePort?: number;
29
+ }>;
30
+ /** PodDisruptionBudget minAvailable — if set, creates a PDB. */
31
+ minAvailable?: number | string;
32
+ /** Init containers (e.g., migrations, cert setup). */
33
+ initContainers?: Array<{
34
+ name: string;
35
+ image: string;
36
+ command?: string[];
37
+ args?: string[];
38
+ }>;
39
+ /** Container security context (supports PSS restricted fields). */
40
+ securityContext?: ContainerSecurityContext;
41
+ /** Termination grace period in seconds. */
42
+ terminationGracePeriodSeconds?: number;
43
+ /** Priority class name for pod scheduling. */
44
+ priorityClassName?: string;
21
45
  /** Additional labels to apply to all resources. */
22
46
  labels?: Record<string, string>;
23
47
  /** CPU limit (e.g., "500m"). */
@@ -38,6 +62,7 @@ export interface WebAppResult {
38
62
  deployment: Record<string, unknown>;
39
63
  service: Record<string, unknown>;
40
64
  ingress?: Record<string, unknown>;
65
+ pdb?: Record<string, unknown>;
41
66
  }
42
67
 
43
68
  /**
@@ -72,6 +97,11 @@ export function WebApp(props: WebAppProps): WebAppResult {
72
97
  memoryRequest = "128Mi",
73
98
  namespace,
74
99
  env,
100
+ initContainers,
101
+ securityContext,
102
+ terminationGracePeriodSeconds,
103
+ priorityClassName,
104
+ minAvailable,
75
105
  } = props;
76
106
 
77
107
  const commonLabels: Record<string, string> = {
@@ -80,6 +110,42 @@ export function WebApp(props: WebAppProps): WebAppResult {
80
110
  ...extraLabels,
81
111
  };
82
112
 
113
+ const container: Record<string, unknown> = {
114
+ name,
115
+ image,
116
+ ports: [{ containerPort: port, name: "http" }],
117
+ resources: {
118
+ limits: { cpu: cpuLimit, memory: memoryLimit },
119
+ requests: { cpu: cpuRequest, memory: memoryRequest },
120
+ },
121
+ livenessProbe: {
122
+ httpGet: { path: "/", port },
123
+ initialDelaySeconds: 10,
124
+ periodSeconds: 10,
125
+ },
126
+ readinessProbe: {
127
+ httpGet: { path: "/", port },
128
+ initialDelaySeconds: 5,
129
+ periodSeconds: 5,
130
+ },
131
+ ...(env && { env }),
132
+ ...(securityContext && { securityContext }),
133
+ };
134
+
135
+ const podSpec: Record<string, unknown> = {
136
+ containers: [container],
137
+ ...(initContainers && {
138
+ initContainers: initContainers.map((ic) => ({
139
+ name: ic.name,
140
+ image: ic.image,
141
+ ...(ic.command && { command: ic.command }),
142
+ ...(ic.args && { args: ic.args }),
143
+ })),
144
+ }),
145
+ ...(terminationGracePeriodSeconds !== undefined && { terminationGracePeriodSeconds }),
146
+ ...(priorityClassName && { priorityClassName }),
147
+ };
148
+
83
149
  // We return plain objects that users pass to constructors.
84
150
  // The actual resource instantiation happens in user code with the generated classes.
85
151
  const deploymentProps: Record<string, unknown> = {
@@ -93,30 +159,7 @@ export function WebApp(props: WebAppProps): WebAppResult {
93
159
  selector: { matchLabels: { "app.kubernetes.io/name": name } },
94
160
  template: {
95
161
  metadata: { labels: { "app.kubernetes.io/name": name, ...extraLabels } },
96
- spec: {
97
- containers: [
98
- {
99
- name,
100
- image,
101
- ports: [{ containerPort: port, name: "http" }],
102
- resources: {
103
- limits: { cpu: cpuLimit, memory: memoryLimit },
104
- requests: { cpu: cpuRequest, memory: memoryRequest },
105
- },
106
- livenessProbe: {
107
- httpGet: { path: "/", port },
108
- initialDelaySeconds: 10,
109
- periodSeconds: 10,
110
- },
111
- readinessProbe: {
112
- httpGet: { path: "/", port },
113
- initialDelaySeconds: 5,
114
- periodSeconds: 5,
115
- },
116
- ...(env && { env }),
117
- },
118
- ],
119
- },
162
+ spec: podSpec,
120
163
  },
121
164
  },
122
165
  };
@@ -140,6 +183,28 @@ export function WebApp(props: WebAppProps): WebAppResult {
140
183
  };
141
184
 
142
185
  if (props.ingressHost) {
186
+ // Build paths — use ingressPaths if provided, otherwise default single "/"
187
+ const paths = props.ingressPaths
188
+ ? props.ingressPaths.map((p) => ({
189
+ path: p.path,
190
+ pathType: p.pathType ?? "Prefix",
191
+ backend: {
192
+ service: {
193
+ name: p.serviceName ?? name,
194
+ port: { number: p.servicePort ?? 80 },
195
+ },
196
+ },
197
+ }))
198
+ : [
199
+ {
200
+ path: "/",
201
+ pathType: "Prefix",
202
+ backend: {
203
+ service: { name, port: { number: 80 } },
204
+ },
205
+ },
206
+ ];
207
+
143
208
  const ingressProps: Record<string, unknown> = {
144
209
  metadata: {
145
210
  name,
@@ -150,17 +215,7 @@ export function WebApp(props: WebAppProps): WebAppResult {
150
215
  rules: [
151
216
  {
152
217
  host: props.ingressHost,
153
- http: {
154
- paths: [
155
- {
156
- path: "/",
157
- pathType: "Prefix",
158
- backend: {
159
- service: { name, port: { number: 80 } },
160
- },
161
- },
162
- ],
163
- },
218
+ http: { paths },
164
219
  },
165
220
  ],
166
221
  ...(props.ingressTlsSecret && {
@@ -176,5 +231,19 @@ export function WebApp(props: WebAppProps): WebAppResult {
176
231
  result.ingress = ingressProps;
177
232
  }
178
233
 
234
+ if (minAvailable !== undefined) {
235
+ result.pdb = {
236
+ metadata: {
237
+ name,
238
+ ...(namespace && { namespace }),
239
+ labels: { ...commonLabels, "app.kubernetes.io/component": "disruption-budget" },
240
+ },
241
+ spec: {
242
+ minAvailable,
243
+ selector: { matchLabels: { "app.kubernetes.io/name": name } },
244
+ },
245
+ };
246
+ }
247
+
179
248
  return result;
180
249
  }