@intentius/chant-lexicon-k8s 0.0.14 → 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 +6 -3
- 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/package.json +27 -24
- package/src/codegen/docs.ts +1 -1
- package/src/composites/adot-collector.ts +8 -2
- package/src/composites/agic-ingress.ts +149 -0
- package/src/composites/alb-ingress.ts +2 -1
- package/src/composites/autoscaled-service.ts +25 -7
- 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 +36 -3
- package/src/composites/composites.test.ts +701 -0
- package/src/composites/config-connector-context.ts +62 -0
- package/src/composites/configured-app.ts +6 -0
- package/src/composites/cron-workload.ts +6 -0
- package/src/composites/ebs-storage-class.ts +4 -4
- package/src/composites/external-dns-agent.ts +6 -0
- package/src/composites/filestore-storage-class.ts +79 -0
- package/src/composites/fluent-bit-agent.ts +5 -0
- package/src/composites/gce-pd-storage-class.ts +85 -0
- package/src/composites/gke-gateway.ts +143 -0
- package/src/composites/index.ts +19 -0
- package/src/composites/metrics-server.ts +1 -1
- package/src/composites/monitored-service.ts +6 -0
- package/src/composites/network-isolated-app.ts +6 -0
- package/src/composites/node-agent.ts +6 -0
- package/src/composites/security-context.ts +10 -0
- package/src/composites/sidecar-app.ts +6 -0
- package/src/composites/stateful-app.ts +4 -7
- package/src/composites/web-app.ts +4 -7
- package/src/composites/worker-pool.ts +4 -7
- package/src/composites/workload-identity-sa.ts +118 -0
- package/src/composites/workload-identity-service-account.ts +116 -0
- package/src/index.ts +6 -1
- 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 +4 -1
- package/src/serializer.test.ts +120 -0
- package/src/serializer.ts +16 -4
package/dist/integrity.json
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "xxhash64",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "490e9ca9417db298",
|
|
5
5
|
"meta.json": "1ce194f36f9b5f90",
|
|
6
6
|
"types/index.d.ts": "beec4cc869064186",
|
|
7
7
|
"rules/hardcoded-namespace.ts": "54b216c71018e101",
|
|
8
8
|
"rules/wk8201.ts": "4dbbd20e21b5fa04",
|
|
9
|
-
"rules/wk8204.ts": "
|
|
9
|
+
"rules/wk8204.ts": "9244d6fdd6d2f7d",
|
|
10
10
|
"rules/wk8301.ts": "283265ab0c5b8511",
|
|
11
|
+
"rules/wk8306.ts": "5338575bfb0f9251",
|
|
11
12
|
"rules/wk8102.ts": "78d4aac387107b56",
|
|
13
|
+
"rules/wk8305.ts": "5c4b9482a0f3b91d",
|
|
12
14
|
"rules/wk8101.ts": "f8ffcf6e5c89076b",
|
|
13
15
|
"rules/wk8302.ts": "a80d1eab37c0dbe4",
|
|
14
16
|
"rules/wk8005.ts": "a9a1b93b80f3aa51",
|
|
@@ -21,6 +23,7 @@
|
|
|
21
23
|
"rules/wk8042.ts": "6064c84481ae8551",
|
|
22
24
|
"rules/wk8006.ts": "6e04f754f79f076e",
|
|
23
25
|
"rules/wk8041.ts": "4df512c93caaef50",
|
|
26
|
+
"rules/wk8304.ts": "f51bf894c5a08dbe",
|
|
24
27
|
"rules/wk8202.ts": "6bd950ae2128256c",
|
|
25
28
|
"rules/wk8208.ts": "1133f9e53c174ae9",
|
|
26
29
|
"rules/wk8105.ts": "8dbcfe399f23656a",
|
|
@@ -30,5 +33,5 @@
|
|
|
30
33
|
"skills/chant-k8s-eks.md": "f79f31f058c7f2ed",
|
|
31
34
|
"skills/chant-k8s-patterns.md": "c5151ed799145c4b"
|
|
32
35
|
},
|
|
33
|
-
"composite": "
|
|
36
|
+
"composite": "2a6c7f09f87d9a38"
|
|
34
37
|
}
|
package/dist/manifest.json
CHANGED
package/dist/rules/wk8204.ts
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
* Containers should set securityContext.runAsNonRoot to true at either
|
|
5
5
|
* the container level or pod level. Running as root inside a container
|
|
6
6
|
* increases the blast radius of a container breakout.
|
|
7
|
+
*
|
|
8
|
+
* Additionally warns when runAsNonRoot: true is set but no explicit
|
|
9
|
+
* runAsUser is provided — without a numeric UID, K8s relies on the
|
|
10
|
+
* image's USER directive, which may be root (UID 0).
|
|
7
11
|
*/
|
|
8
12
|
|
|
9
13
|
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
@@ -30,13 +34,17 @@ export const wk8204: PostSynthCheck = {
|
|
|
30
34
|
// Check pod-level securityContext
|
|
31
35
|
const podSecCtx = podSpec.securityContext as Record<string, unknown> | undefined;
|
|
32
36
|
const podRunAsNonRoot = podSecCtx?.runAsNonRoot === true;
|
|
37
|
+
const podRunAsUser = podSecCtx?.runAsUser;
|
|
33
38
|
|
|
34
39
|
const containers = extractContainers(manifest);
|
|
35
40
|
for (const container of containers) {
|
|
36
41
|
const secCtx = container.securityContext;
|
|
37
42
|
const containerRunAsNonRoot = secCtx?.runAsNonRoot === true;
|
|
43
|
+
const containerRunAsUser = secCtx?.runAsUser;
|
|
44
|
+
|
|
45
|
+
const hasRunAsNonRoot = podRunAsNonRoot || containerRunAsNonRoot;
|
|
38
46
|
|
|
39
|
-
if (!
|
|
47
|
+
if (!hasRunAsNonRoot) {
|
|
40
48
|
diagnostics.push({
|
|
41
49
|
checkId: "WK8204",
|
|
42
50
|
severity: "warning",
|
|
@@ -44,6 +52,30 @@ export const wk8204: PostSynthCheck = {
|
|
|
44
52
|
entity: resourceName,
|
|
45
53
|
lexicon: "k8s",
|
|
46
54
|
});
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// runAsNonRoot is true — check for explicit runAsUser
|
|
59
|
+
const effectiveRunAsUser = containerRunAsUser ?? podRunAsUser;
|
|
60
|
+
|
|
61
|
+
if (effectiveRunAsUser === 0) {
|
|
62
|
+
// Contradictory: runAsNonRoot: true + runAsUser: 0
|
|
63
|
+
diagnostics.push({
|
|
64
|
+
checkId: "WK8204",
|
|
65
|
+
severity: "warning",
|
|
66
|
+
message: `Container "${container.name ?? "(unnamed)"}" in ${manifest.kind} "${resourceName}" has runAsNonRoot: true but runAsUser: 0 — these settings are contradictory and the container will fail to start`,
|
|
67
|
+
entity: resourceName,
|
|
68
|
+
lexicon: "k8s",
|
|
69
|
+
});
|
|
70
|
+
} else if (effectiveRunAsUser === undefined || effectiveRunAsUser === null) {
|
|
71
|
+
// runAsNonRoot: true but no explicit UID
|
|
72
|
+
diagnostics.push({
|
|
73
|
+
checkId: "WK8204",
|
|
74
|
+
severity: "warning",
|
|
75
|
+
message: `Container "${container.name ?? "(unnamed)"}" in ${manifest.kind} "${resourceName}" has runAsNonRoot: true but no explicit runAsUser — set a numeric UID to ensure the container doesn't run as root`,
|
|
76
|
+
entity: resourceName,
|
|
77
|
+
lexicon: "k8s",
|
|
78
|
+
});
|
|
47
79
|
}
|
|
48
80
|
}
|
|
49
81
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WK8304: SSL Redirect Without Certificate
|
|
3
|
+
*
|
|
4
|
+
* Flags Ingress resources that have `alb.ingress.kubernetes.io/ssl-redirect`
|
|
5
|
+
* annotation set but are missing `alb.ingress.kubernetes.io/certificate-arn`
|
|
6
|
+
* or don't have HTTPS in their listen-ports.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
10
|
+
import { getPrimaryOutput, parseK8sManifests } from "./k8s-helpers";
|
|
11
|
+
|
|
12
|
+
export const wk8304: PostSynthCheck = {
|
|
13
|
+
id: "WK8304",
|
|
14
|
+
description: "SSL redirect without certificate — ssl-redirect annotation requires a valid certificate-arn and HTTPS listen-ports",
|
|
15
|
+
|
|
16
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
17
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
18
|
+
|
|
19
|
+
for (const [, output] of ctx.outputs) {
|
|
20
|
+
const yaml = getPrimaryOutput(output);
|
|
21
|
+
const manifests = parseK8sManifests(yaml);
|
|
22
|
+
|
|
23
|
+
for (const manifest of manifests) {
|
|
24
|
+
if (manifest.kind !== "Ingress") continue;
|
|
25
|
+
|
|
26
|
+
const annotations = manifest.metadata?.annotations as Record<string, string> | undefined;
|
|
27
|
+
if (!annotations) continue;
|
|
28
|
+
|
|
29
|
+
const sslRedirect = annotations["alb.ingress.kubernetes.io/ssl-redirect"];
|
|
30
|
+
if (!sslRedirect) continue;
|
|
31
|
+
|
|
32
|
+
const resourceName = manifest.metadata?.name ?? "Ingress";
|
|
33
|
+
const certArn = annotations["alb.ingress.kubernetes.io/certificate-arn"];
|
|
34
|
+
|
|
35
|
+
if (!certArn) {
|
|
36
|
+
diagnostics.push({
|
|
37
|
+
checkId: "WK8304",
|
|
38
|
+
severity: "warning",
|
|
39
|
+
message: `Ingress "${resourceName}" has ssl-redirect annotation but no certificate-arn — HTTPS redirect will fail without a TLS certificate`,
|
|
40
|
+
entity: resourceName,
|
|
41
|
+
lexicon: "k8s",
|
|
42
|
+
});
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check listen-ports includes HTTPS
|
|
47
|
+
const listenPorts = annotations["alb.ingress.kubernetes.io/listen-ports"];
|
|
48
|
+
if (listenPorts) {
|
|
49
|
+
try {
|
|
50
|
+
const ports = JSON.parse(listenPorts) as Array<Record<string, number>>;
|
|
51
|
+
const hasHttps = ports.some((p) => "HTTPS" in p);
|
|
52
|
+
if (!hasHttps) {
|
|
53
|
+
diagnostics.push({
|
|
54
|
+
checkId: "WK8304",
|
|
55
|
+
severity: "warning",
|
|
56
|
+
message: `Ingress "${resourceName}" has ssl-redirect but listen-ports does not include HTTPS`,
|
|
57
|
+
entity: resourceName,
|
|
58
|
+
lexicon: "k8s",
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
// Can't parse listen-ports — skip this check
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return diagnostics;
|
|
69
|
+
},
|
|
70
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WK8305: Ingress Port Not Matching Service
|
|
3
|
+
*
|
|
4
|
+
* Flags Ingress backends whose `service.port.number` does not match
|
|
5
|
+
* any declared port on the referenced Service in the manifest set.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
9
|
+
import { getPrimaryOutput, parseK8sManifests } from "./k8s-helpers";
|
|
10
|
+
import type { K8sManifest } from "./k8s-helpers";
|
|
11
|
+
|
|
12
|
+
export const wk8305: PostSynthCheck = {
|
|
13
|
+
id: "WK8305",
|
|
14
|
+
description: "Ingress port not matching Service — backend port must match a declared Service port",
|
|
15
|
+
|
|
16
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
17
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
18
|
+
|
|
19
|
+
for (const [, output] of ctx.outputs) {
|
|
20
|
+
const yaml = getPrimaryOutput(output);
|
|
21
|
+
const manifests = parseK8sManifests(yaml);
|
|
22
|
+
|
|
23
|
+
// Build a map of Service name+namespace → set of port numbers
|
|
24
|
+
const servicePorts = collectServicePorts(manifests);
|
|
25
|
+
|
|
26
|
+
for (const manifest of manifests) {
|
|
27
|
+
if (manifest.kind !== "Ingress") continue;
|
|
28
|
+
|
|
29
|
+
const ingressName = manifest.metadata?.name ?? "Ingress";
|
|
30
|
+
const ingressNamespace = manifest.metadata?.namespace ?? "default";
|
|
31
|
+
const spec = manifest.spec;
|
|
32
|
+
if (!spec) continue;
|
|
33
|
+
|
|
34
|
+
const rules = spec.rules as Array<Record<string, unknown>> | undefined;
|
|
35
|
+
if (!rules) continue;
|
|
36
|
+
|
|
37
|
+
for (const rule of rules) {
|
|
38
|
+
const http = rule.http as Record<string, unknown> | undefined;
|
|
39
|
+
if (!http) continue;
|
|
40
|
+
|
|
41
|
+
const paths = http.paths as Array<Record<string, unknown>> | undefined;
|
|
42
|
+
if (!paths) continue;
|
|
43
|
+
|
|
44
|
+
for (const pathEntry of paths) {
|
|
45
|
+
const backend = pathEntry.backend as Record<string, unknown> | undefined;
|
|
46
|
+
if (!backend) continue;
|
|
47
|
+
|
|
48
|
+
const service = backend.service as Record<string, unknown> | undefined;
|
|
49
|
+
if (!service) continue;
|
|
50
|
+
|
|
51
|
+
const svcName = service.name as string | undefined;
|
|
52
|
+
const port = service.port as Record<string, unknown> | undefined;
|
|
53
|
+
const portNumber = port?.number as number | undefined;
|
|
54
|
+
|
|
55
|
+
if (!svcName || portNumber === undefined) continue;
|
|
56
|
+
|
|
57
|
+
// Look up the Service in the manifest set
|
|
58
|
+
const key = `${ingressNamespace}/${svcName}`;
|
|
59
|
+
const knownPorts = servicePorts.get(key);
|
|
60
|
+
|
|
61
|
+
// Skip if the Service is not in the manifest set (external service)
|
|
62
|
+
if (!knownPorts) continue;
|
|
63
|
+
|
|
64
|
+
if (!knownPorts.has(portNumber)) {
|
|
65
|
+
diagnostics.push({
|
|
66
|
+
checkId: "WK8305",
|
|
67
|
+
severity: "warning",
|
|
68
|
+
message: `Ingress "${ingressName}" references Service "${svcName}" port ${portNumber}, but the Service only declares ports [${[...knownPorts].join(", ")}]`,
|
|
69
|
+
entity: ingressName,
|
|
70
|
+
lexicon: "k8s",
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return diagnostics;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Collect port numbers from all Service manifests, keyed by namespace/name.
|
|
84
|
+
*/
|
|
85
|
+
function collectServicePorts(manifests: K8sManifest[]): Map<string, Set<number>> {
|
|
86
|
+
const result = new Map<string, Set<number>>();
|
|
87
|
+
|
|
88
|
+
for (const manifest of manifests) {
|
|
89
|
+
if (manifest.kind !== "Service") continue;
|
|
90
|
+
|
|
91
|
+
const name = manifest.metadata?.name;
|
|
92
|
+
const namespace = manifest.metadata?.namespace ?? "default";
|
|
93
|
+
if (!name) continue;
|
|
94
|
+
|
|
95
|
+
const key = `${namespace}/${name}`;
|
|
96
|
+
const ports = new Set<number>();
|
|
97
|
+
|
|
98
|
+
const spec = manifest.spec;
|
|
99
|
+
if (spec) {
|
|
100
|
+
const specPorts = spec.ports as Array<Record<string, unknown>> | undefined;
|
|
101
|
+
if (specPorts) {
|
|
102
|
+
for (const p of specPorts) {
|
|
103
|
+
const port = p.port as number | undefined;
|
|
104
|
+
if (port !== undefined) {
|
|
105
|
+
ports.add(port);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
result.set(key, ports);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WK8306: Container Command Starts With Flag
|
|
3
|
+
*
|
|
4
|
+
* If `command[0]` starts with `-` or `--`, it's almost certainly a mistake —
|
|
5
|
+
* the first element should be the binary/entrypoint, flags belong in `args`.
|
|
6
|
+
* This causes OCI runtime errors because the container runtime tries to
|
|
7
|
+
* execute the flag as a binary.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
11
|
+
import { getPrimaryOutput, parseK8sManifests, extractContainers, WORKLOAD_KINDS } from "./k8s-helpers";
|
|
12
|
+
|
|
13
|
+
export const wk8306: PostSynthCheck = {
|
|
14
|
+
id: "WK8306",
|
|
15
|
+
description: "Container command starts with flag — first element should be a binary, not a flag",
|
|
16
|
+
|
|
17
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
18
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
19
|
+
|
|
20
|
+
for (const [, output] of ctx.outputs) {
|
|
21
|
+
const yaml = getPrimaryOutput(output);
|
|
22
|
+
const manifests = parseK8sManifests(yaml);
|
|
23
|
+
|
|
24
|
+
for (const manifest of manifests) {
|
|
25
|
+
if (!manifest.kind || !WORKLOAD_KINDS.has(manifest.kind)) continue;
|
|
26
|
+
|
|
27
|
+
const resourceName = manifest.metadata?.name ?? manifest.kind;
|
|
28
|
+
const containers = extractContainers(manifest);
|
|
29
|
+
|
|
30
|
+
for (const container of containers) {
|
|
31
|
+
const command = (container as Record<string, unknown>).command as unknown[] | undefined;
|
|
32
|
+
if (!Array.isArray(command) || command.length === 0) continue;
|
|
33
|
+
|
|
34
|
+
const firstArg = String(command[0]);
|
|
35
|
+
if (firstArg.startsWith("-")) {
|
|
36
|
+
diagnostics.push({
|
|
37
|
+
checkId: "WK8306",
|
|
38
|
+
severity: "error",
|
|
39
|
+
message: `Container "${container.name ?? "(unnamed)"}" in ${manifest.kind} "${resourceName}" has command[0]="${firstArg}" which starts with a flag — the first element should be the binary, flags belong in args`,
|
|
40
|
+
entity: resourceName,
|
|
41
|
+
lexicon: "k8s",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return diagnostics;
|
|
49
|
+
},
|
|
50
|
+
};
|
package/package.json
CHANGED
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-k8s",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"files": [
|
|
6
|
+
"files": [
|
|
7
|
+
"src/",
|
|
8
|
+
"dist/"
|
|
9
|
+
],
|
|
7
10
|
"publishConfig": {
|
|
8
|
-
|
|
9
|
-
},
|
|
10
|
-
"exports": {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
},
|
|
17
|
-
"scripts": {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
},
|
|
24
|
-
"dependencies": {
|
|
25
|
-
|
|
26
|
-
},
|
|
27
|
-
"devDependencies": {
|
|
28
|
-
|
|
29
|
-
}
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./src/index.ts",
|
|
15
|
+
"./*": "./src/*.ts",
|
|
16
|
+
"./manifest": "./dist/manifest.json",
|
|
17
|
+
"./meta": "./dist/meta.json",
|
|
18
|
+
"./types": "./dist/types/index.d.ts"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"generate": "bun run src/codegen/generate-cli.ts",
|
|
22
|
+
"bundle": "bun run src/package-cli.ts",
|
|
23
|
+
"validate": "bun run src/validate-cli.ts",
|
|
24
|
+
"docs": "bun run src/codegen/docs-cli.ts",
|
|
25
|
+
"prepack": "bun run generate && bun run bundle && bun run validate"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@intentius/chant": "0.0.15"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"typescript": "^5.9.3"
|
|
32
|
+
}
|
|
30
33
|
}
|
package/src/codegen/docs.ts
CHANGED
|
@@ -1102,7 +1102,7 @@ Skills are structured markdown documents bundled with a lexicon. When an AI agen
|
|
|
1102
1102
|
|
|
1103
1103
|
## Installation
|
|
1104
1104
|
|
|
1105
|
-
When you scaffold a new project with \`chant init --lexicon k8s\`, the skill is installed to
|
|
1105
|
+
When you scaffold a new project with \`chant init --lexicon k8s\`, the skill is installed to \`skills/chant-k8s/SKILL.md\` for automatic discovery by Claude Code.
|
|
1106
1106
|
|
|
1107
1107
|
## Skill: chant-k8s
|
|
1108
1108
|
|
|
@@ -129,7 +129,7 @@ service:
|
|
|
129
129
|
metrics:
|
|
130
130
|
receivers: [otlp]
|
|
131
131
|
processors: [batch]
|
|
132
|
-
exporters: [${exporterNames.join(", ")}]
|
|
132
|
+
exporters: [${exporterNames.filter((e) => e !== "awsxray").join(", ") || "awsemf"}]
|
|
133
133
|
traces:
|
|
134
134
|
receivers: [otlp]
|
|
135
135
|
processors: [batch]
|
|
@@ -139,7 +139,7 @@ service:
|
|
|
139
139
|
const container: Record<string, unknown> = {
|
|
140
140
|
name,
|
|
141
141
|
image,
|
|
142
|
-
|
|
142
|
+
args: ["--config=/etc/adot/config.yaml"],
|
|
143
143
|
ports: [
|
|
144
144
|
{ containerPort: 4317, name: "otlp-grpc" },
|
|
145
145
|
{ containerPort: 4318, name: "otlp-http" },
|
|
@@ -151,6 +151,12 @@ service:
|
|
|
151
151
|
volumeMounts: [
|
|
152
152
|
{ name: "config", mountPath: "/etc/adot", readOnly: true },
|
|
153
153
|
],
|
|
154
|
+
securityContext: {
|
|
155
|
+
runAsNonRoot: true,
|
|
156
|
+
runAsUser: 10001,
|
|
157
|
+
readOnlyRootFilesystem: true,
|
|
158
|
+
allowPrivilegeEscalation: false,
|
|
159
|
+
},
|
|
154
160
|
};
|
|
155
161
|
|
|
156
162
|
const daemonSetProps: Record<string, unknown> = {
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgicIngress composite — Ingress with Azure Application Gateway Ingress Controller annotations.
|
|
3
|
+
*
|
|
4
|
+
* @aks Full `appgw.ingress.kubernetes.io/*` annotation set including SSL redirect,
|
|
5
|
+
* WAF policy, backend path prefix, and cookie-based affinity.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface AgicIngressHost {
|
|
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 AgicIngressProps {
|
|
21
|
+
/** Ingress name — used in metadata and labels. */
|
|
22
|
+
name: string;
|
|
23
|
+
/** Host definitions with paths. */
|
|
24
|
+
hosts: AgicIngressHost[];
|
|
25
|
+
/** Azure Key Vault certificate URI or secret name for TLS. */
|
|
26
|
+
certificateArn?: string;
|
|
27
|
+
/** WAF policy resource ID. */
|
|
28
|
+
wafPolicyId?: string;
|
|
29
|
+
/** Health check path for backend. */
|
|
30
|
+
healthCheckPath?: string;
|
|
31
|
+
/** Enable HTTP->HTTPS redirect (default: true when certificateArn set). */
|
|
32
|
+
sslRedirect?: boolean;
|
|
33
|
+
/** Backend path prefix override. */
|
|
34
|
+
backendPathPrefix?: string;
|
|
35
|
+
/** Enable cookie-based affinity (default: false). */
|
|
36
|
+
cookieAffinity?: boolean;
|
|
37
|
+
/** Additional annotations on the Ingress. */
|
|
38
|
+
annotations?: Record<string, string>;
|
|
39
|
+
/** Additional labels to apply to all resources. */
|
|
40
|
+
labels?: Record<string, string>;
|
|
41
|
+
/** Namespace for all resources. */
|
|
42
|
+
namespace?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface AgicIngressResult {
|
|
46
|
+
ingress: Record<string, unknown>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Create an AgicIngress composite — returns prop objects for
|
|
51
|
+
* an Ingress with Azure Application Gateway Ingress Controller annotations.
|
|
52
|
+
*
|
|
53
|
+
* @aks
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* import { AgicIngress } from "@intentius/chant-lexicon-k8s";
|
|
57
|
+
*
|
|
58
|
+
* const { ingress } = AgicIngress({
|
|
59
|
+
* name: "api-ingress",
|
|
60
|
+
* hosts: [
|
|
61
|
+
* {
|
|
62
|
+
* hostname: "api.example.com",
|
|
63
|
+
* paths: [{ path: "/", serviceName: "api", servicePort: 80 }],
|
|
64
|
+
* },
|
|
65
|
+
* ],
|
|
66
|
+
* certificateArn: "keyvault-secret-name",
|
|
67
|
+
* wafPolicyId: "/subscriptions/.../Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies/my-waf",
|
|
68
|
+
* });
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export function AgicIngress(props: AgicIngressProps): AgicIngressResult {
|
|
72
|
+
const {
|
|
73
|
+
name,
|
|
74
|
+
hosts,
|
|
75
|
+
certificateArn,
|
|
76
|
+
wafPolicyId,
|
|
77
|
+
healthCheckPath,
|
|
78
|
+
sslRedirect,
|
|
79
|
+
backendPathPrefix,
|
|
80
|
+
cookieAffinity = false,
|
|
81
|
+
annotations: extraAnnotations = {},
|
|
82
|
+
labels: extraLabels = {},
|
|
83
|
+
namespace,
|
|
84
|
+
} = props;
|
|
85
|
+
|
|
86
|
+
const commonLabels: Record<string, string> = {
|
|
87
|
+
"app.kubernetes.io/name": name,
|
|
88
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
89
|
+
...extraLabels,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Build AGIC annotations
|
|
93
|
+
const annotations: Record<string, string> = {
|
|
94
|
+
"kubernetes.io/ingress.class": "azure/application-gateway",
|
|
95
|
+
...extraAnnotations,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (sslRedirect ?? (certificateArn !== undefined)) {
|
|
99
|
+
annotations["appgw.ingress.kubernetes.io/ssl-redirect"] = "true";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (certificateArn) {
|
|
103
|
+
annotations["appgw.ingress.kubernetes.io/appgw-ssl-certificate"] = certificateArn;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (wafPolicyId) {
|
|
107
|
+
annotations["appgw.ingress.kubernetes.io/waf-policy-for-path"] = wafPolicyId;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (healthCheckPath) {
|
|
111
|
+
annotations["appgw.ingress.kubernetes.io/health-probe-path"] = healthCheckPath;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (backendPathPrefix) {
|
|
115
|
+
annotations["appgw.ingress.kubernetes.io/backend-path-prefix"] = backendPathPrefix;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (cookieAffinity) {
|
|
119
|
+
annotations["appgw.ingress.kubernetes.io/cookie-based-affinity"] = "true";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const ingressRules = hosts.map((host) => ({
|
|
123
|
+
host: host.hostname,
|
|
124
|
+
http: {
|
|
125
|
+
paths: host.paths.map((p) => ({
|
|
126
|
+
path: p.path,
|
|
127
|
+
pathType: p.pathType ?? "Prefix",
|
|
128
|
+
backend: {
|
|
129
|
+
service: { name: p.serviceName, port: { number: p.servicePort } },
|
|
130
|
+
},
|
|
131
|
+
})),
|
|
132
|
+
},
|
|
133
|
+
}));
|
|
134
|
+
|
|
135
|
+
const ingressProps: Record<string, unknown> = {
|
|
136
|
+
metadata: {
|
|
137
|
+
name,
|
|
138
|
+
...(namespace && { namespace }),
|
|
139
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "ingress" },
|
|
140
|
+
annotations,
|
|
141
|
+
},
|
|
142
|
+
spec: {
|
|
143
|
+
ingressClassName: "azure/application-gateway",
|
|
144
|
+
rules: ingressRules,
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
return { ingress: ingressProps };
|
|
149
|
+
}
|
|
@@ -13,6 +13,7 @@ export interface AlbIngressHost {
|
|
|
13
13
|
path: string;
|
|
14
14
|
pathType?: string;
|
|
15
15
|
serviceName: string;
|
|
16
|
+
/** Port on the Kubernetes Service (not the container port). */
|
|
16
17
|
servicePort: number;
|
|
17
18
|
}>;
|
|
18
19
|
}
|
|
@@ -105,7 +106,7 @@ export function AlbIngress(props: AlbIngressProps): AlbIngressResult {
|
|
|
105
106
|
annotations["alb.ingress.kubernetes.io/listen-ports"] = '[{"HTTPS":443}]';
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
if (sslRedirect ??
|
|
109
|
+
if (sslRedirect ?? !!certificateArn) {
|
|
109
110
|
annotations["alb.ingress.kubernetes.io/ssl-redirect"] = "443";
|
|
110
111
|
}
|
|
111
112
|
|