@intentius/chant-lexicon-helm 0.0.16

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 (110) hide show
  1. package/README.md +22 -0
  2. package/dist/integrity.json +36 -0
  3. package/dist/manifest.json +37 -0
  4. package/dist/meta.json +208 -0
  5. package/dist/rules/chart-metadata.ts +64 -0
  6. package/dist/rules/helm-helpers.ts +64 -0
  7. package/dist/rules/no-hardcoded-image.ts +62 -0
  8. package/dist/rules/values-no-secrets.ts +82 -0
  9. package/dist/rules/whm101.ts +46 -0
  10. package/dist/rules/whm102.ts +33 -0
  11. package/dist/rules/whm103.ts +59 -0
  12. package/dist/rules/whm104.ts +35 -0
  13. package/dist/rules/whm105.ts +30 -0
  14. package/dist/rules/whm201.ts +36 -0
  15. package/dist/rules/whm202.ts +50 -0
  16. package/dist/rules/whm203.ts +39 -0
  17. package/dist/rules/whm204.ts +60 -0
  18. package/dist/rules/whm301.ts +41 -0
  19. package/dist/rules/whm302.ts +40 -0
  20. package/dist/rules/whm401.ts +57 -0
  21. package/dist/rules/whm402.ts +45 -0
  22. package/dist/rules/whm403.ts +45 -0
  23. package/dist/rules/whm404.ts +36 -0
  24. package/dist/rules/whm405.ts +53 -0
  25. package/dist/rules/whm406.ts +34 -0
  26. package/dist/rules/whm407.ts +83 -0
  27. package/dist/rules/whm501.ts +103 -0
  28. package/dist/rules/whm502.ts +94 -0
  29. package/dist/skills/chant-helm-chart-patterns.md +229 -0
  30. package/dist/skills/chant-helm-chart-security-patterns.md +192 -0
  31. package/dist/skills/chant-helm-create-chart.md +211 -0
  32. package/dist/types/index.d.ts +132 -0
  33. package/package.json +34 -0
  34. package/src/codegen/docs-cli.ts +4 -0
  35. package/src/codegen/docs.ts +483 -0
  36. package/src/codegen/generate-cli.ts +28 -0
  37. package/src/codegen/generate.ts +249 -0
  38. package/src/codegen/naming.ts +38 -0
  39. package/src/codegen/package.ts +64 -0
  40. package/src/composites/composites.test.ts +1050 -0
  41. package/src/composites/helm-batch-job.ts +209 -0
  42. package/src/composites/helm-crd-lifecycle.ts +184 -0
  43. package/src/composites/helm-cron-job.ts +177 -0
  44. package/src/composites/helm-daemon-set.ts +169 -0
  45. package/src/composites/helm-external-secret.ts +93 -0
  46. package/src/composites/helm-library.ts +51 -0
  47. package/src/composites/helm-microservice.ts +331 -0
  48. package/src/composites/helm-monitored-service.ts +252 -0
  49. package/src/composites/helm-namespace-env.ts +154 -0
  50. package/src/composites/helm-secure-ingress.ts +114 -0
  51. package/src/composites/helm-stateful-service.ts +213 -0
  52. package/src/composites/helm-web-app.ts +264 -0
  53. package/src/composites/helm-worker.ts +207 -0
  54. package/src/composites/index.ts +38 -0
  55. package/src/coverage.test.ts +21 -0
  56. package/src/coverage.ts +50 -0
  57. package/src/generated/index.d.ts +132 -0
  58. package/src/generated/index.ts +13 -0
  59. package/src/generated/lexicon-helm.json +208 -0
  60. package/src/helpers.test.ts +51 -0
  61. package/src/helpers.ts +100 -0
  62. package/src/import/generator.ts +285 -0
  63. package/src/import/import.test.ts +224 -0
  64. package/src/import/parser.ts +160 -0
  65. package/src/import/template-stripper.ts +123 -0
  66. package/src/index.ts +108 -0
  67. package/src/intrinsics.test.ts +380 -0
  68. package/src/intrinsics.ts +484 -0
  69. package/src/lint/post-synth/helm-helpers.ts +64 -0
  70. package/src/lint/post-synth/post-synth.test.ts +504 -0
  71. package/src/lint/post-synth/whm101.ts +46 -0
  72. package/src/lint/post-synth/whm102.ts +33 -0
  73. package/src/lint/post-synth/whm103.ts +59 -0
  74. package/src/lint/post-synth/whm104.ts +35 -0
  75. package/src/lint/post-synth/whm105.ts +30 -0
  76. package/src/lint/post-synth/whm201.ts +36 -0
  77. package/src/lint/post-synth/whm202.ts +50 -0
  78. package/src/lint/post-synth/whm203.ts +39 -0
  79. package/src/lint/post-synth/whm204.ts +60 -0
  80. package/src/lint/post-synth/whm301.ts +41 -0
  81. package/src/lint/post-synth/whm302.ts +40 -0
  82. package/src/lint/post-synth/whm401.ts +57 -0
  83. package/src/lint/post-synth/whm402.ts +45 -0
  84. package/src/lint/post-synth/whm403.ts +45 -0
  85. package/src/lint/post-synth/whm404.ts +36 -0
  86. package/src/lint/post-synth/whm405.ts +53 -0
  87. package/src/lint/post-synth/whm406.ts +34 -0
  88. package/src/lint/post-synth/whm407.ts +83 -0
  89. package/src/lint/post-synth/whm501.ts +103 -0
  90. package/src/lint/post-synth/whm502.ts +94 -0
  91. package/src/lint/rules/chart-metadata.ts +64 -0
  92. package/src/lint/rules/lint-rules.test.ts +97 -0
  93. package/src/lint/rules/no-hardcoded-image.ts +62 -0
  94. package/src/lint/rules/values-no-secrets.ts +82 -0
  95. package/src/lsp/completions.test.ts +72 -0
  96. package/src/lsp/completions.ts +20 -0
  97. package/src/lsp/hover.test.ts +46 -0
  98. package/src/lsp/hover.ts +46 -0
  99. package/src/package-cli.ts +28 -0
  100. package/src/plugin.test.ts +71 -0
  101. package/src/plugin.ts +206 -0
  102. package/src/resources.ts +77 -0
  103. package/src/serializer.test.ts +930 -0
  104. package/src/serializer.ts +835 -0
  105. package/src/skills/chart-patterns.md +229 -0
  106. package/src/skills/chart-security-patterns.md +192 -0
  107. package/src/skills/create-chart.md +211 -0
  108. package/src/validate-cli.ts +21 -0
  109. package/src/validate.test.ts +37 -0
  110. package/src/validate.ts +36 -0
@@ -0,0 +1,213 @@
1
+ /**
2
+ * HelmStatefulService composite — StatefulSet + headless Service + PVC templates.
3
+ *
4
+ * Produces a Helm chart for stateful workloads with persistent storage.
5
+ */
6
+
7
+ import { values, include, printf, toYaml, With } from "../intrinsics";
8
+
9
+ export interface HelmStatefulServiceProps {
10
+ /** Chart and release name. */
11
+ name: string;
12
+ /** Default container image repository. */
13
+ imageRepository?: string;
14
+ /** Default container image tag. */
15
+ imageTag?: string;
16
+ /** Default replica count. */
17
+ replicas?: number;
18
+ /** Default service port. */
19
+ port?: number;
20
+ /** Default storage size. */
21
+ storageSize?: string;
22
+ /** Default storage class (empty = cluster default). */
23
+ storageClass?: string;
24
+ /** Chart appVersion. */
25
+ appVersion?: string;
26
+ /** Pod-level security context defaults. */
27
+ podSecurityContext?: Record<string, unknown>;
28
+ /** Container-level security context defaults. */
29
+ securityContext?: Record<string, unknown>;
30
+ /** Node selector defaults. */
31
+ nodeSelector?: Record<string, string>;
32
+ /** Tolerations defaults. */
33
+ tolerations?: Array<Record<string, unknown>>;
34
+ /** Affinity defaults. */
35
+ affinity?: Record<string, unknown>;
36
+ /** Pod annotations defaults. */
37
+ podAnnotations?: Record<string, string>;
38
+ /** Liveness probe defaults. */
39
+ livenessProbe?: Record<string, unknown>;
40
+ /** Readiness probe defaults. */
41
+ readinessProbe?: Record<string, unknown>;
42
+ /** Include ServiceAccount. Default: false. */
43
+ serviceAccount?: boolean;
44
+ /** StatefulSet update strategy defaults. */
45
+ updateStrategy?: Record<string, unknown>;
46
+ }
47
+
48
+ export interface HelmStatefulServiceResult {
49
+ chart: Record<string, unknown>;
50
+ values: Record<string, unknown>;
51
+ statefulSet: Record<string, unknown>;
52
+ service: Record<string, unknown>;
53
+ serviceAccount?: Record<string, unknown>;
54
+ }
55
+
56
+ export function HelmStatefulService(props: HelmStatefulServiceProps): HelmStatefulServiceResult {
57
+ const {
58
+ name,
59
+ imageRepository = "postgres",
60
+ imageTag = "16",
61
+ replicas = 1,
62
+ port = 5432,
63
+ storageSize = "10Gi",
64
+ storageClass = "",
65
+ appVersion = "1.0.0",
66
+ serviceAccount = false,
67
+ } = props;
68
+
69
+ const chart = {
70
+ apiVersion: "v2",
71
+ name,
72
+ version: "0.1.0",
73
+ appVersion,
74
+ type: "application",
75
+ description: `A Helm chart for ${name} (stateful)`,
76
+ };
77
+
78
+ const valuesObj: Record<string, unknown> = {
79
+ replicaCount: replicas,
80
+ image: {
81
+ repository: imageRepository,
82
+ tag: imageTag,
83
+ pullPolicy: "IfNotPresent",
84
+ },
85
+ service: {
86
+ port,
87
+ },
88
+ persistence: {
89
+ size: storageSize,
90
+ storageClass,
91
+ accessModes: ["ReadWriteOnce"],
92
+ },
93
+ resources: {},
94
+ };
95
+
96
+ if (props.podSecurityContext) valuesObj.podSecurityContext = props.podSecurityContext;
97
+ if (props.securityContext) valuesObj.securityContext = props.securityContext;
98
+ if (props.nodeSelector) valuesObj.nodeSelector = props.nodeSelector;
99
+ if (props.tolerations) valuesObj.tolerations = props.tolerations;
100
+ if (props.affinity) valuesObj.affinity = props.affinity;
101
+ if (props.podAnnotations) valuesObj.podAnnotations = props.podAnnotations;
102
+ if (props.livenessProbe) valuesObj.livenessProbe = props.livenessProbe;
103
+ if (props.readinessProbe) valuesObj.readinessProbe = props.readinessProbe;
104
+ if (props.updateStrategy) valuesObj.updateStrategy = props.updateStrategy;
105
+
106
+ if (serviceAccount) {
107
+ valuesObj.serviceAccount = {
108
+ create: true,
109
+ name: "",
110
+ annotations: {},
111
+ };
112
+ }
113
+
114
+ const containerSpec: Record<string, unknown> = {
115
+ name,
116
+ image: printf("%s:%s", values.image.repository, values.image.tag),
117
+ imagePullPolicy: values.image.pullPolicy,
118
+ ports: [{ containerPort: values.service.port, name: "tcp" }],
119
+ resources: toYaml(values.resources),
120
+ volumeMounts: [{
121
+ name: "data",
122
+ mountPath: "/data",
123
+ }],
124
+ };
125
+
126
+ if (props.securityContext) containerSpec.securityContext = toYaml(values.securityContext);
127
+ if (props.livenessProbe) containerSpec.livenessProbe = toYaml(values.livenessProbe);
128
+ if (props.readinessProbe) containerSpec.readinessProbe = toYaml(values.readinessProbe);
129
+
130
+ const podSpec: Record<string, unknown> = {
131
+ containers: [containerSpec],
132
+ };
133
+
134
+ if (props.podSecurityContext) podSpec.securityContext = toYaml(values.podSecurityContext);
135
+ if (props.nodeSelector) podSpec.nodeSelector = With(values.nodeSelector, toYaml(values.nodeSelector));
136
+ if (props.tolerations) podSpec.tolerations = With(values.tolerations, toYaml(values.tolerations));
137
+ if (props.affinity) podSpec.affinity = With(values.affinity, toYaml(values.affinity));
138
+ if (serviceAccount) podSpec.serviceAccountName = include(`${name}.serviceAccountName`);
139
+
140
+ const templateMetadata: Record<string, unknown> = {
141
+ labels: include(`${name}.selectorLabels`),
142
+ };
143
+ if (props.podAnnotations) templateMetadata.annotations = toYaml(values.podAnnotations);
144
+
145
+ const statefulSetSpec: Record<string, unknown> = {
146
+ serviceName: include(`${name}.fullname`),
147
+ replicas: values.replicaCount,
148
+ selector: {
149
+ matchLabels: include(`${name}.selectorLabels`),
150
+ },
151
+ template: {
152
+ metadata: templateMetadata,
153
+ spec: podSpec,
154
+ },
155
+ volumeClaimTemplates: [{
156
+ metadata: { name: "data" },
157
+ spec: {
158
+ accessModes: values.persistence.accessModes,
159
+ storageClassName: values.persistence.storageClass,
160
+ resources: {
161
+ requests: { storage: values.persistence.size },
162
+ },
163
+ },
164
+ }],
165
+ };
166
+
167
+ if (props.updateStrategy) statefulSetSpec.updateStrategy = toYaml(values.updateStrategy);
168
+
169
+ const statefulSet = {
170
+ apiVersion: "apps/v1",
171
+ kind: "StatefulSet",
172
+ metadata: {
173
+ name: include(`${name}.fullname`),
174
+ labels: include(`${name}.labels`),
175
+ },
176
+ spec: statefulSetSpec,
177
+ };
178
+
179
+ const service = {
180
+ apiVersion: "v1",
181
+ kind: "Service",
182
+ metadata: {
183
+ name: include(`${name}.fullname`),
184
+ labels: include(`${name}.labels`),
185
+ },
186
+ spec: {
187
+ clusterIP: "None",
188
+ ports: [{
189
+ port: values.service.port,
190
+ targetPort: "tcp",
191
+ protocol: "TCP",
192
+ name: "tcp",
193
+ }],
194
+ selector: include(`${name}.selectorLabels`),
195
+ },
196
+ };
197
+
198
+ const result: HelmStatefulServiceResult = { chart, values: valuesObj, statefulSet, service };
199
+
200
+ if (serviceAccount) {
201
+ result.serviceAccount = {
202
+ apiVersion: "v1",
203
+ kind: "ServiceAccount",
204
+ metadata: {
205
+ name: include(`${name}.serviceAccountName`),
206
+ labels: include(`${name}.labels`),
207
+ annotations: toYaml(values.serviceAccount.annotations),
208
+ },
209
+ };
210
+ }
211
+
212
+ return result;
213
+ }
@@ -0,0 +1,264 @@
1
+ /**
2
+ * HelmWebApp composite — Deployment + Service + Ingress (conditional) + HPA (conditional) + ServiceAccount.
3
+ *
4
+ * Produces a full set of Helm chart entities with parameterized values references.
5
+ */
6
+
7
+ import { values, include, printf, toYaml, If, With } from "../intrinsics";
8
+
9
+ export interface HelmWebAppProps {
10
+ /** Chart and release name. */
11
+ name: string;
12
+ /** Default container image repository. */
13
+ imageRepository?: string;
14
+ /** Default container image tag (empty = appVersion). */
15
+ imageTag?: string;
16
+ /** Default service port. */
17
+ port?: number;
18
+ /** Default replica count. */
19
+ replicas?: number;
20
+ /** Default service type. */
21
+ serviceType?: string;
22
+ /** Include Ingress resource (conditional on values.ingress.enabled). */
23
+ ingress?: boolean;
24
+ /** Include HPA resource (conditional on values.autoscaling.enabled). */
25
+ autoscaling?: boolean;
26
+ /** Include ServiceAccount (conditional on values.serviceAccount.create). */
27
+ serviceAccount?: boolean;
28
+ /** Chart appVersion. */
29
+ appVersion?: string;
30
+ /** Pod-level security context defaults. */
31
+ podSecurityContext?: Record<string, unknown>;
32
+ /** Container-level security context defaults. */
33
+ securityContext?: Record<string, unknown>;
34
+ /** Node selector defaults. */
35
+ nodeSelector?: Record<string, string>;
36
+ /** Tolerations defaults. */
37
+ tolerations?: Array<Record<string, unknown>>;
38
+ /** Affinity defaults. */
39
+ affinity?: Record<string, unknown>;
40
+ /** Pod annotations defaults. */
41
+ podAnnotations?: Record<string, string>;
42
+ /** Liveness probe defaults. */
43
+ livenessProbe?: Record<string, unknown>;
44
+ /** Readiness probe defaults. */
45
+ readinessProbe?: Record<string, unknown>;
46
+ /** Deployment strategy defaults. */
47
+ strategy?: Record<string, unknown>;
48
+ }
49
+
50
+ export interface HelmWebAppResult {
51
+ chart: Record<string, unknown>;
52
+ values: Record<string, unknown>;
53
+ deployment: Record<string, unknown>;
54
+ service: Record<string, unknown>;
55
+ serviceAccount?: Record<string, unknown>;
56
+ ingress?: Record<string, unknown>;
57
+ hpa?: Record<string, unknown>;
58
+ }
59
+
60
+ export function HelmWebApp(props: HelmWebAppProps): HelmWebAppResult {
61
+ const {
62
+ name,
63
+ imageRepository = "nginx",
64
+ imageTag = "",
65
+ port = 80,
66
+ replicas = 1,
67
+ serviceType = "ClusterIP",
68
+ ingress = true,
69
+ autoscaling = true,
70
+ serviceAccount = true,
71
+ appVersion = "1.0.0",
72
+ } = props;
73
+
74
+ const chart = {
75
+ apiVersion: "v2",
76
+ name,
77
+ version: "0.1.0",
78
+ appVersion,
79
+ type: "application",
80
+ description: `A Helm chart for ${name}`,
81
+ };
82
+
83
+ const valuesObj: Record<string, unknown> = {
84
+ replicaCount: replicas,
85
+ image: {
86
+ repository: imageRepository,
87
+ tag: imageTag,
88
+ pullPolicy: "IfNotPresent",
89
+ },
90
+ service: {
91
+ type: serviceType,
92
+ port,
93
+ },
94
+ resources: {},
95
+ };
96
+
97
+ if (props.podSecurityContext) valuesObj.podSecurityContext = props.podSecurityContext;
98
+ if (props.securityContext) valuesObj.securityContext = props.securityContext;
99
+ if (props.nodeSelector) valuesObj.nodeSelector = props.nodeSelector;
100
+ if (props.tolerations) valuesObj.tolerations = props.tolerations;
101
+ if (props.affinity) valuesObj.affinity = props.affinity;
102
+ if (props.podAnnotations) valuesObj.podAnnotations = props.podAnnotations;
103
+ if (props.livenessProbe) valuesObj.livenessProbe = props.livenessProbe;
104
+ if (props.readinessProbe) valuesObj.readinessProbe = props.readinessProbe;
105
+ if (props.strategy) valuesObj.strategy = props.strategy;
106
+
107
+ if (serviceAccount) {
108
+ valuesObj.serviceAccount = {
109
+ create: true,
110
+ name: "",
111
+ annotations: {},
112
+ };
113
+ }
114
+
115
+ if (ingress) {
116
+ valuesObj.ingress = {
117
+ enabled: false,
118
+ className: "",
119
+ annotations: {},
120
+ hosts: [{ host: `${name}.local`, paths: [{ path: "/", pathType: "ImplementationSpecific" }] }],
121
+ tls: [],
122
+ };
123
+ }
124
+
125
+ if (autoscaling) {
126
+ valuesObj.autoscaling = {
127
+ enabled: false,
128
+ minReplicas: 1,
129
+ maxReplicas: 100,
130
+ targetCPUUtilizationPercentage: 80,
131
+ };
132
+ }
133
+
134
+ const containerSpec: Record<string, unknown> = {
135
+ name,
136
+ image: printf("%s:%s", values.image.repository, values.image.tag),
137
+ imagePullPolicy: values.image.pullPolicy,
138
+ ports: [{ containerPort: values.service.port, name: "http" }],
139
+ resources: toYaml(values.resources),
140
+ };
141
+
142
+ if (props.securityContext) containerSpec.securityContext = toYaml(values.securityContext);
143
+ if (props.livenessProbe) containerSpec.livenessProbe = toYaml(values.livenessProbe);
144
+ if (props.readinessProbe) containerSpec.readinessProbe = toYaml(values.readinessProbe);
145
+
146
+ const podSpec: Record<string, unknown> = {
147
+ containers: [containerSpec],
148
+ };
149
+
150
+ if (props.podSecurityContext) podSpec.securityContext = toYaml(values.podSecurityContext);
151
+ if (props.nodeSelector) podSpec.nodeSelector = With(values.nodeSelector, toYaml(values.nodeSelector));
152
+ if (props.tolerations) podSpec.tolerations = With(values.tolerations, toYaml(values.tolerations));
153
+ if (props.affinity) podSpec.affinity = With(values.affinity, toYaml(values.affinity));
154
+
155
+ const templateMetadata: Record<string, unknown> = {
156
+ labels: include(`${name}.selectorLabels`),
157
+ };
158
+ if (props.podAnnotations) templateMetadata.annotations = toYaml(values.podAnnotations);
159
+
160
+ const deploymentSpec: Record<string, unknown> = {
161
+ replicas: values.replicaCount,
162
+ selector: {
163
+ matchLabels: include(`${name}.selectorLabels`),
164
+ },
165
+ template: {
166
+ metadata: templateMetadata,
167
+ spec: podSpec,
168
+ },
169
+ };
170
+
171
+ if (props.strategy) deploymentSpec.strategy = toYaml(values.strategy);
172
+
173
+ const deployment = {
174
+ apiVersion: "apps/v1",
175
+ kind: "Deployment",
176
+ metadata: {
177
+ name: include(`${name}.fullname`),
178
+ labels: include(`${name}.labels`),
179
+ },
180
+ spec: deploymentSpec,
181
+ };
182
+
183
+ const service = {
184
+ apiVersion: "v1",
185
+ kind: "Service",
186
+ metadata: {
187
+ name: include(`${name}.fullname`),
188
+ labels: include(`${name}.labels`),
189
+ },
190
+ spec: {
191
+ type: values.service.type,
192
+ ports: [{
193
+ port: values.service.port,
194
+ targetPort: "http",
195
+ protocol: "TCP",
196
+ name: "http",
197
+ }],
198
+ selector: include(`${name}.selectorLabels`),
199
+ },
200
+ };
201
+
202
+ const result: HelmWebAppResult = { chart, values: valuesObj, deployment, service };
203
+
204
+ if (serviceAccount) {
205
+ result.serviceAccount = {
206
+ apiVersion: "v1",
207
+ kind: "ServiceAccount",
208
+ metadata: {
209
+ name: include(`${name}.serviceAccountName`),
210
+ labels: include(`${name}.labels`),
211
+ annotations: toYaml(values.serviceAccount.annotations),
212
+ },
213
+ };
214
+ }
215
+
216
+ if (ingress) {
217
+ result.ingress = {
218
+ apiVersion: "networking.k8s.io/v1",
219
+ kind: "Ingress",
220
+ metadata: {
221
+ name: include(`${name}.fullname`),
222
+ labels: include(`${name}.labels`),
223
+ annotations: toYaml(values.ingress.annotations),
224
+ },
225
+ spec: {
226
+ ingressClassName: values.ingress.className,
227
+ rules: values.ingress.hosts,
228
+ tls: values.ingress.tls,
229
+ },
230
+ };
231
+ }
232
+
233
+ if (autoscaling) {
234
+ result.hpa = {
235
+ apiVersion: "autoscaling/v2",
236
+ kind: "HorizontalPodAutoscaler",
237
+ metadata: {
238
+ name: include(`${name}.fullname`),
239
+ labels: include(`${name}.labels`),
240
+ },
241
+ spec: {
242
+ scaleTargetRef: {
243
+ apiVersion: "apps/v1",
244
+ kind: "Deployment",
245
+ name: include(`${name}.fullname`),
246
+ },
247
+ minReplicas: values.autoscaling.minReplicas,
248
+ maxReplicas: values.autoscaling.maxReplicas,
249
+ metrics: [{
250
+ type: "Resource",
251
+ resource: {
252
+ name: "cpu",
253
+ target: {
254
+ type: "Utilization",
255
+ averageUtilization: values.autoscaling.targetCPUUtilizationPercentage,
256
+ },
257
+ },
258
+ }],
259
+ },
260
+ };
261
+ }
262
+
263
+ return result;
264
+ }
@@ -0,0 +1,207 @@
1
+ /**
2
+ * HelmWorker composite — Deployment (no Service) + ServiceAccount + optional HPA + PDB.
3
+ *
4
+ * Worker pattern for background processors, queue consumers, and async tasks
5
+ * that don't serve HTTP traffic.
6
+ */
7
+
8
+ import { values, include, printf, toYaml, If } from "../intrinsics";
9
+
10
+ export interface HelmWorkerProps {
11
+ /** Chart and release name. */
12
+ name: string;
13
+ /** Default container image repository. */
14
+ imageRepository?: string;
15
+ /** Default container image tag. */
16
+ imageTag?: string;
17
+ /** Default replica count. Default: 2. */
18
+ replicas?: number;
19
+ /** Include HPA (conditional). Default: false. */
20
+ autoscaling?: boolean;
21
+ /** Include PDB. Default: true. */
22
+ pdb?: boolean;
23
+ /** Chart appVersion. */
24
+ appVersion?: string;
25
+ }
26
+
27
+ export interface HelmWorkerResult {
28
+ chart: Record<string, unknown>;
29
+ values: Record<string, unknown>;
30
+ deployment: Record<string, unknown>;
31
+ serviceAccount: Record<string, unknown>;
32
+ hpa?: Record<string, unknown>;
33
+ pdb?: Record<string, unknown>;
34
+ }
35
+
36
+ export function HelmWorker(props: HelmWorkerProps): HelmWorkerResult {
37
+ const {
38
+ name,
39
+ imageRepository = "worker",
40
+ imageTag = "",
41
+ replicas = 2,
42
+ autoscaling = false,
43
+ pdb = true,
44
+ appVersion = "1.0.0",
45
+ } = props;
46
+
47
+ const chart = {
48
+ apiVersion: "v2",
49
+ name,
50
+ version: "0.1.0",
51
+ appVersion,
52
+ type: "application",
53
+ description: `A Helm chart for ${name} worker`,
54
+ };
55
+
56
+ const valuesObj: Record<string, unknown> = {
57
+ replicaCount: replicas,
58
+ image: {
59
+ repository: imageRepository,
60
+ tag: imageTag,
61
+ pullPolicy: "IfNotPresent",
62
+ },
63
+ serviceAccount: {
64
+ create: true,
65
+ name: "",
66
+ annotations: {},
67
+ },
68
+ resources: {
69
+ limits: { cpu: "500m", memory: "256Mi" },
70
+ requests: { cpu: "100m", memory: "128Mi" },
71
+ },
72
+ queue: {
73
+ concurrency: 5,
74
+ pollInterval: "5s",
75
+ },
76
+ livenessProbe: {
77
+ exec: { command: ["/bin/sh", "-c", "pgrep -f worker"] },
78
+ initialDelaySeconds: 15,
79
+ periodSeconds: 20,
80
+ },
81
+ readinessProbe: {
82
+ exec: { command: ["/bin/sh", "-c", "pgrep -f worker"] },
83
+ initialDelaySeconds: 5,
84
+ periodSeconds: 10,
85
+ },
86
+ };
87
+
88
+ if (autoscaling) {
89
+ valuesObj.autoscaling = {
90
+ enabled: false,
91
+ minReplicas: replicas,
92
+ maxReplicas: 10,
93
+ targetCPUUtilizationPercentage: 80,
94
+ };
95
+ }
96
+
97
+ if (pdb) {
98
+ valuesObj.podDisruptionBudget = {
99
+ enabled: true,
100
+ minAvailable: 1,
101
+ };
102
+ }
103
+
104
+ const deployment = {
105
+ apiVersion: "apps/v1",
106
+ kind: "Deployment",
107
+ metadata: {
108
+ name: include(`${name}.fullname`),
109
+ labels: include(`${name}.labels`),
110
+ },
111
+ spec: {
112
+ replicas: values.replicaCount,
113
+ selector: {
114
+ matchLabels: include(`${name}.selectorLabels`),
115
+ },
116
+ template: {
117
+ metadata: {
118
+ labels: include(`${name}.selectorLabels`),
119
+ },
120
+ spec: {
121
+ serviceAccountName: include(`${name}.serviceAccountName`),
122
+ securityContext: {
123
+ runAsNonRoot: true,
124
+ },
125
+ containers: [{
126
+ name,
127
+ image: printf("%s:%s", values.image.repository, values.image.tag),
128
+ imagePullPolicy: values.image.pullPolicy,
129
+ resources: toYaml(values.resources),
130
+ livenessProbe: toYaml(values.livenessProbe),
131
+ readinessProbe: toYaml(values.readinessProbe),
132
+ env: [
133
+ { name: "QUEUE_CONCURRENCY", value: values.queue.concurrency },
134
+ { name: "QUEUE_POLL_INTERVAL", value: values.queue.pollInterval },
135
+ ],
136
+ }],
137
+ },
138
+ },
139
+ },
140
+ };
141
+
142
+ const serviceAccount = {
143
+ apiVersion: "v1",
144
+ kind: "ServiceAccount",
145
+ metadata: {
146
+ name: include(`${name}.serviceAccountName`),
147
+ labels: include(`${name}.labels`),
148
+ annotations: toYaml(values.serviceAccount.annotations),
149
+ },
150
+ };
151
+
152
+ const result: HelmWorkerResult = {
153
+ chart,
154
+ values: valuesObj,
155
+ deployment,
156
+ serviceAccount,
157
+ };
158
+
159
+ if (autoscaling) {
160
+ result.hpa = {
161
+ apiVersion: "autoscaling/v2",
162
+ kind: "HorizontalPodAutoscaler",
163
+ metadata: {
164
+ name: include(`${name}.fullname`),
165
+ labels: include(`${name}.labels`),
166
+ },
167
+ spec: {
168
+ scaleTargetRef: {
169
+ apiVersion: "apps/v1",
170
+ kind: "Deployment",
171
+ name: include(`${name}.fullname`),
172
+ },
173
+ minReplicas: values.autoscaling.minReplicas,
174
+ maxReplicas: values.autoscaling.maxReplicas,
175
+ metrics: [{
176
+ type: "Resource",
177
+ resource: {
178
+ name: "cpu",
179
+ target: {
180
+ type: "Utilization",
181
+ averageUtilization: values.autoscaling.targetCPUUtilizationPercentage,
182
+ },
183
+ },
184
+ }],
185
+ },
186
+ };
187
+ }
188
+
189
+ if (pdb) {
190
+ result.pdb = {
191
+ apiVersion: "policy/v1",
192
+ kind: "PodDisruptionBudget",
193
+ metadata: {
194
+ name: include(`${name}.fullname`),
195
+ labels: include(`${name}.labels`),
196
+ },
197
+ spec: {
198
+ minAvailable: values.podDisruptionBudget.minAvailable,
199
+ selector: {
200
+ matchLabels: include(`${name}.selectorLabels`),
201
+ },
202
+ },
203
+ };
204
+ }
205
+
206
+ return result;
207
+ }