@intentius/chant-lexicon-k8s 0.0.12

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 (123) hide show
  1. package/dist/integrity.json +32 -0
  2. package/dist/manifest.json +8 -0
  3. package/dist/meta.json +1413 -0
  4. package/dist/rules/hardcoded-namespace.ts +56 -0
  5. package/dist/rules/k8s-helpers.ts +149 -0
  6. package/dist/rules/wk8005.ts +59 -0
  7. package/dist/rules/wk8006.ts +68 -0
  8. package/dist/rules/wk8041.ts +73 -0
  9. package/dist/rules/wk8042.ts +48 -0
  10. package/dist/rules/wk8101.ts +65 -0
  11. package/dist/rules/wk8102.ts +42 -0
  12. package/dist/rules/wk8103.ts +45 -0
  13. package/dist/rules/wk8104.ts +69 -0
  14. package/dist/rules/wk8105.ts +45 -0
  15. package/dist/rules/wk8201.ts +55 -0
  16. package/dist/rules/wk8202.ts +46 -0
  17. package/dist/rules/wk8203.ts +46 -0
  18. package/dist/rules/wk8204.ts +54 -0
  19. package/dist/rules/wk8205.ts +56 -0
  20. package/dist/rules/wk8207.ts +45 -0
  21. package/dist/rules/wk8208.ts +45 -0
  22. package/dist/rules/wk8209.ts +45 -0
  23. package/dist/rules/wk8301.ts +51 -0
  24. package/dist/rules/wk8302.ts +46 -0
  25. package/dist/rules/wk8303.ts +96 -0
  26. package/dist/skills/chant-k8s.md +433 -0
  27. package/dist/types/index.d.ts +2934 -0
  28. package/package.json +30 -0
  29. package/src/actions/actions.test.ts +83 -0
  30. package/src/actions/apps.ts +23 -0
  31. package/src/actions/batch.ts +9 -0
  32. package/src/actions/core.ts +62 -0
  33. package/src/actions/index.ts +50 -0
  34. package/src/actions/networking.ts +15 -0
  35. package/src/actions/rbac.ts +13 -0
  36. package/src/codegen/docs-cli.ts +3 -0
  37. package/src/codegen/docs.ts +1147 -0
  38. package/src/codegen/generate-cli.ts +41 -0
  39. package/src/codegen/generate-lexicon.ts +69 -0
  40. package/src/codegen/generate-typescript.ts +97 -0
  41. package/src/codegen/generate.ts +144 -0
  42. package/src/codegen/naming.test.ts +63 -0
  43. package/src/codegen/naming.ts +187 -0
  44. package/src/codegen/package.ts +56 -0
  45. package/src/codegen/patches.ts +108 -0
  46. package/src/codegen/snapshot.test.ts +95 -0
  47. package/src/codegen/typecheck.test.ts +24 -0
  48. package/src/codegen/typecheck.ts +4 -0
  49. package/src/codegen/versions.ts +43 -0
  50. package/src/composites/autoscaled-service.ts +236 -0
  51. package/src/composites/composites.test.ts +1109 -0
  52. package/src/composites/cron-workload.ts +167 -0
  53. package/src/composites/index.ts +14 -0
  54. package/src/composites/namespace-env.ts +163 -0
  55. package/src/composites/node-agent.ts +224 -0
  56. package/src/composites/stateful-app.ts +134 -0
  57. package/src/composites/web-app.ts +180 -0
  58. package/src/composites/worker-pool.ts +230 -0
  59. package/src/coverage.test.ts +27 -0
  60. package/src/coverage.ts +35 -0
  61. package/src/crd/loader.ts +112 -0
  62. package/src/crd/parser.test.ts +217 -0
  63. package/src/crd/parser.ts +279 -0
  64. package/src/crd/types.ts +54 -0
  65. package/src/default-labels.test.ts +111 -0
  66. package/src/default-labels.ts +122 -0
  67. package/src/generated/index.d.ts +2934 -0
  68. package/src/generated/index.ts +203 -0
  69. package/src/generated/lexicon-k8s.json +1413 -0
  70. package/src/generated/runtime.ts +4 -0
  71. package/src/import/generator.test.ts +121 -0
  72. package/src/import/generator.ts +285 -0
  73. package/src/import/parser.test.ts +156 -0
  74. package/src/import/parser.ts +204 -0
  75. package/src/import/roundtrip.test.ts +86 -0
  76. package/src/index.ts +38 -0
  77. package/src/lint/post-synth/k8s-helpers.test.ts +219 -0
  78. package/src/lint/post-synth/k8s-helpers.ts +149 -0
  79. package/src/lint/post-synth/post-synth.test.ts +969 -0
  80. package/src/lint/post-synth/wk8005.ts +59 -0
  81. package/src/lint/post-synth/wk8006.ts +68 -0
  82. package/src/lint/post-synth/wk8041.ts +73 -0
  83. package/src/lint/post-synth/wk8042.ts +48 -0
  84. package/src/lint/post-synth/wk8101.ts +65 -0
  85. package/src/lint/post-synth/wk8102.ts +42 -0
  86. package/src/lint/post-synth/wk8103.ts +45 -0
  87. package/src/lint/post-synth/wk8104.ts +69 -0
  88. package/src/lint/post-synth/wk8105.ts +45 -0
  89. package/src/lint/post-synth/wk8201.ts +55 -0
  90. package/src/lint/post-synth/wk8202.ts +46 -0
  91. package/src/lint/post-synth/wk8203.ts +46 -0
  92. package/src/lint/post-synth/wk8204.ts +54 -0
  93. package/src/lint/post-synth/wk8205.ts +56 -0
  94. package/src/lint/post-synth/wk8207.ts +45 -0
  95. package/src/lint/post-synth/wk8208.ts +45 -0
  96. package/src/lint/post-synth/wk8209.ts +45 -0
  97. package/src/lint/post-synth/wk8301.ts +51 -0
  98. package/src/lint/post-synth/wk8302.ts +46 -0
  99. package/src/lint/post-synth/wk8303.ts +96 -0
  100. package/src/lint/rules/hardcoded-namespace.ts +56 -0
  101. package/src/lint/rules/rules.test.ts +69 -0
  102. package/src/lsp/completions.test.ts +64 -0
  103. package/src/lsp/completions.ts +20 -0
  104. package/src/lsp/hover.test.ts +69 -0
  105. package/src/lsp/hover.ts +68 -0
  106. package/src/package-cli.ts +28 -0
  107. package/src/plugin.test.ts +209 -0
  108. package/src/plugin.ts +915 -0
  109. package/src/serializer.test.ts +275 -0
  110. package/src/serializer.ts +278 -0
  111. package/src/spec/fetch.test.ts +24 -0
  112. package/src/spec/fetch.ts +68 -0
  113. package/src/spec/parse.test.ts +102 -0
  114. package/src/spec/parse.ts +477 -0
  115. package/src/testdata/manifests/configmap.yaml +7 -0
  116. package/src/testdata/manifests/deployment.yaml +22 -0
  117. package/src/testdata/manifests/full-app.yaml +61 -0
  118. package/src/testdata/manifests/secret.yaml +7 -0
  119. package/src/testdata/manifests/service.yaml +15 -0
  120. package/src/validate-cli.ts +21 -0
  121. package/src/validate.test.ts +29 -0
  122. package/src/validate.ts +46 -0
  123. package/src/variables.ts +36 -0
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Schema augmentation patches for the Kubernetes OpenAPI spec.
3
+ *
4
+ * The official K8s OpenAPI spec has known issues: recursive definitions
5
+ * in JSONSchemaProps, ambiguous IntOrString typing, and Quantity being
6
+ * opaque. Patches are applied after fetch but before parse.
7
+ */
8
+
9
+ /**
10
+ * A targeted fix for a schema definition.
11
+ */
12
+ export interface SchemaPatch {
13
+ /** Human-readable description of what this patch fixes */
14
+ description: string;
15
+ /** JSON path to the definition to patch (e.g. "definitions.io.k8s.api.core.v1.Container") */
16
+ path: string;
17
+ /** Function that applies the patch to the definition */
18
+ apply(def: Record<string, unknown>): void;
19
+ }
20
+
21
+ /**
22
+ * Registry of known schema patches.
23
+ */
24
+ export const schemaPatches: SchemaPatch[] = [
25
+ {
26
+ description:
27
+ "Break infinite recursion in JSONSchemaProps by capping nested $ref depth",
28
+ path: "definitions.io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSONSchemaProps",
29
+ apply(def) {
30
+ // JSONSchemaProps references itself in properties, items, additionalProperties, etc.
31
+ // We mark it as a plain object to prevent infinite recursion in the codegen walker.
32
+ const props = def.properties as Record<string, unknown> | undefined;
33
+ if (!props) return;
34
+
35
+ // Replace self-referencing fields with opaque Record<string, any>
36
+ for (const key of [
37
+ "properties",
38
+ "additionalProperties",
39
+ "not",
40
+ "allOf",
41
+ "oneOf",
42
+ "anyOf",
43
+ ]) {
44
+ if (props[key]) {
45
+ (props[key] as Record<string, unknown>).description ??=
46
+ "JSON Schema definition (recursive, treated as opaque)";
47
+ }
48
+ }
49
+ },
50
+ },
51
+ {
52
+ description:
53
+ "Patch IntOrString to be explicitly typed as string | number",
54
+ path: "definitions.io.k8s.apimachinery.pkg.util.intstr.IntOrString",
55
+ apply(def) {
56
+ // The spec marks this as just "string" with a format, but it's actually
57
+ // a union type used for ports, resource quantities, etc.
58
+ if (!def["x-kubernetes-int-or-string"]) {
59
+ def["x-kubernetes-int-or-string"] = true;
60
+ }
61
+ },
62
+ },
63
+ {
64
+ description: "Patch Quantity to be typed as string",
65
+ path: "definitions.io.k8s.apimachinery.pkg.api.resource.Quantity",
66
+ apply(def) {
67
+ // Quantity is serialized as a string (e.g., "500m", "1Gi") but the
68
+ // spec sometimes marks it as having no type. Force string.
69
+ if (!def.type) {
70
+ def.type = "string";
71
+ }
72
+ },
73
+ },
74
+ ];
75
+
76
+ /**
77
+ * Apply all registered patches to a parsed schema.
78
+ * Modifies the schema in place.
79
+ */
80
+ export function applyPatches(
81
+ schema: Record<string, unknown>,
82
+ ): { applied: string[]; skipped: string[] } {
83
+ const applied: string[] = [];
84
+ const skipped: string[] = [];
85
+
86
+ for (const patch of schemaPatches) {
87
+ const segments = patch.path.split(".");
88
+ let target: Record<string, unknown> | undefined = schema;
89
+
90
+ for (const segment of segments) {
91
+ if (target && typeof target === "object" && segment in target) {
92
+ target = target[segment] as Record<string, unknown>;
93
+ } else {
94
+ target = undefined;
95
+ break;
96
+ }
97
+ }
98
+
99
+ if (target) {
100
+ patch.apply(target);
101
+ applied.push(patch.description);
102
+ } else {
103
+ skipped.push(`${patch.description} (path "${patch.path}" not found)`);
104
+ }
105
+ }
106
+
107
+ return { applied, skipped };
108
+ }
@@ -0,0 +1,95 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { existsSync, readFileSync } from "fs";
3
+ import { join, dirname } from "path";
4
+ import { fileURLToPath } from "url";
5
+
6
+ const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
7
+ const generatedDir = join(pkgDir, "src", "generated");
8
+ const hasGenerated = existsSync(join(generatedDir, "lexicon-k8s.json"));
9
+
10
+ describe("snapshot", () => {
11
+ test.skipIf(!hasGenerated)("lexicon-k8s.json is valid JSON", () => {
12
+ const raw = readFileSync(join(generatedDir, "lexicon-k8s.json"), "utf-8");
13
+ const parsed = JSON.parse(raw);
14
+ expect(typeof parsed).toBe("object");
15
+ expect(parsed).not.toBeNull();
16
+ });
17
+
18
+ test.skipIf(!hasGenerated)("lexicon entries have required fields", () => {
19
+ const raw = readFileSync(join(generatedDir, "lexicon-k8s.json"), "utf-8");
20
+ const registry = JSON.parse(raw) as Record<string, any>;
21
+
22
+ for (const [name, entry] of Object.entries(registry)) {
23
+ expect(entry.resourceType).toBeDefined();
24
+ expect(entry.kind).toBeDefined();
25
+ expect(["resource", "property"]).toContain(entry.kind);
26
+ }
27
+ });
28
+
29
+ test.skipIf(!hasGenerated)("contains Deployment entry", () => {
30
+ const raw = readFileSync(join(generatedDir, "lexicon-k8s.json"), "utf-8");
31
+ const registry = JSON.parse(raw) as Record<string, any>;
32
+
33
+ const deployment = Object.values(registry).find(
34
+ (e: any) => e.resourceType === "K8s::Apps::Deployment",
35
+ );
36
+ expect(deployment).toBeDefined();
37
+ expect(deployment!.kind).toBe("resource");
38
+ expect(deployment!.apiVersion).toBe("apps/v1");
39
+ expect(deployment!.gvkKind).toBe("Deployment");
40
+ });
41
+
42
+ test.skipIf(!hasGenerated)("contains Pod entry", () => {
43
+ const raw = readFileSync(join(generatedDir, "lexicon-k8s.json"), "utf-8");
44
+ const registry = JSON.parse(raw) as Record<string, any>;
45
+
46
+ const pod = Object.values(registry).find(
47
+ (e: any) => e.resourceType === "K8s::Core::Pod",
48
+ );
49
+ expect(pod).toBeDefined();
50
+ expect(pod!.kind).toBe("resource");
51
+ });
52
+
53
+ test.skipIf(!hasGenerated)("contains Service entry", () => {
54
+ const raw = readFileSync(join(generatedDir, "lexicon-k8s.json"), "utf-8");
55
+ const registry = JSON.parse(raw) as Record<string, any>;
56
+
57
+ const svc = Object.values(registry).find(
58
+ (e: any) => e.resourceType === "K8s::Core::Service",
59
+ );
60
+ expect(svc).toBeDefined();
61
+ expect(svc!.kind).toBe("resource");
62
+ expect(svc!.apiVersion).toBe("v1");
63
+ });
64
+
65
+ test.skipIf(!hasGenerated)("contains ConfigMap entry", () => {
66
+ const raw = readFileSync(join(generatedDir, "lexicon-k8s.json"), "utf-8");
67
+ const registry = JSON.parse(raw) as Record<string, any>;
68
+
69
+ const cm = Object.values(registry).find(
70
+ (e: any) => e.resourceType === "K8s::Core::ConfigMap",
71
+ );
72
+ expect(cm).toBeDefined();
73
+ });
74
+
75
+ test.skipIf(!hasGenerated)("contains Container property type", () => {
76
+ const raw = readFileSync(join(generatedDir, "lexicon-k8s.json"), "utf-8");
77
+ const registry = JSON.parse(raw) as Record<string, any>;
78
+
79
+ const container = Object.values(registry).find(
80
+ (e: any) => e.resourceType === "K8s::Core::Container",
81
+ );
82
+ expect(container).toBeDefined();
83
+ expect(container!.kind).toBe("property");
84
+ });
85
+
86
+ test.skipIf(!hasGenerated)("index.d.ts contains class declarations", () => {
87
+ const dtsPath = join(generatedDir, "index.d.ts");
88
+ if (!existsSync(dtsPath)) return;
89
+
90
+ const dts = readFileSync(dtsPath, "utf-8");
91
+ expect(dts).toContain("class Deployment");
92
+ expect(dts).toContain("class Service");
93
+ expect(dts).toContain("class Pod");
94
+ });
95
+ });
@@ -0,0 +1,24 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { typecheckDTS } from "./typecheck";
3
+
4
+ describe("typecheckDTS", () => {
5
+ test("passes valid .d.ts content", async () => {
6
+ const result = await typecheckDTS("export declare class Foo { bar: string; }");
7
+ expect(result.ok).toBe(true);
8
+ expect(result.diagnostics).toHaveLength(0);
9
+ });
10
+
11
+ test("fails on syntax errors", async () => {
12
+ const result = await typecheckDTS("export declare class { }"); // missing class name
13
+ expect(result.ok).toBe(false);
14
+ expect(result.diagnostics.length).toBeGreaterThan(0);
15
+ });
16
+
17
+ test("fails on undefined type references", async () => {
18
+ const result = await typecheckDTS(
19
+ "export declare class Foo { bar: UndefinedType; }",
20
+ );
21
+ expect(result.ok).toBe(false);
22
+ expect(result.diagnostics.length).toBeGreaterThan(0);
23
+ });
24
+ });
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Re-export from core — typecheck is lexicon-agnostic.
3
+ */
4
+ export { typecheckDTS, type TypeCheckResult } from "@intentius/chant/codegen/typecheck";
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Pinned dependency versions for the Kubernetes lexicon.
3
+ *
4
+ * Centralises the schema version used by fetch.ts and provides
5
+ * a helper to check for newer Kubernetes releases.
6
+ */
7
+
8
+ /** Pinned versions of external dependencies. */
9
+ export const PINNED_VERSIONS = {
10
+ k8sOpenAPI: "v1.32.0",
11
+ } as const;
12
+
13
+ /**
14
+ * Build the raw GitHub URL for the K8s swagger.json for a given version.
15
+ */
16
+ export function k8sSwaggerUrl(version?: string): string {
17
+ const v = version ?? PINNED_VERSIONS.k8sOpenAPI;
18
+ return `https://raw.githubusercontent.com/kubernetes/kubernetes/${v}/api/openapi-spec/swagger.json`;
19
+ }
20
+
21
+ /**
22
+ * Check for a newer stable Kubernetes release on GitHub.
23
+ * Returns the latest tag if newer than pinned, or null if up-to-date.
24
+ */
25
+ export async function checkForUpdates(): Promise<string | null> {
26
+ try {
27
+ const res = await fetch(
28
+ "https://api.github.com/repos/kubernetes/kubernetes/releases/latest",
29
+ {
30
+ headers: { Accept: "application/vnd.github+json" },
31
+ },
32
+ );
33
+ if (!res.ok) return null;
34
+
35
+ const data = (await res.json()) as { tag_name?: string };
36
+ const latest = data.tag_name;
37
+ if (!latest) return null;
38
+
39
+ return latest !== PINNED_VERSIONS.k8sOpenAPI ? latest : null;
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
@@ -0,0 +1,236 @@
1
+ /**
2
+ * AutoscaledService composite — Deployment + Service + HPA + PDB.
3
+ *
4
+ * A higher-level construct for production HTTP services with autoscaling
5
+ * and disruption budget. Enforces that HPA targets match the Deployment,
6
+ * PDB selectors match pod labels, and resource requests are set.
7
+ */
8
+
9
+ export interface AutoscaledServiceProps {
10
+ /** Application name — used in metadata and labels. */
11
+ name: string;
12
+ /** Container image (e.g., "nginx:1.25"). */
13
+ image: string;
14
+ /** Container port (default: 80). */
15
+ port?: number;
16
+ /** Minimum replicas for HPA (default: 2). */
17
+ minReplicas?: number;
18
+ /** Maximum replicas for HPA (required). */
19
+ maxReplicas: number;
20
+ /** Target CPU utilization percentage (default: 70). */
21
+ targetCPUPercent?: number;
22
+ /** Target memory utilization percentage — if set, adds memory metric. */
23
+ targetMemoryPercent?: number;
24
+ /** PDB minAvailable (default: 1). */
25
+ minAvailable?: number | string;
26
+ /** CPU request (required — HPA needs resource requests). */
27
+ cpuRequest: string;
28
+ /** Memory request (required). */
29
+ memoryRequest: string;
30
+ /** CPU limit (optional — left unset if not provided). */
31
+ cpuLimit?: string;
32
+ /** Memory limit (optional — left unset if not provided). */
33
+ memoryLimit?: string;
34
+ /** Topology spread constraints (default: false). true → zone spreading, object → custom. */
35
+ topologySpread?: boolean | {
36
+ maxSkew?: number;
37
+ topologyKey?: string;
38
+ };
39
+ /** Liveness probe path (default: "/healthz"). */
40
+ livenessPath?: string;
41
+ /** Readiness probe path (default: "/readyz"). */
42
+ readinessPath?: string;
43
+ /** Additional labels to apply to all resources. */
44
+ labels?: Record<string, string>;
45
+ /** Namespace for all resources. */
46
+ namespace?: string;
47
+ /** Environment variables for the container. */
48
+ env?: Array<{ name: string; value: string }>;
49
+ }
50
+
51
+ export interface AutoscaledServiceResult {
52
+ deployment: Record<string, unknown>;
53
+ service: Record<string, unknown>;
54
+ hpa: Record<string, unknown>;
55
+ pdb: Record<string, unknown>;
56
+ }
57
+
58
+ /**
59
+ * Create an AutoscaledService composite — returns prop objects for
60
+ * a Deployment, Service, HorizontalPodAutoscaler, and PodDisruptionBudget.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * import { AutoscaledService } from "@intentius/chant-lexicon-k8s";
65
+ *
66
+ * const { deployment, service, hpa, pdb } = AutoscaledService({
67
+ * name: "api",
68
+ * image: "api:1.0",
69
+ * maxReplicas: 10,
70
+ * cpuRequest: "100m",
71
+ * memoryRequest: "128Mi",
72
+ * });
73
+ * ```
74
+ */
75
+ export function AutoscaledService(props: AutoscaledServiceProps): AutoscaledServiceResult {
76
+ const {
77
+ name,
78
+ image,
79
+ port = 80,
80
+ minReplicas = 2,
81
+ maxReplicas,
82
+ targetCPUPercent = 70,
83
+ targetMemoryPercent,
84
+ minAvailable = 1,
85
+ cpuRequest,
86
+ memoryRequest,
87
+ cpuLimit,
88
+ memoryLimit,
89
+ topologySpread = false,
90
+ livenessPath = "/healthz",
91
+ readinessPath = "/readyz",
92
+ labels: extraLabels = {},
93
+ namespace,
94
+ env,
95
+ } = props;
96
+
97
+ const commonLabels: Record<string, string> = {
98
+ "app.kubernetes.io/name": name,
99
+ "app.kubernetes.io/managed-by": "chant",
100
+ ...extraLabels,
101
+ };
102
+
103
+ // Build topologySpreadConstraints if enabled
104
+ const topologyConstraints: Array<Record<string, unknown>> | undefined = (() => {
105
+ if (!topologySpread) return undefined;
106
+ const maxSkew = typeof topologySpread === "object" ? (topologySpread.maxSkew ?? 1) : 1;
107
+ const topologyKey = typeof topologySpread === "object"
108
+ ? (topologySpread.topologyKey ?? "topology.kubernetes.io/zone")
109
+ : "topology.kubernetes.io/zone";
110
+ return [{
111
+ maxSkew,
112
+ topologyKey,
113
+ whenUnsatisfiable: "DoNotSchedule",
114
+ labelSelector: { matchLabels: { "app.kubernetes.io/name": name } },
115
+ }];
116
+ })();
117
+
118
+ const resources: Record<string, unknown> = {
119
+ requests: { cpu: cpuRequest, memory: memoryRequest },
120
+ ...(cpuLimit || memoryLimit
121
+ ? {
122
+ limits: {
123
+ ...(cpuLimit && { cpu: cpuLimit }),
124
+ ...(memoryLimit && { memory: memoryLimit }),
125
+ },
126
+ }
127
+ : {}),
128
+ };
129
+
130
+ const deploymentProps: Record<string, unknown> = {
131
+ metadata: {
132
+ name,
133
+ ...(namespace && { namespace }),
134
+ labels: { ...commonLabels, "app.kubernetes.io/component": "server" },
135
+ },
136
+ spec: {
137
+ replicas: minReplicas,
138
+ selector: { matchLabels: { "app.kubernetes.io/name": name } },
139
+ template: {
140
+ metadata: { labels: { "app.kubernetes.io/name": name, ...extraLabels } },
141
+ spec: {
142
+ containers: [
143
+ {
144
+ name,
145
+ image,
146
+ ports: [{ containerPort: port, name: "http" }],
147
+ resources,
148
+ livenessProbe: {
149
+ httpGet: { path: livenessPath, port },
150
+ initialDelaySeconds: 10,
151
+ periodSeconds: 10,
152
+ },
153
+ readinessProbe: {
154
+ httpGet: { path: readinessPath, port },
155
+ initialDelaySeconds: 5,
156
+ periodSeconds: 5,
157
+ },
158
+ ...(env && { env }),
159
+ },
160
+ ],
161
+ ...(topologyConstraints && { topologySpreadConstraints: topologyConstraints }),
162
+ },
163
+ },
164
+ },
165
+ };
166
+
167
+ const serviceProps: Record<string, unknown> = {
168
+ metadata: {
169
+ name,
170
+ ...(namespace && { namespace }),
171
+ labels: { ...commonLabels, "app.kubernetes.io/component": "server" },
172
+ },
173
+ spec: {
174
+ selector: { "app.kubernetes.io/name": name },
175
+ ports: [{ port: 80, targetPort: port, protocol: "TCP", name: "http" }],
176
+ type: "ClusterIP",
177
+ },
178
+ };
179
+
180
+ const metrics: Array<Record<string, unknown>> = [
181
+ {
182
+ type: "Resource",
183
+ resource: {
184
+ name: "cpu",
185
+ target: { type: "Utilization", averageUtilization: targetCPUPercent },
186
+ },
187
+ },
188
+ ];
189
+
190
+ if (targetMemoryPercent !== undefined) {
191
+ metrics.push({
192
+ type: "Resource",
193
+ resource: {
194
+ name: "memory",
195
+ target: { type: "Utilization", averageUtilization: targetMemoryPercent },
196
+ },
197
+ });
198
+ }
199
+
200
+ const hpaProps: Record<string, unknown> = {
201
+ metadata: {
202
+ name,
203
+ ...(namespace && { namespace }),
204
+ labels: { ...commonLabels, "app.kubernetes.io/component": "autoscaler" },
205
+ },
206
+ spec: {
207
+ scaleTargetRef: {
208
+ apiVersion: "apps/v1",
209
+ kind: "Deployment",
210
+ name,
211
+ },
212
+ minReplicas,
213
+ maxReplicas,
214
+ metrics,
215
+ },
216
+ };
217
+
218
+ const pdbProps: Record<string, unknown> = {
219
+ metadata: {
220
+ name,
221
+ ...(namespace && { namespace }),
222
+ labels: { ...commonLabels, "app.kubernetes.io/component": "disruption-budget" },
223
+ },
224
+ spec: {
225
+ minAvailable,
226
+ selector: { matchLabels: { "app.kubernetes.io/name": name } },
227
+ },
228
+ };
229
+
230
+ return {
231
+ deployment: deploymentProps,
232
+ service: serviceProps,
233
+ hpa: hpaProps,
234
+ pdb: pdbProps,
235
+ };
236
+ }