@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.
- package/dist/integrity.json +20 -15
- package/dist/manifest.json +1 -1
- package/dist/rules/wk8204.ts +33 -1
- package/dist/rules/wk8304.ts +70 -0
- package/dist/rules/wk8305.ts +115 -0
- package/dist/rules/wk8306.ts +50 -0
- package/dist/skills/chant-k8s-eks.md +156 -0
- package/dist/skills/chant-k8s-patterns.md +245 -0
- package/dist/skills/chant-k8s.md +36 -227
- package/package.json +27 -24
- package/src/codegen/docs.ts +5 -5
- package/src/composites/adot-collector.ts +245 -0
- package/src/composites/agic-ingress.ts +149 -0
- package/src/composites/alb-ingress.ts +152 -0
- package/src/composites/autoscaled-service.ts +51 -0
- package/src/composites/azure-disk-storage-class.ts +82 -0
- package/src/composites/azure-file-storage-class.ts +77 -0
- package/src/composites/azure-monitor-collector.ts +232 -0
- package/src/composites/batch-job.ts +221 -0
- package/src/composites/composites.test.ts +1584 -0
- package/src/composites/config-connector-context.ts +62 -0
- package/src/composites/configured-app.ts +224 -0
- package/src/composites/cron-workload.ts +6 -0
- package/src/composites/ebs-storage-class.ts +96 -0
- package/src/composites/efs-storage-class.ts +77 -0
- package/src/composites/external-dns-agent.ts +174 -0
- package/src/composites/filestore-storage-class.ts +79 -0
- package/src/composites/fluent-bit-agent.ts +220 -0
- package/src/composites/gce-pd-storage-class.ts +85 -0
- package/src/composites/gke-gateway.ts +143 -0
- package/src/composites/index.ts +47 -0
- package/src/composites/irsa-service-account.ts +114 -0
- package/src/composites/metrics-server.ts +224 -0
- package/src/composites/monitored-service.ts +221 -0
- package/src/composites/network-isolated-app.ts +202 -0
- package/src/composites/node-agent.ts +6 -0
- package/src/composites/secure-ingress.ts +149 -0
- package/src/composites/security-context.ts +10 -0
- package/src/composites/sidecar-app.ts +207 -0
- package/src/composites/stateful-app.ts +67 -15
- package/src/composites/web-app.ts +104 -35
- package/src/composites/worker-pool.ts +38 -4
- package/src/composites/workload-identity-sa.ts +118 -0
- package/src/composites/workload-identity-service-account.ts +116 -0
- package/src/index.ts +24 -2
- package/src/lint/post-synth/post-synth.test.ts +362 -1
- package/src/lint/post-synth/wk8204.ts +33 -1
- package/src/lint/post-synth/wk8304.ts +70 -0
- package/src/lint/post-synth/wk8305.ts +115 -0
- package/src/lint/post-synth/wk8306.ts +50 -0
- package/src/plugin.test.ts +2 -2
- package/src/plugin.ts +556 -242
- package/src/serializer.test.ts +120 -0
- 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
|
-
|
|
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
|
}
|