@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,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AzureDiskStorageClass composite — StorageClass for Azure Disk CSI driver.
|
|
3
|
+
*
|
|
4
|
+
* @aks Creates a StorageClass with the `disk.csi.azure.com` provisioner.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface AzureDiskStorageClassProps {
|
|
8
|
+
/** StorageClass name. */
|
|
9
|
+
name: string;
|
|
10
|
+
/** Azure Disk SKU name (default: "Premium_LRS"). */
|
|
11
|
+
skuName?: string;
|
|
12
|
+
/** OS disk caching mode (default: "ReadOnly"). */
|
|
13
|
+
cachingMode?: string;
|
|
14
|
+
/** Network access policy (default: "AllowAll"). */
|
|
15
|
+
networkAccessPolicy?: string;
|
|
16
|
+
/** Reclaim policy (default: "Delete"). */
|
|
17
|
+
reclaimPolicy?: string;
|
|
18
|
+
/** Volume binding mode (default: "WaitForFirstConsumer"). */
|
|
19
|
+
volumeBindingMode?: string;
|
|
20
|
+
/** Allow volume expansion (default: true). */
|
|
21
|
+
allowVolumeExpansion?: boolean;
|
|
22
|
+
/** Additional labels. */
|
|
23
|
+
labels?: Record<string, string>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface AzureDiskStorageClassResult {
|
|
27
|
+
storageClass: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create an AzureDiskStorageClass composite — returns prop objects for
|
|
32
|
+
* a StorageClass with the Azure Disk CSI provisioner.
|
|
33
|
+
*
|
|
34
|
+
* @aks
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* import { AzureDiskStorageClass } from "@intentius/chant-lexicon-k8s";
|
|
38
|
+
*
|
|
39
|
+
* const { storageClass } = AzureDiskStorageClass({
|
|
40
|
+
* name: "premium-disk",
|
|
41
|
+
* skuName: "Premium_LRS",
|
|
42
|
+
* });
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export function AzureDiskStorageClass(props: AzureDiskStorageClassProps): AzureDiskStorageClassResult {
|
|
46
|
+
const {
|
|
47
|
+
name,
|
|
48
|
+
skuName = "Premium_LRS",
|
|
49
|
+
cachingMode = "ReadOnly",
|
|
50
|
+
networkAccessPolicy = "AllowAll",
|
|
51
|
+
reclaimPolicy = "Delete",
|
|
52
|
+
volumeBindingMode = "WaitForFirstConsumer",
|
|
53
|
+
allowVolumeExpansion = true,
|
|
54
|
+
labels: extraLabels = {},
|
|
55
|
+
} = props;
|
|
56
|
+
|
|
57
|
+
const commonLabels: Record<string, string> = {
|
|
58
|
+
"app.kubernetes.io/name": name,
|
|
59
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
60
|
+
...extraLabels,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const parameters: Record<string, string> = {
|
|
64
|
+
skuName,
|
|
65
|
+
cachingMode,
|
|
66
|
+
networkAccessPolicy,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const storageClassProps: Record<string, unknown> = {
|
|
70
|
+
metadata: {
|
|
71
|
+
name,
|
|
72
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "storage" },
|
|
73
|
+
},
|
|
74
|
+
provisioner: "disk.csi.azure.com",
|
|
75
|
+
parameters,
|
|
76
|
+
reclaimPolicy,
|
|
77
|
+
volumeBindingMode,
|
|
78
|
+
allowVolumeExpansion,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return { storageClass: storageClassProps };
|
|
82
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AzureFileStorageClass composite — StorageClass for Azure Files CSI driver.
|
|
3
|
+
*
|
|
4
|
+
* @aks Creates a StorageClass with the `file.csi.azure.com` provisioner.
|
|
5
|
+
* Azure Files provides ReadWriteMany access mode (shared across pods/nodes).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface AzureFileStorageClassProps {
|
|
9
|
+
/** StorageClass name. */
|
|
10
|
+
name: string;
|
|
11
|
+
/** Azure Files SKU name (default: "Premium_LRS"). */
|
|
12
|
+
skuName?: string;
|
|
13
|
+
/** Protocol for Azure Files (default: "smb"). */
|
|
14
|
+
protocol?: string;
|
|
15
|
+
/** Specific Azure file share name (optional; dynamically provisioned if omitted). */
|
|
16
|
+
shareName?: string;
|
|
17
|
+
/** Reclaim policy (default: "Delete"). */
|
|
18
|
+
reclaimPolicy?: string;
|
|
19
|
+
/** Additional labels. */
|
|
20
|
+
labels?: Record<string, string>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface AzureFileStorageClassResult {
|
|
24
|
+
storageClass: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create an AzureFileStorageClass composite — returns prop objects for
|
|
29
|
+
* a StorageClass with the Azure Files CSI provisioner.
|
|
30
|
+
*
|
|
31
|
+
* @aks
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* import { AzureFileStorageClass } from "@intentius/chant-lexicon-k8s";
|
|
35
|
+
*
|
|
36
|
+
* const { storageClass } = AzureFileStorageClass({
|
|
37
|
+
* name: "azure-files-shared",
|
|
38
|
+
* skuName: "Premium_LRS",
|
|
39
|
+
* protocol: "nfs",
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export function AzureFileStorageClass(props: AzureFileStorageClassProps): AzureFileStorageClassResult {
|
|
44
|
+
const {
|
|
45
|
+
name,
|
|
46
|
+
skuName = "Premium_LRS",
|
|
47
|
+
protocol = "smb",
|
|
48
|
+
shareName,
|
|
49
|
+
reclaimPolicy = "Delete",
|
|
50
|
+
labels: extraLabels = {},
|
|
51
|
+
} = props;
|
|
52
|
+
|
|
53
|
+
const commonLabels: Record<string, string> = {
|
|
54
|
+
"app.kubernetes.io/name": name,
|
|
55
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
56
|
+
...extraLabels,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const parameters: Record<string, string> = {
|
|
60
|
+
skuName,
|
|
61
|
+
protocol,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
if (shareName) parameters.shareName = shareName;
|
|
65
|
+
|
|
66
|
+
const storageClassProps: Record<string, unknown> = {
|
|
67
|
+
metadata: {
|
|
68
|
+
name,
|
|
69
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "storage" },
|
|
70
|
+
},
|
|
71
|
+
provisioner: "file.csi.azure.com",
|
|
72
|
+
parameters,
|
|
73
|
+
reclaimPolicy,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return { storageClass: storageClassProps };
|
|
77
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AzureMonitorCollector composite — DaemonSet + RBAC + ConfigMap for Azure Monitor / OTel collector.
|
|
3
|
+
*
|
|
4
|
+
* @aks Azure Monitor agent with OpenTelemetry collector config for
|
|
5
|
+
* Log Analytics workspace integration on AKS clusters.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface AzureMonitorCollectorProps {
|
|
9
|
+
/** Azure Log Analytics workspace ID. */
|
|
10
|
+
workspaceId: string;
|
|
11
|
+
/** AKS cluster name. */
|
|
12
|
+
clusterName: string;
|
|
13
|
+
/** Agent name (default: "azure-monitor-collector"). */
|
|
14
|
+
name?: string;
|
|
15
|
+
/** Collector image (default: "mcr.microsoft.com/azuremonitor/containerinsights/ciprod:latest"). */
|
|
16
|
+
image?: string;
|
|
17
|
+
/** Namespace (default: "azure-monitor"). */
|
|
18
|
+
namespace?: string;
|
|
19
|
+
/** Additional labels. */
|
|
20
|
+
labels?: Record<string, string>;
|
|
21
|
+
/** CPU request (default: "100m"). */
|
|
22
|
+
cpuRequest?: string;
|
|
23
|
+
/** Memory request (default: "256Mi"). */
|
|
24
|
+
memoryRequest?: string;
|
|
25
|
+
/** CPU limit (default: "500m"). */
|
|
26
|
+
cpuLimit?: string;
|
|
27
|
+
/** Memory limit (default: "512Mi"). */
|
|
28
|
+
memoryLimit?: string;
|
|
29
|
+
/** Azure AD client ID for Workload Identity (adds azure.workload.identity annotations to ServiceAccount). */
|
|
30
|
+
clientId?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface AzureMonitorCollectorResult {
|
|
34
|
+
daemonSet: Record<string, unknown>;
|
|
35
|
+
serviceAccount: Record<string, unknown>;
|
|
36
|
+
clusterRole: Record<string, unknown>;
|
|
37
|
+
clusterRoleBinding: Record<string, unknown>;
|
|
38
|
+
configMap: Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Create an AzureMonitorCollector composite — returns prop objects for
|
|
43
|
+
* a DaemonSet, ServiceAccount, ClusterRole, ClusterRoleBinding, and ConfigMap.
|
|
44
|
+
*
|
|
45
|
+
* @aks
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* import { AzureMonitorCollector } from "@intentius/chant-lexicon-k8s";
|
|
49
|
+
*
|
|
50
|
+
* const { daemonSet, serviceAccount, clusterRole, clusterRoleBinding, configMap } = AzureMonitorCollector({
|
|
51
|
+
* workspaceId: "00000000-0000-0000-0000-000000000000",
|
|
52
|
+
* clusterName: "my-aks-cluster",
|
|
53
|
+
* });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export function AzureMonitorCollector(props: AzureMonitorCollectorProps): AzureMonitorCollectorResult {
|
|
57
|
+
const {
|
|
58
|
+
workspaceId,
|
|
59
|
+
clusterName,
|
|
60
|
+
name = "azure-monitor-collector",
|
|
61
|
+
image = "mcr.microsoft.com/azuremonitor/containerinsights/ciprod:latest",
|
|
62
|
+
namespace = "azure-monitor",
|
|
63
|
+
labels: extraLabels = {},
|
|
64
|
+
cpuRequest = "100m",
|
|
65
|
+
memoryRequest = "256Mi",
|
|
66
|
+
cpuLimit = "500m",
|
|
67
|
+
memoryLimit = "512Mi",
|
|
68
|
+
clientId,
|
|
69
|
+
} = props;
|
|
70
|
+
|
|
71
|
+
const saName = `${name}-sa`;
|
|
72
|
+
const clusterRoleName = `${name}-role`;
|
|
73
|
+
const bindingName = `${name}-binding`;
|
|
74
|
+
const configMapName = `${name}-config`;
|
|
75
|
+
|
|
76
|
+
const commonLabels: Record<string, string> = {
|
|
77
|
+
"app.kubernetes.io/name": name,
|
|
78
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
79
|
+
...extraLabels,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Build OTel collector config YAML for Azure Monitor
|
|
83
|
+
const collectorConfig = `receivers:
|
|
84
|
+
otlp:
|
|
85
|
+
protocols:
|
|
86
|
+
grpc:
|
|
87
|
+
endpoint: 0.0.0.0:4317
|
|
88
|
+
http:
|
|
89
|
+
endpoint: 0.0.0.0:4318
|
|
90
|
+
|
|
91
|
+
processors:
|
|
92
|
+
batch:
|
|
93
|
+
timeout: 30s
|
|
94
|
+
send_batch_size: 8192
|
|
95
|
+
|
|
96
|
+
exporters:
|
|
97
|
+
azuremonitor:
|
|
98
|
+
connection_string: InstrumentationKey=\${APPINSIGHTS_INSTRUMENTATIONKEY}
|
|
99
|
+
endpoint: https://dc.services.visualstudio.com/v2/track
|
|
100
|
+
azuremonitor/logs:
|
|
101
|
+
workspace_id: ${workspaceId}
|
|
102
|
+
cluster_name: ${clusterName}
|
|
103
|
+
|
|
104
|
+
service:
|
|
105
|
+
pipelines:
|
|
106
|
+
metrics:
|
|
107
|
+
receivers: [otlp]
|
|
108
|
+
processors: [batch]
|
|
109
|
+
exporters: [azuremonitor]
|
|
110
|
+
traces:
|
|
111
|
+
receivers: [otlp]
|
|
112
|
+
processors: [batch]
|
|
113
|
+
exporters: [azuremonitor]
|
|
114
|
+
logs:
|
|
115
|
+
receivers: [otlp]
|
|
116
|
+
processors: [batch]
|
|
117
|
+
exporters: [azuremonitor/logs]
|
|
118
|
+
`;
|
|
119
|
+
|
|
120
|
+
const container: Record<string, unknown> = {
|
|
121
|
+
name,
|
|
122
|
+
image,
|
|
123
|
+
ports: [
|
|
124
|
+
{ containerPort: 4317, name: "otlp-grpc" },
|
|
125
|
+
{ containerPort: 4318, name: "otlp-http" },
|
|
126
|
+
],
|
|
127
|
+
env: [
|
|
128
|
+
{ name: "AKS_CLUSTER_NAME", value: clusterName },
|
|
129
|
+
{ name: "WORKSPACE_ID", value: workspaceId },
|
|
130
|
+
],
|
|
131
|
+
resources: {
|
|
132
|
+
requests: { cpu: cpuRequest, memory: memoryRequest },
|
|
133
|
+
limits: { cpu: cpuLimit, memory: memoryLimit },
|
|
134
|
+
},
|
|
135
|
+
volumeMounts: [
|
|
136
|
+
{ name: "config", mountPath: "/etc/otel", readOnly: true },
|
|
137
|
+
],
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const daemonSetProps: Record<string, unknown> = {
|
|
141
|
+
metadata: {
|
|
142
|
+
name,
|
|
143
|
+
namespace,
|
|
144
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "agent" },
|
|
145
|
+
},
|
|
146
|
+
spec: {
|
|
147
|
+
selector: { matchLabels: { "app.kubernetes.io/name": name } },
|
|
148
|
+
template: {
|
|
149
|
+
metadata: { labels: { "app.kubernetes.io/name": name, ...extraLabels } },
|
|
150
|
+
spec: {
|
|
151
|
+
serviceAccountName: saName,
|
|
152
|
+
containers: [container],
|
|
153
|
+
volumes: [
|
|
154
|
+
{ name: "config", configMap: { name: configMapName } },
|
|
155
|
+
],
|
|
156
|
+
tolerations: [{ operator: "Exists" }],
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const saLabels: Record<string, string> = {
|
|
163
|
+
...commonLabels,
|
|
164
|
+
"app.kubernetes.io/component": "agent",
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
if (clientId) {
|
|
168
|
+
saLabels["azure.workload.identity/use"] = "true";
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const serviceAccountProps: Record<string, unknown> = {
|
|
172
|
+
metadata: {
|
|
173
|
+
name: saName,
|
|
174
|
+
namespace,
|
|
175
|
+
labels: saLabels,
|
|
176
|
+
...(clientId ? { annotations: { "azure.workload.identity/client-id": clientId } } : {}),
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const clusterRoleProps: Record<string, unknown> = {
|
|
181
|
+
metadata: {
|
|
182
|
+
name: clusterRoleName,
|
|
183
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
|
|
184
|
+
},
|
|
185
|
+
rules: [
|
|
186
|
+
{ apiGroups: [""], resources: ["pods", "nodes", "endpoints"], verbs: ["get", "list", "watch"] },
|
|
187
|
+
{ apiGroups: ["apps"], resources: ["replicasets"], verbs: ["get", "list", "watch"] },
|
|
188
|
+
{ apiGroups: ["batch"], resources: ["jobs"], verbs: ["get", "list", "watch"] },
|
|
189
|
+
{ apiGroups: [""], resources: ["nodes/proxy"], verbs: ["get"] },
|
|
190
|
+
{ apiGroups: [""], resources: ["nodes/stats", "configmaps", "events"], verbs: ["create", "get"] },
|
|
191
|
+
{ apiGroups: [""], resources: ["configmaps"], verbs: ["get", "update", "create"], resourceNames: ["otel-container-insight-clusterleader"] },
|
|
192
|
+
],
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const clusterRoleBindingProps: Record<string, unknown> = {
|
|
196
|
+
metadata: {
|
|
197
|
+
name: bindingName,
|
|
198
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
|
|
199
|
+
},
|
|
200
|
+
roleRef: {
|
|
201
|
+
apiGroup: "rbac.authorization.k8s.io",
|
|
202
|
+
kind: "ClusterRole",
|
|
203
|
+
name: clusterRoleName,
|
|
204
|
+
},
|
|
205
|
+
subjects: [
|
|
206
|
+
{
|
|
207
|
+
kind: "ServiceAccount",
|
|
208
|
+
name: saName,
|
|
209
|
+
namespace,
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const configMapProps: Record<string, unknown> = {
|
|
215
|
+
metadata: {
|
|
216
|
+
name: configMapName,
|
|
217
|
+
namespace,
|
|
218
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "config" },
|
|
219
|
+
},
|
|
220
|
+
data: {
|
|
221
|
+
"config.yaml": collectorConfig,
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
daemonSet: daemonSetProps,
|
|
227
|
+
serviceAccount: serviceAccountProps,
|
|
228
|
+
clusterRole: clusterRoleProps,
|
|
229
|
+
clusterRoleBinding: clusterRoleBindingProps,
|
|
230
|
+
configMap: configMapProps,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BatchJob composite — Job + optional RBAC (ServiceAccount + Role + RoleBinding).
|
|
3
|
+
*
|
|
4
|
+
* A higher-level construct for one-shot batch jobs (data migrations,
|
|
5
|
+
* seed tasks, backups). For scheduled workloads, use CronWorkload instead.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ContainerSecurityContext } from "./security-context";
|
|
9
|
+
|
|
10
|
+
/** Parse a K8s memory string (e.g. "256Mi", "1Gi") to bytes for comparison. */
|
|
11
|
+
function parseMemoryBytes(mem: string): number {
|
|
12
|
+
const match = mem.match(/^(\d+(?:\.\d+)?)\s*(Ki|Mi|Gi|Ti|k|M|G|T|[eE]\d+)?$/);
|
|
13
|
+
if (!match) return 0;
|
|
14
|
+
const value = parseFloat(match[1]);
|
|
15
|
+
const unit = match[2] ?? "";
|
|
16
|
+
const multipliers: Record<string, number> = {
|
|
17
|
+
"": 1, Ki: 1024, Mi: 1024 ** 2, Gi: 1024 ** 3, Ti: 1024 ** 4,
|
|
18
|
+
k: 1e3, M: 1e6, G: 1e9, T: 1e12,
|
|
19
|
+
};
|
|
20
|
+
if (unit.startsWith("e") || unit.startsWith("E")) return value * 10 ** parseInt(unit.slice(1));
|
|
21
|
+
return value * (multipliers[unit] ?? 1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Parse a K8s CPU string (e.g. "500m", "1") to millicores for comparison. */
|
|
25
|
+
function parseCpuMillis(cpu: string): number {
|
|
26
|
+
if (cpu.endsWith("m")) return parseFloat(cpu.slice(0, -1));
|
|
27
|
+
return parseFloat(cpu) * 1000;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface BatchJobProps {
|
|
31
|
+
/** Job name — used in metadata and labels. */
|
|
32
|
+
name: string;
|
|
33
|
+
/** Container image. */
|
|
34
|
+
image: string;
|
|
35
|
+
/** Command to run in the container. */
|
|
36
|
+
command?: string[];
|
|
37
|
+
/** Arguments to the command. */
|
|
38
|
+
args?: string[];
|
|
39
|
+
/** Number of retries before marking the job as failed (default: 6). */
|
|
40
|
+
backoffLimit?: number;
|
|
41
|
+
/** Seconds after completion before the job is eligible for GC. */
|
|
42
|
+
ttlSecondsAfterFinished?: number;
|
|
43
|
+
/** Number of completions required (default: 1). */
|
|
44
|
+
completions?: number;
|
|
45
|
+
/** Number of pods running in parallel (default: 1). */
|
|
46
|
+
parallelism?: number;
|
|
47
|
+
/** Restart policy (default: "OnFailure"). */
|
|
48
|
+
restartPolicy?: string;
|
|
49
|
+
/** RBAC rules for the service account. Omit for defaults; pass [] to skip RBAC entirely. */
|
|
50
|
+
rbacRules?: Array<{
|
|
51
|
+
apiGroups: string[];
|
|
52
|
+
resources: string[];
|
|
53
|
+
verbs: string[];
|
|
54
|
+
}>;
|
|
55
|
+
/** Additional labels to apply to all resources. */
|
|
56
|
+
labels?: Record<string, string>;
|
|
57
|
+
/** Namespace for all resources. */
|
|
58
|
+
namespace?: string;
|
|
59
|
+
/** CPU request (default: "100m"). */
|
|
60
|
+
cpuRequest?: string;
|
|
61
|
+
/** Memory request (default: "128Mi"). */
|
|
62
|
+
memoryRequest?: string;
|
|
63
|
+
/** CPU limit (default: "500m"). */
|
|
64
|
+
cpuLimit?: string;
|
|
65
|
+
/** Memory limit (default: "256Mi"). */
|
|
66
|
+
memoryLimit?: string;
|
|
67
|
+
/** Environment variables for the container. */
|
|
68
|
+
env?: Array<{ name: string; value: string }>;
|
|
69
|
+
/** Container security context (supports PSS restricted fields). */
|
|
70
|
+
securityContext?: ContainerSecurityContext;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface BatchJobResult {
|
|
74
|
+
job: Record<string, unknown>;
|
|
75
|
+
serviceAccount?: Record<string, unknown>;
|
|
76
|
+
role?: Record<string, unknown>;
|
|
77
|
+
roleBinding?: Record<string, unknown>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create a BatchJob composite — returns prop objects for
|
|
82
|
+
* a Job, and optional ServiceAccount, Role, and RoleBinding.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* import { BatchJob } from "@intentius/chant-lexicon-k8s";
|
|
87
|
+
*
|
|
88
|
+
* const { job, serviceAccount, role, roleBinding } = BatchJob({
|
|
89
|
+
* name: "db-migrate",
|
|
90
|
+
* image: "migrate:1.0",
|
|
91
|
+
* command: ["python", "manage.py", "migrate"],
|
|
92
|
+
* backoffLimit: 3,
|
|
93
|
+
* ttlSecondsAfterFinished: 3600,
|
|
94
|
+
* });
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export function BatchJob(props: BatchJobProps): BatchJobResult {
|
|
98
|
+
const {
|
|
99
|
+
name,
|
|
100
|
+
image,
|
|
101
|
+
command,
|
|
102
|
+
args,
|
|
103
|
+
backoffLimit = 6,
|
|
104
|
+
ttlSecondsAfterFinished,
|
|
105
|
+
completions = 1,
|
|
106
|
+
parallelism = 1,
|
|
107
|
+
restartPolicy = "OnFailure",
|
|
108
|
+
rbacRules,
|
|
109
|
+
labels: extraLabels = {},
|
|
110
|
+
namespace,
|
|
111
|
+
cpuRequest = "100m",
|
|
112
|
+
memoryRequest: rawMemoryRequest = "128Mi",
|
|
113
|
+
cpuLimit: rawCpuLimit = "500m",
|
|
114
|
+
memoryLimit: rawMemoryLimit = "256Mi",
|
|
115
|
+
env,
|
|
116
|
+
securityContext,
|
|
117
|
+
} = props;
|
|
118
|
+
|
|
119
|
+
// Ensure limits >= requests (K8s rejects pods where request > limit).
|
|
120
|
+
const memoryRequest = rawMemoryRequest;
|
|
121
|
+
const memoryLimit = parseMemoryBytes(rawMemoryRequest) > parseMemoryBytes(rawMemoryLimit)
|
|
122
|
+
? rawMemoryRequest : rawMemoryLimit;
|
|
123
|
+
const cpuLimit = parseCpuMillis(cpuRequest) > parseCpuMillis(rawCpuLimit)
|
|
124
|
+
? cpuRequest : rawCpuLimit;
|
|
125
|
+
|
|
126
|
+
const saName = `${name}-sa`;
|
|
127
|
+
const roleName = `${name}-role`;
|
|
128
|
+
const bindingName = `${name}-binding`;
|
|
129
|
+
|
|
130
|
+
const commonLabels: Record<string, string> = {
|
|
131
|
+
"app.kubernetes.io/name": name,
|
|
132
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
133
|
+
...extraLabels,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// undefined → default RBAC rules; explicit [] → no RBAC resources
|
|
137
|
+
const createRbac = rbacRules === undefined || rbacRules.length > 0;
|
|
138
|
+
const effectiveRbacRules = rbacRules === undefined
|
|
139
|
+
? [{ apiGroups: [""], resources: ["pods"], verbs: ["get", "list"] }]
|
|
140
|
+
: rbacRules;
|
|
141
|
+
|
|
142
|
+
const container: Record<string, unknown> = {
|
|
143
|
+
name,
|
|
144
|
+
image,
|
|
145
|
+
...(command && { command }),
|
|
146
|
+
...(args && { args }),
|
|
147
|
+
resources: {
|
|
148
|
+
limits: { cpu: cpuLimit, memory: memoryLimit },
|
|
149
|
+
requests: { cpu: cpuRequest, memory: memoryRequest },
|
|
150
|
+
},
|
|
151
|
+
...(env && { env }),
|
|
152
|
+
...(securityContext && { securityContext }),
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const jobProps: Record<string, unknown> = {
|
|
156
|
+
metadata: {
|
|
157
|
+
name,
|
|
158
|
+
...(namespace && { namespace }),
|
|
159
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "batch" },
|
|
160
|
+
},
|
|
161
|
+
spec: {
|
|
162
|
+
backoffLimit,
|
|
163
|
+
completions,
|
|
164
|
+
parallelism,
|
|
165
|
+
...(ttlSecondsAfterFinished !== undefined && { ttlSecondsAfterFinished }),
|
|
166
|
+
template: {
|
|
167
|
+
metadata: { labels: { "app.kubernetes.io/name": name, ...extraLabels } },
|
|
168
|
+
spec: {
|
|
169
|
+
...(createRbac && { serviceAccountName: saName }),
|
|
170
|
+
restartPolicy,
|
|
171
|
+
containers: [container],
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const result: BatchJobResult = {
|
|
178
|
+
job: jobProps,
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
if (createRbac) {
|
|
182
|
+
result.serviceAccount = {
|
|
183
|
+
metadata: {
|
|
184
|
+
name: saName,
|
|
185
|
+
...(namespace && { namespace }),
|
|
186
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "batch" },
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
result.role = {
|
|
191
|
+
metadata: {
|
|
192
|
+
name: roleName,
|
|
193
|
+
...(namespace && { namespace }),
|
|
194
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
|
|
195
|
+
},
|
|
196
|
+
rules: effectiveRbacRules,
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
result.roleBinding = {
|
|
200
|
+
metadata: {
|
|
201
|
+
name: bindingName,
|
|
202
|
+
...(namespace && { namespace }),
|
|
203
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
|
|
204
|
+
},
|
|
205
|
+
roleRef: {
|
|
206
|
+
apiGroup: "rbac.authorization.k8s.io",
|
|
207
|
+
kind: "Role",
|
|
208
|
+
name: roleName,
|
|
209
|
+
},
|
|
210
|
+
subjects: [
|
|
211
|
+
{
|
|
212
|
+
kind: "ServiceAccount",
|
|
213
|
+
name: saName,
|
|
214
|
+
...(namespace && { namespace }),
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return result;
|
|
221
|
+
}
|