@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,167 @@
1
+ /**
2
+ * CronWorkload composite — CronJob + ServiceAccount + Role + RoleBinding.
3
+ *
4
+ * A higher-level construct for deploying scheduled workloads with
5
+ * proper RBAC permissions.
6
+ */
7
+
8
+ export interface CronWorkloadProps {
9
+ /** Workload name — used in metadata and labels. */
10
+ name: string;
11
+ /** Container image. */
12
+ image: string;
13
+ /** Cron schedule expression (e.g., "0 * * * *"). */
14
+ schedule: string;
15
+ /** Command to run in the container. */
16
+ command?: string[];
17
+ /** Arguments to the command. */
18
+ args?: string[];
19
+ /** RBAC rules for the service account. */
20
+ rbacRules?: Array<{
21
+ apiGroups: string[];
22
+ resources: string[];
23
+ verbs: string[];
24
+ }>;
25
+ /** Job history limits. */
26
+ successfulJobsHistoryLimit?: number;
27
+ failedJobsHistoryLimit?: number;
28
+ /** Restart policy for the job (default: "OnFailure"). */
29
+ restartPolicy?: string;
30
+ /** Additional labels to apply to all resources. */
31
+ labels?: Record<string, string>;
32
+ /** Namespace for all resources. */
33
+ namespace?: string;
34
+ /** Environment variables. */
35
+ env?: Array<{ name: string; value: string }>;
36
+ }
37
+
38
+ export interface CronWorkloadResult {
39
+ cronJob: Record<string, unknown>;
40
+ serviceAccount: Record<string, unknown>;
41
+ role: Record<string, unknown>;
42
+ roleBinding: Record<string, unknown>;
43
+ }
44
+
45
+ /**
46
+ * Create a CronWorkload composite — returns prop objects for
47
+ * a CronJob, ServiceAccount, Role, and RoleBinding.
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * import { CronWorkload } from "@intentius/chant-lexicon-k8s";
52
+ *
53
+ * const { cronJob, serviceAccount, role, roleBinding } = CronWorkload({
54
+ * name: "db-backup",
55
+ * image: "postgres:16",
56
+ * schedule: "0 2 * * *",
57
+ * command: ["pg_dump", "-h", "postgres", "mydb"],
58
+ * rbacRules: [
59
+ * { apiGroups: [""], resources: ["secrets"], verbs: ["get"] },
60
+ * ],
61
+ * });
62
+ * ```
63
+ */
64
+ export function CronWorkload(props: CronWorkloadProps): CronWorkloadResult {
65
+ const {
66
+ name,
67
+ image,
68
+ schedule,
69
+ command,
70
+ args,
71
+ rbacRules = [],
72
+ successfulJobsHistoryLimit = 3,
73
+ failedJobsHistoryLimit = 1,
74
+ restartPolicy = "OnFailure",
75
+ labels: extraLabels = {},
76
+ namespace,
77
+ env,
78
+ } = props;
79
+
80
+ const saName = `${name}-sa`;
81
+ const roleName = `${name}-role`;
82
+ const bindingName = `${name}-binding`;
83
+
84
+ const commonLabels: Record<string, string> = {
85
+ "app.kubernetes.io/name": name,
86
+ "app.kubernetes.io/managed-by": "chant",
87
+ ...extraLabels,
88
+ };
89
+
90
+ const cronJobProps: Record<string, unknown> = {
91
+ metadata: {
92
+ name,
93
+ ...(namespace && { namespace }),
94
+ labels: { ...commonLabels, "app.kubernetes.io/component": "worker" },
95
+ },
96
+ spec: {
97
+ schedule,
98
+ successfulJobsHistoryLimit,
99
+ failedJobsHistoryLimit,
100
+ jobTemplate: {
101
+ spec: {
102
+ template: {
103
+ spec: {
104
+ serviceAccountName: saName,
105
+ restartPolicy,
106
+ containers: [
107
+ {
108
+ name,
109
+ image,
110
+ ...(command && { command }),
111
+ ...(args && { args }),
112
+ ...(env && { env }),
113
+ },
114
+ ],
115
+ },
116
+ },
117
+ },
118
+ },
119
+ },
120
+ };
121
+
122
+ const serviceAccountProps: Record<string, unknown> = {
123
+ metadata: {
124
+ name: saName,
125
+ ...(namespace && { namespace }),
126
+ labels: { ...commonLabels, "app.kubernetes.io/component": "worker" },
127
+ },
128
+ };
129
+
130
+ const roleProps: Record<string, unknown> = {
131
+ metadata: {
132
+ name: roleName,
133
+ ...(namespace && { namespace }),
134
+ labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
135
+ },
136
+ rules: rbacRules.length > 0 ? rbacRules : [
137
+ { apiGroups: [""], resources: ["pods"], verbs: ["get", "list"] },
138
+ ],
139
+ };
140
+
141
+ const roleBindingProps: Record<string, unknown> = {
142
+ metadata: {
143
+ name: bindingName,
144
+ ...(namespace && { namespace }),
145
+ labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
146
+ },
147
+ roleRef: {
148
+ apiGroup: "rbac.authorization.k8s.io",
149
+ kind: "Role",
150
+ name: roleName,
151
+ },
152
+ subjects: [
153
+ {
154
+ kind: "ServiceAccount",
155
+ name: saName,
156
+ ...(namespace && { namespace }),
157
+ },
158
+ ],
159
+ };
160
+
161
+ return {
162
+ cronJob: cronJobProps,
163
+ serviceAccount: serviceAccountProps,
164
+ role: roleProps,
165
+ roleBinding: roleBindingProps,
166
+ };
167
+ }
@@ -0,0 +1,14 @@
1
+ export { WebApp } from "./web-app";
2
+ export type { WebAppProps, WebAppResult } from "./web-app";
3
+ export { StatefulApp } from "./stateful-app";
4
+ export type { StatefulAppProps, StatefulAppResult } from "./stateful-app";
5
+ export { CronWorkload } from "./cron-workload";
6
+ export type { CronWorkloadProps, CronWorkloadResult } from "./cron-workload";
7
+ export { AutoscaledService } from "./autoscaled-service";
8
+ export type { AutoscaledServiceProps, AutoscaledServiceResult } from "./autoscaled-service";
9
+ export { WorkerPool } from "./worker-pool";
10
+ export type { WorkerPoolProps, WorkerPoolResult } from "./worker-pool";
11
+ export { NamespaceEnv } from "./namespace-env";
12
+ export type { NamespaceEnvProps, NamespaceEnvResult } from "./namespace-env";
13
+ export { NodeAgent } from "./node-agent";
14
+ export type { NodeAgentProps, NodeAgentResult } from "./node-agent";
@@ -0,0 +1,163 @@
1
+ /**
2
+ * NamespaceEnv composite — Namespace + optional ResourceQuota + LimitRange + NetworkPolicy.
3
+ *
4
+ * A higher-level construct for multi-tenant namespace provisioning with
5
+ * resource guardrails and network isolation.
6
+ */
7
+
8
+ export interface NamespaceEnvProps {
9
+ /** Namespace name. */
10
+ name: string;
11
+ /** Total namespace CPU quota (e.g., "8"). */
12
+ cpuQuota?: string;
13
+ /** Total namespace memory quota (e.g., "16Gi"). */
14
+ memoryQuota?: string;
15
+ /** Maximum pods in namespace. */
16
+ maxPods?: number;
17
+ /** LimitRange default CPU request (e.g., "100m"). */
18
+ defaultCpuRequest?: string;
19
+ /** LimitRange default memory request (e.g., "128Mi"). */
20
+ defaultMemoryRequest?: string;
21
+ /** LimitRange default CPU limit (e.g., "500m"). */
22
+ defaultCpuLimit?: string;
23
+ /** LimitRange default memory limit (e.g., "512Mi"). */
24
+ defaultMemoryLimit?: string;
25
+ /** Create default-deny ingress NetworkPolicy (default: true). */
26
+ defaultDenyIngress?: boolean;
27
+ /** Create default-deny egress NetworkPolicy (default: false). */
28
+ defaultDenyEgress?: boolean;
29
+ /** Additional labels to apply to all resources. */
30
+ labels?: Record<string, string>;
31
+ }
32
+
33
+ export interface NamespaceEnvResult {
34
+ namespace: Record<string, unknown>;
35
+ resourceQuota?: Record<string, unknown>;
36
+ limitRange?: Record<string, unknown>;
37
+ networkPolicy?: Record<string, unknown>;
38
+ }
39
+
40
+ /**
41
+ * Create a NamespaceEnv composite — returns prop objects for
42
+ * a Namespace, optional ResourceQuota, LimitRange, and NetworkPolicy.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * import { NamespaceEnv } from "@intentius/chant-lexicon-k8s";
47
+ *
48
+ * const { namespace, resourceQuota, limitRange, networkPolicy } = NamespaceEnv({
49
+ * name: "team-alpha",
50
+ * cpuQuota: "8",
51
+ * memoryQuota: "16Gi",
52
+ * defaultCpuRequest: "100m",
53
+ * defaultMemoryRequest: "128Mi",
54
+ * defaultDenyIngress: true,
55
+ * });
56
+ * ```
57
+ */
58
+ export function NamespaceEnv(props: NamespaceEnvProps): NamespaceEnvResult {
59
+ const {
60
+ name,
61
+ cpuQuota,
62
+ memoryQuota,
63
+ maxPods,
64
+ defaultCpuRequest,
65
+ defaultMemoryRequest,
66
+ defaultCpuLimit,
67
+ defaultMemoryLimit,
68
+ defaultDenyIngress = true,
69
+ defaultDenyEgress = false,
70
+ labels: extraLabels = {},
71
+ } = props;
72
+
73
+ const commonLabels: Record<string, string> = {
74
+ "app.kubernetes.io/name": name,
75
+ "app.kubernetes.io/managed-by": "chant",
76
+ ...extraLabels,
77
+ };
78
+
79
+ const namespaceProps: Record<string, unknown> = {
80
+ metadata: {
81
+ name,
82
+ labels: { ...commonLabels, "app.kubernetes.io/component": "namespace" },
83
+ },
84
+ };
85
+
86
+ const result: NamespaceEnvResult = {
87
+ namespace: namespaceProps,
88
+ };
89
+
90
+ // ResourceQuota — only if at least one quota prop is set
91
+ const hasQuota = cpuQuota || memoryQuota || maxPods !== undefined;
92
+ if (hasQuota) {
93
+ const hard: Record<string, unknown> = {};
94
+ if (cpuQuota) hard["limits.cpu"] = cpuQuota;
95
+ if (memoryQuota) hard["limits.memory"] = memoryQuota;
96
+ if (maxPods !== undefined) hard.pods = String(maxPods);
97
+
98
+ result.resourceQuota = {
99
+ metadata: {
100
+ name: `${name}-quota`,
101
+ namespace: name,
102
+ labels: { ...commonLabels, "app.kubernetes.io/component": "quota" },
103
+ },
104
+ spec: { hard },
105
+ };
106
+ }
107
+
108
+ // LimitRange — only if at least one default limit prop is set
109
+ const hasLimits = defaultCpuRequest || defaultMemoryRequest || defaultCpuLimit || defaultMemoryLimit;
110
+
111
+ if (hasQuota && !hasLimits) {
112
+ console.warn(
113
+ `[NamespaceEnv] "${name}": ResourceQuota set but no LimitRange defaults. ` +
114
+ `Pods without explicit resource requests will fail to schedule.`
115
+ );
116
+ }
117
+ if (hasLimits) {
118
+ const defaultLimits: Record<string, string> = {};
119
+ const defaultRequests: Record<string, string> = {};
120
+
121
+ if (defaultCpuLimit) defaultLimits.cpu = defaultCpuLimit;
122
+ if (defaultMemoryLimit) defaultLimits.memory = defaultMemoryLimit;
123
+ if (defaultCpuRequest) defaultRequests.cpu = defaultCpuRequest;
124
+ if (defaultMemoryRequest) defaultRequests.memory = defaultMemoryRequest;
125
+
126
+ const limit: Record<string, unknown> = { type: "Container" };
127
+ if (Object.keys(defaultLimits).length > 0) limit.default = defaultLimits;
128
+ if (Object.keys(defaultRequests).length > 0) limit.defaultRequest = defaultRequests;
129
+
130
+ result.limitRange = {
131
+ metadata: {
132
+ name: `${name}-limits`,
133
+ namespace: name,
134
+ labels: { ...commonLabels, "app.kubernetes.io/component": "limits" },
135
+ },
136
+ spec: {
137
+ limits: [limit],
138
+ },
139
+ };
140
+ }
141
+
142
+ // NetworkPolicy — default-deny ingress and/or egress
143
+ const hasNetworkPolicy = defaultDenyIngress || defaultDenyEgress;
144
+ if (hasNetworkPolicy) {
145
+ const policyTypes: string[] = [];
146
+ if (defaultDenyIngress) policyTypes.push("Ingress");
147
+ if (defaultDenyEgress) policyTypes.push("Egress");
148
+
149
+ result.networkPolicy = {
150
+ metadata: {
151
+ name: `${name}-default-deny`,
152
+ namespace: name,
153
+ labels: { ...commonLabels, "app.kubernetes.io/component": "network-policy" },
154
+ },
155
+ spec: {
156
+ podSelector: {},
157
+ policyTypes,
158
+ },
159
+ };
160
+ }
161
+
162
+ return result;
163
+ }
@@ -0,0 +1,224 @@
1
+ /**
2
+ * NodeAgent composite — DaemonSet + ServiceAccount + ClusterRole + ClusterRoleBinding + optional ConfigMap.
3
+ *
4
+ * A higher-level construct for per-node agents (Fluentd, Prometheus Node Exporter,
5
+ * security scanners) that need cluster-wide RBAC and tolerations.
6
+ */
7
+
8
+ export interface NodeAgentProps {
9
+ /** Agent name — used in metadata and labels. */
10
+ name: string;
11
+ /** Container image. */
12
+ image: string;
13
+ /** Metrics port (optional). */
14
+ port?: number;
15
+ /** Host path mounts. */
16
+ hostPaths?: Array<{
17
+ name: string;
18
+ hostPath: string;
19
+ mountPath: string;
20
+ readOnly?: boolean;
21
+ }>;
22
+ /** Config data — creates a ConfigMap mounted at /etc/{name}/. */
23
+ config?: Record<string, string>;
24
+ /** ClusterRole RBAC rules (required). */
25
+ rbacRules: Array<{
26
+ apiGroups: string[];
27
+ resources: string[];
28
+ verbs: string[];
29
+ }>;
30
+ /** Tolerate all taints so agent runs on every node (default: true). */
31
+ tolerateAllTaints?: boolean;
32
+ /** Namespace for namespaced resources (DaemonSet, ServiceAccount, ConfigMap). */
33
+ namespace?: string;
34
+ /** Additional labels to apply to all resources. */
35
+ labels?: Record<string, string>;
36
+ /** CPU request (default: "50m"). */
37
+ cpuRequest?: string;
38
+ /** Memory request (default: "64Mi"). */
39
+ memoryRequest?: string;
40
+ /** CPU limit (default: "200m"). */
41
+ cpuLimit?: string;
42
+ /** Memory limit (default: "128Mi"). */
43
+ memoryLimit?: string;
44
+ /** Environment variables for the container. */
45
+ env?: Array<{ name: string; value: string }>;
46
+ }
47
+
48
+ export interface NodeAgentResult {
49
+ daemonSet: Record<string, unknown>;
50
+ serviceAccount: Record<string, unknown>;
51
+ clusterRole: Record<string, unknown>;
52
+ clusterRoleBinding: Record<string, unknown>;
53
+ configMap?: Record<string, unknown>;
54
+ }
55
+
56
+ /**
57
+ * Create a NodeAgent composite — returns prop objects for
58
+ * a DaemonSet, ServiceAccount, ClusterRole, ClusterRoleBinding, and optional ConfigMap.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * import { NodeAgent } from "@intentius/chant-lexicon-k8s";
63
+ *
64
+ * const { daemonSet, serviceAccount, clusterRole, clusterRoleBinding } = NodeAgent({
65
+ * name: "log-collector",
66
+ * image: "fluentd:v1.16",
67
+ * hostPaths: [{ name: "varlog", hostPath: "/var/log", mountPath: "/var/log" }],
68
+ * rbacRules: [
69
+ * { apiGroups: [""], resources: ["pods", "namespaces"], verbs: ["get", "list", "watch"] },
70
+ * ],
71
+ * });
72
+ * ```
73
+ */
74
+ export function NodeAgent(props: NodeAgentProps): NodeAgentResult {
75
+ const {
76
+ name,
77
+ image,
78
+ port,
79
+ hostPaths = [],
80
+ config,
81
+ rbacRules,
82
+ tolerateAllTaints = true,
83
+ namespace,
84
+ cpuRequest = "50m",
85
+ memoryRequest = "64Mi",
86
+ cpuLimit = "200m",
87
+ memoryLimit = "128Mi",
88
+ labels: extraLabels = {},
89
+ env,
90
+ } = props;
91
+
92
+ const saName = `${name}-sa`;
93
+ const clusterRoleName = `${name}-role`;
94
+ const bindingName = `${name}-binding`;
95
+ const configMapName = `${name}-config`;
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 volumes and volumeMounts from hostPaths
104
+ const volumes: Array<Record<string, unknown>> = [];
105
+ const volumeMounts: Array<Record<string, unknown>> = [];
106
+
107
+ for (const hp of hostPaths) {
108
+ volumes.push({
109
+ name: hp.name,
110
+ hostPath: { path: hp.hostPath },
111
+ });
112
+ volumeMounts.push({
113
+ name: hp.name,
114
+ mountPath: hp.mountPath,
115
+ readOnly: hp.readOnly !== false,
116
+ });
117
+ }
118
+
119
+ // ConfigMap volume
120
+ if (config) {
121
+ volumes.push({
122
+ name: "config",
123
+ configMap: { name: configMapName },
124
+ });
125
+ volumeMounts.push({
126
+ name: "config",
127
+ mountPath: `/etc/${name}`,
128
+ readOnly: true,
129
+ });
130
+ }
131
+
132
+ const container: Record<string, unknown> = {
133
+ name,
134
+ image,
135
+ ...(port && { ports: [{ containerPort: port, name: "metrics" }] }),
136
+ resources: {
137
+ requests: { cpu: cpuRequest, memory: memoryRequest },
138
+ limits: { cpu: cpuLimit, memory: memoryLimit },
139
+ },
140
+ ...(env && { env }),
141
+ ...(volumeMounts.length > 0 && { volumeMounts }),
142
+ };
143
+
144
+ const podSpec: Record<string, unknown> = {
145
+ serviceAccountName: saName,
146
+ containers: [container],
147
+ ...(volumes.length > 0 && { volumes }),
148
+ ...(tolerateAllTaints && {
149
+ tolerations: [{ operator: "Exists" }],
150
+ }),
151
+ };
152
+
153
+ const daemonSetProps: Record<string, unknown> = {
154
+ metadata: {
155
+ name,
156
+ ...(namespace && { namespace }),
157
+ labels: { ...commonLabels, "app.kubernetes.io/component": "agent" },
158
+ },
159
+ spec: {
160
+ selector: { matchLabels: { "app.kubernetes.io/name": name } },
161
+ template: {
162
+ metadata: { labels: { "app.kubernetes.io/name": name, ...extraLabels } },
163
+ spec: podSpec,
164
+ },
165
+ },
166
+ };
167
+
168
+ const serviceAccountProps: Record<string, unknown> = {
169
+ metadata: {
170
+ name: saName,
171
+ ...(namespace && { namespace }),
172
+ labels: { ...commonLabels, "app.kubernetes.io/component": "agent" },
173
+ },
174
+ };
175
+
176
+ // ClusterRole — cluster-scoped, no namespace
177
+ const clusterRoleProps: Record<string, unknown> = {
178
+ metadata: {
179
+ name: clusterRoleName,
180
+ labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
181
+ },
182
+ rules: rbacRules,
183
+ };
184
+
185
+ // ClusterRoleBinding — cluster-scoped, no namespace
186
+ const clusterRoleBindingProps: Record<string, unknown> = {
187
+ metadata: {
188
+ name: bindingName,
189
+ labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
190
+ },
191
+ roleRef: {
192
+ apiGroup: "rbac.authorization.k8s.io",
193
+ kind: "ClusterRole",
194
+ name: clusterRoleName,
195
+ },
196
+ subjects: [
197
+ {
198
+ kind: "ServiceAccount",
199
+ name: saName,
200
+ ...(namespace && { namespace }),
201
+ },
202
+ ],
203
+ };
204
+
205
+ const result: NodeAgentResult = {
206
+ daemonSet: daemonSetProps,
207
+ serviceAccount: serviceAccountProps,
208
+ clusterRole: clusterRoleProps,
209
+ clusterRoleBinding: clusterRoleBindingProps,
210
+ };
211
+
212
+ if (config) {
213
+ result.configMap = {
214
+ metadata: {
215
+ name: configMapName,
216
+ ...(namespace && { namespace }),
217
+ labels: { ...commonLabels, "app.kubernetes.io/component": "config" },
218
+ },
219
+ data: config,
220
+ };
221
+ }
222
+
223
+ return result;
224
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * StatefulApp composite — StatefulSet + headless Service + PVC template.
3
+ *
4
+ * A higher-level construct for deploying stateful applications
5
+ * like databases, caches, and message queues.
6
+ */
7
+
8
+ export interface StatefulAppProps {
9
+ /** Application name — used in metadata and labels. */
10
+ name: string;
11
+ /** Container image (e.g., "postgres:16"). */
12
+ image: string;
13
+ /** Container port (default: 5432). */
14
+ port?: number;
15
+ /** Number of replicas (default: 1). */
16
+ replicas?: number;
17
+ /** Storage size for the PVC (e.g., "10Gi"). */
18
+ storageSize?: string;
19
+ /** Storage class name. */
20
+ storageClassName?: string;
21
+ /** Volume mount path inside the container (default: "/data"). */
22
+ mountPath?: string;
23
+ /** Additional labels to apply to all resources. */
24
+ labels?: Record<string, string>;
25
+ /** CPU limit (e.g., "1"). */
26
+ cpuLimit?: string;
27
+ /** Memory limit (e.g., "1Gi"). */
28
+ memoryLimit?: string;
29
+ /** Namespace for all resources. */
30
+ namespace?: string;
31
+ /** Environment variables for the container. */
32
+ env?: Array<{ name: string; value: string }>;
33
+ }
34
+
35
+ export interface StatefulAppResult {
36
+ statefulSet: Record<string, unknown>;
37
+ service: Record<string, unknown>;
38
+ }
39
+
40
+ /**
41
+ * Create a StatefulApp composite — returns prop objects for
42
+ * a StatefulSet and headless Service.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * import { StatefulApp } from "@intentius/chant-lexicon-k8s";
47
+ *
48
+ * const { statefulSet, service } = StatefulApp({
49
+ * name: "postgres",
50
+ * image: "postgres:16",
51
+ * port: 5432,
52
+ * storageSize: "20Gi",
53
+ * env: [{ name: "POSTGRES_PASSWORD", value: "changeme" }],
54
+ * });
55
+ * ```
56
+ */
57
+ export function StatefulApp(props: StatefulAppProps): StatefulAppResult {
58
+ const {
59
+ name,
60
+ image,
61
+ port = 5432,
62
+ replicas = 1,
63
+ storageSize = "10Gi",
64
+ storageClassName,
65
+ mountPath = "/data",
66
+ labels: extraLabels = {},
67
+ cpuLimit = "1",
68
+ memoryLimit = "1Gi",
69
+ namespace,
70
+ env,
71
+ } = props;
72
+
73
+ const commonLabels: Record<string, string> = {
74
+ "app.kubernetes.io/name": name,
75
+ "app.kubernetes.io/managed-by": "chant",
76
+ ...extraLabels,
77
+ };
78
+
79
+ const statefulSetProps: Record<string, unknown> = {
80
+ metadata: {
81
+ name,
82
+ ...(namespace && { namespace }),
83
+ labels: { ...commonLabels, "app.kubernetes.io/component": "database" },
84
+ },
85
+ spec: {
86
+ serviceName: name,
87
+ replicas,
88
+ selector: { matchLabels: { "app.kubernetes.io/name": name } },
89
+ template: {
90
+ 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
+ },
105
+ },
106
+ volumeClaimTemplates: [
107
+ {
108
+ metadata: { name: "data" },
109
+ spec: {
110
+ accessModes: ["ReadWriteOnce"],
111
+ ...(storageClassName && { storageClassName }),
112
+ resources: { requests: { storage: storageSize } },
113
+ },
114
+ },
115
+ ],
116
+ },
117
+ };
118
+
119
+ // Headless service (clusterIP: None) for StatefulSet DNS
120
+ const serviceProps: Record<string, unknown> = {
121
+ metadata: {
122
+ name,
123
+ ...(namespace && { namespace }),
124
+ labels: { ...commonLabels, "app.kubernetes.io/component": "database" },
125
+ },
126
+ spec: {
127
+ selector: { "app.kubernetes.io/name": name },
128
+ ports: [{ port, targetPort: port, protocol: "TCP", name: "app" }],
129
+ clusterIP: "None",
130
+ },
131
+ };
132
+
133
+ return { statefulSet: statefulSetProps, service: serviceProps };
134
+ }