@highstate/k8s 0.20.0 → 0.21.1

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 (81) hide show
  1. package/dist/chunk-23vn2rdc.js +11 -0
  2. package/dist/chunk-2pfx13ay.js +11 -0
  3. package/dist/chunk-46ntav0c.js +299 -0
  4. package/dist/chunk-556pc9e6.js +155 -0
  5. package/dist/chunk-7kgjgcft.js +170 -0
  6. package/dist/{chunk-ADHZK6V2.js → chunk-9hs97f1q.js} +13 -11
  7. package/dist/chunk-aame3x1b.js +11 -0
  8. package/dist/chunk-b05q6fm2.js +37 -0
  9. package/dist/chunk-bmvc9d2d.js +11 -0
  10. package/dist/chunk-de82bbp2.js +7 -0
  11. package/dist/chunk-facs31cb.js +624 -0
  12. package/dist/chunk-h1b79v66.js +1425 -0
  13. package/dist/chunk-k4w9zpn5.js +215 -0
  14. package/dist/chunk-pqc6w52f.js +352 -0
  15. package/dist/chunk-qyshvz32.js +176 -0
  16. package/dist/chunk-tpfyj6fe.js +199 -0
  17. package/dist/chunk-z6bmpnm7.js +180 -0
  18. package/dist/highstate.manifest.json +3 -3
  19. package/dist/impl/dynamic-endpoint-resolver.js +82 -81
  20. package/dist/impl/gateway-route.js +131 -168
  21. package/dist/impl/tls-certificate.js +31 -32
  22. package/dist/index.js +245 -201
  23. package/dist/units/cert-manager/index.js +19 -13
  24. package/dist/units/cluster-patch/index.js +9 -8
  25. package/dist/units/dns01-issuer/index.js +44 -41
  26. package/dist/units/existing-cluster/index.js +25 -13
  27. package/dist/units/gateway-api/index.js +15 -16
  28. package/dist/units/reduced-access-cluster/index.js +28 -32
  29. package/package.json +21 -21
  30. package/src/cron-job.ts +26 -1
  31. package/src/deployment.ts +17 -1
  32. package/src/job.ts +15 -1
  33. package/src/scripting/bundle.ts +21 -98
  34. package/src/scripting/environment.ts +2 -9
  35. package/src/shared.ts +1 -1
  36. package/src/stateful-set.ts +17 -1
  37. package/src/workload.ts +31 -14
  38. package/LICENSE +0 -21
  39. package/dist/chunk-23X5SXQG.js +0 -301
  40. package/dist/chunk-23X5SXQG.js.map +0 -1
  41. package/dist/chunk-ADHZK6V2.js.map +0 -1
  42. package/dist/chunk-BTAEFJ5N.js +0 -291
  43. package/dist/chunk-BTAEFJ5N.js.map +0 -1
  44. package/dist/chunk-HH2JJELM.js +0 -13
  45. package/dist/chunk-HH2JJELM.js.map +0 -1
  46. package/dist/chunk-IXE3OKB4.js +0 -249
  47. package/dist/chunk-IXE3OKB4.js.map +0 -1
  48. package/dist/chunk-OG2OPX7B.js +0 -333
  49. package/dist/chunk-OG2OPX7B.js.map +0 -1
  50. package/dist/chunk-P26SQ2ZB.js +0 -393
  51. package/dist/chunk-P26SQ2ZB.js.map +0 -1
  52. package/dist/chunk-PG27ZY2H.js +0 -319
  53. package/dist/chunk-PG27ZY2H.js.map +0 -1
  54. package/dist/chunk-PZYGZSN5.js +0 -54
  55. package/dist/chunk-PZYGZSN5.js.map +0 -1
  56. package/dist/chunk-S77TE7UC.js +0 -309
  57. package/dist/chunk-S77TE7UC.js.map +0 -1
  58. package/dist/chunk-SZKOAHNX.js +0 -1804
  59. package/dist/chunk-SZKOAHNX.js.map +0 -1
  60. package/dist/chunk-TOLFVF4S.js +0 -889
  61. package/dist/chunk-TOLFVF4S.js.map +0 -1
  62. package/dist/chunk-TVKT3ZYX.js +0 -423
  63. package/dist/chunk-TVKT3ZYX.js.map +0 -1
  64. package/dist/cron-job-RKB2HYTO.js +0 -7
  65. package/dist/cron-job-RKB2HYTO.js.map +0 -1
  66. package/dist/deployment-T35TUOL2.js +0 -7
  67. package/dist/deployment-T35TUOL2.js.map +0 -1
  68. package/dist/impl/dynamic-endpoint-resolver.js.map +0 -1
  69. package/dist/impl/gateway-route.js.map +0 -1
  70. package/dist/impl/tls-certificate.js.map +0 -1
  71. package/dist/index.js.map +0 -1
  72. package/dist/job-PE4AKOHB.js +0 -7
  73. package/dist/job-PE4AKOHB.js.map +0 -1
  74. package/dist/stateful-set-LUIRHQJY.js +0 -7
  75. package/dist/stateful-set-LUIRHQJY.js.map +0 -1
  76. package/dist/units/cert-manager/index.js.map +0 -1
  77. package/dist/units/cluster-patch/index.js.map +0 -1
  78. package/dist/units/dns01-issuer/index.js.map +0 -1
  79. package/dist/units/existing-cluster/index.js.map +0 -1
  80. package/dist/units/gateway-api/index.js.map +0 -1
  81. package/dist/units/reduced-access-cluster/index.js.map +0 -1
@@ -0,0 +1,215 @@
1
+ // @bun
2
+ import {
3
+ Namespace,
4
+ NamespacedResource,
5
+ commonExtraArgs,
6
+ getProvider,
7
+ mapMetadata
8
+ } from "./chunk-facs31cb.js";
9
+
10
+ // src/service.ts
11
+ import {
12
+ addEndpointMetadata,
13
+ l3EndpointToL4,
14
+ mergeEndpoints,
15
+ parseEndpoint,
16
+ parseL4Protocol
17
+ } from "@highstate/common";
18
+ import { check, getOrCreate } from "@highstate/contract";
19
+ import { k8s } from "@highstate/library";
20
+ import {
21
+ interpolate,
22
+ makeEntityOutput,
23
+ normalize,
24
+ output,
25
+ toPromise
26
+ } from "@highstate/pulumi";
27
+ import { core } from "@pulumi/kubernetes";
28
+ import { omit, pipe } from "remeda";
29
+ var serviceExtraArgs = [...commonExtraArgs, "port", "ports", "external"];
30
+ function isEndpointFromCluster(endpoint, cluster) {
31
+ return check(k8s.serviceEndpointSchema, endpoint) && endpoint.metadata["k8s.service"].clusterId === cluster.id;
32
+ }
33
+
34
+ class Service extends NamespacedResource {
35
+ spec;
36
+ status;
37
+ static apiVersion = "v1";
38
+ static kind = "Service";
39
+ constructor(type, name, args, opts, metadata, namespace, spec, status) {
40
+ super(type, name, args, opts, metadata, namespace);
41
+ this.spec = spec;
42
+ this.status = status;
43
+ }
44
+ get entity() {
45
+ return makeEntityOutput({
46
+ entity: k8s.serviceEntity,
47
+ identity: this.metadata.uid,
48
+ meta: {
49
+ title: this.metadata.name
50
+ },
51
+ value: {
52
+ ...this.entityBase,
53
+ endpoints: this.endpoints
54
+ }
55
+ });
56
+ }
57
+ static create(name, args, opts) {
58
+ return new CreatedService(name, args, opts);
59
+ }
60
+ static createOrPatch(name, args, opts) {
61
+ if (args.existing) {
62
+ return new ServicePatch(name, {
63
+ ...args,
64
+ name: output(args.existing).metadata.name,
65
+ namespace: Namespace.forResourceAsync(args.existing, output(args.namespace).cluster)
66
+ });
67
+ }
68
+ return new CreatedService(name, args, opts);
69
+ }
70
+ static async createOrGet(name, args, opts) {
71
+ if (args.existing) {
72
+ return await Service.forAsync(args.existing, output(args.namespace).cluster);
73
+ }
74
+ return new CreatedService(name, args, opts);
75
+ }
76
+ static patch(name, args, opts) {
77
+ return new ServicePatch(name, args, opts);
78
+ }
79
+ static wrap(name, args, opts) {
80
+ return new WrappedService(name, args, opts);
81
+ }
82
+ static get(name, args, opts) {
83
+ return new ExternalService(name, args, opts);
84
+ }
85
+ static serviceCache = new Map;
86
+ static for(entity, cluster) {
87
+ return getOrCreate(Service.serviceCache, `${entity.clusterName}.${entity.metadata.namespace}.${entity.metadata.name}.${entity.clusterId}`, (name) => {
88
+ return Service.get(name, {
89
+ name: entity.metadata.name,
90
+ namespace: Namespace.forResourceAsync(entity, cluster)
91
+ });
92
+ });
93
+ }
94
+ static async forAsync(entity, cluster) {
95
+ const resolvedEntity = await toPromise(entity);
96
+ return Service.for(resolvedEntity, output(cluster));
97
+ }
98
+ get endpoints() {
99
+ return output({
100
+ cluster: this.cluster,
101
+ metadata: this.metadata,
102
+ spec: this.spec,
103
+ status: this.status,
104
+ portForwardCluster: this.namespace.portForwardCluster
105
+ }).apply(({ cluster, metadata, spec, status, portForwardCluster }) => {
106
+ function createMetadata(isInternal) {
107
+ return {
108
+ "k8s.service": {
109
+ clusterId: cluster.id,
110
+ clusterName: cluster.name,
111
+ name: metadata.name,
112
+ namespace: metadata.namespace,
113
+ isInternal,
114
+ selector: spec.selector,
115
+ targetPort: spec.ports[0].targetPort ?? spec.ports[0].port
116
+ }
117
+ };
118
+ }
119
+ const implRef = portForwardCluster ? {
120
+ package: "@highstate/k8s",
121
+ data: {
122
+ cluster: portForwardCluster
123
+ }
124
+ } : undefined;
125
+ const internalHosts = spec.clusterIPs.length ? [...spec.clusterIPs, `${metadata.name}.${metadata.namespace}.svc.cluster.local`] : [];
126
+ const internalEndpoints = internalHosts.flatMap((ip) => spec.ports.map((port) => pipe(ip, parseEndpoint, (endpoint) => l3EndpointToL4(endpoint, port.port, parseL4Protocol(port.protocol)), (endpoint) => addEndpointMetadata(endpoint, createMetadata(true)), (endpoint) => implRef ? { ...endpoint, implRef } : endpoint)));
127
+ const externalHosts = spec.type === "NodePort" || spec.type === "LoadBalancer" ? [
128
+ ...spec.externalIPs,
129
+ ...cluster.endpoints,
130
+ ...status.loadBalancer?.ingress?.map((ingress) => ingress.ip ?? ingress.hostname) ?? []
131
+ ] : [];
132
+ const externalEndpoints = externalHosts.flatMap((ip) => spec.ports.map((port) => pipe(ip, parseEndpoint, (endpoint) => l3EndpointToL4(endpoint, port.nodePort, parseL4Protocol(port.protocol)), (endpoint) => addEndpointMetadata(endpoint, createMetadata(false)), (endpoint) => implRef ? { ...endpoint, implRef } : endpoint)));
133
+ return mergeEndpoints([...internalEndpoints, ...externalEndpoints]);
134
+ });
135
+ }
136
+ }
137
+ function createServiceSpec(args, cluster) {
138
+ return output(args).apply((args2) => {
139
+ return {
140
+ ...omit(args2, serviceExtraArgs),
141
+ ports: normalize(args2.port, args2.ports),
142
+ externalIPs: args2.externalIPs && args2.externalIPs.length > 0 ? args2.externalIPs : cluster.externalIps.map((ip) => ip.value),
143
+ type: getServiceType(args2, cluster)
144
+ };
145
+ });
146
+ }
147
+
148
+ class CreatedService extends Service {
149
+ constructor(name, args, opts) {
150
+ const service = output(args.namespace).cluster.apply((cluster) => {
151
+ return new core.v1.Service(name, {
152
+ metadata: mapMetadata(args, name),
153
+ spec: createServiceSpec(args, cluster)
154
+ }, { ...opts, parent: this, provider: getProvider(cluster) });
155
+ });
156
+ super("highstate:k8s:Service", name, args, opts, service.metadata, output(args.namespace), service.spec, service.status);
157
+ }
158
+ }
159
+
160
+ class ServicePatch extends Service {
161
+ constructor(name, args, opts) {
162
+ const service = output(args.namespace).cluster.apply((cluster) => {
163
+ return new core.v1.ServicePatch(name, {
164
+ metadata: mapMetadata(args, name),
165
+ spec: createServiceSpec(args, cluster)
166
+ }, { ...opts, parent: this, provider: getProvider(cluster) });
167
+ });
168
+ super("highstate:k8s:ServicePatch", name, args, opts, service.metadata, output(args.namespace), service.spec, service.status);
169
+ }
170
+ }
171
+
172
+ class WrappedService extends Service {
173
+ constructor(name, args, opts) {
174
+ super("highstate:k8s:WrappedService", name, args, opts, output(args.service).metadata, output(args.namespace), output(args.service).spec, output(args.service).status);
175
+ }
176
+ }
177
+
178
+ class ExternalService extends Service {
179
+ constructor(name, args, opts) {
180
+ const service = output(args.namespace).cluster.apply((cluster) => {
181
+ return core.v1.Service.get(name, interpolate`${output(args.namespace).metadata.name}/${args.name}`, { ...opts, parent: this, provider: getProvider(cluster) });
182
+ });
183
+ super("highstate:k8s:ExternalService", name, args, opts, service.metadata, output(args.namespace), service.spec, service.status);
184
+ }
185
+ }
186
+ function mapContainerPortToServicePort(port) {
187
+ return {
188
+ name: port.name,
189
+ port: port.containerPort,
190
+ targetPort: port.containerPort,
191
+ protocol: port.protocol
192
+ };
193
+ }
194
+ function mapServiceToLabelSelector(service) {
195
+ return {
196
+ matchLabels: service.spec.selector
197
+ };
198
+ }
199
+ function getServiceType(service, cluster) {
200
+ if (service?.type) {
201
+ return service.type;
202
+ }
203
+ if (!service?.external) {
204
+ return "ClusterIP";
205
+ }
206
+ return cluster.quirks?.externalServiceType === "LoadBalancer" ? "LoadBalancer" : "NodePort";
207
+ }
208
+ function l4EndpointToServicePort(endpoint) {
209
+ return {
210
+ port: endpoint.port,
211
+ protocol: endpoint.protocol.toUpperCase()
212
+ };
213
+ }
214
+
215
+ export { isEndpointFromCluster, Service, createServiceSpec, mapContainerPortToServicePort, mapServiceToLabelSelector, getServiceType, l4EndpointToServicePort };
@@ -0,0 +1,352 @@
1
+ // @bun
2
+ import {
3
+ Deployment
4
+ } from "./chunk-7kgjgcft.js";
5
+ import {
6
+ StatefulSet
7
+ } from "./chunk-z6bmpnm7.js";
8
+ import {
9
+ NetworkPolicy
10
+ } from "./chunk-h1b79v66.js";
11
+ import {
12
+ Service,
13
+ createServiceSpec
14
+ } from "./chunk-k4w9zpn5.js";
15
+ import {
16
+ getNamespaceName,
17
+ getProvider
18
+ } from "./chunk-facs31cb.js";
19
+
20
+ // src/helm.ts
21
+ import { mkdir, readFile, unlink } from "fs/promises";
22
+ import { resolve } from "path";
23
+ import {
24
+ AccessPointRoute
25
+ } from "@highstate/common";
26
+ import { normalize, normalizeInputs, toPromise } from "@highstate/pulumi";
27
+ import { local } from "@pulumi/command";
28
+ import { apps, core, helm } from "@pulumi/kubernetes";
29
+ import {
30
+ ComponentResource,
31
+ output
32
+ } from "@pulumi/pulumi";
33
+ import { sha256 } from "crypto-hash";
34
+ import { glob } from "glob";
35
+ import spawn from "nano-spawn";
36
+ import { isNonNullish, omit } from "remeda";
37
+ class Chart extends ComponentResource {
38
+ name;
39
+ args;
40
+ opts;
41
+ chart;
42
+ routes;
43
+ networkPolicies;
44
+ workloads;
45
+ constructor(name, args, opts) {
46
+ super("highstate:k8s:Chart", name, args, opts);
47
+ this.name = name;
48
+ this.args = args;
49
+ this.opts = opts;
50
+ const namespace = output(args.namespace).apply((namespace2) => output(namespace2 ? getNamespaceName(namespace2) : "default"));
51
+ this.chart = output(args.namespace).cluster.apply((cluster) => {
52
+ return new helm.v4.Chart(name, omit({
53
+ ...args,
54
+ chart: resolveHelmChart(args.chart),
55
+ namespace
56
+ }, ["route", "routes"]), {
57
+ ...opts,
58
+ parent: this,
59
+ provider: getProvider(cluster),
60
+ transforms: [
61
+ ...opts?.transforms ?? [],
62
+ async (resourceArgs) => {
63
+ const namespace2 = await toPromise(output(args.namespace).metadata.name);
64
+ const serviceName = args.serviceName ?? name;
65
+ const expectedName = `${name}:${namespace2}/${serviceName}`;
66
+ if (resourceArgs.type === "kubernetes:core/v1:Service" && resourceArgs.name === expectedName) {
67
+ const spec = await toPromise(resourceArgs.props.spec);
68
+ const serviceSpec = await toPromise(createServiceSpec(args.service ?? {}, cluster));
69
+ return {
70
+ props: {
71
+ ...resourceArgs.props,
72
+ spec: {
73
+ ...spec,
74
+ ...serviceSpec.ports?.length !== 0 ? serviceSpec : omit(serviceSpec, ["ports"])
75
+ }
76
+ },
77
+ opts: resourceArgs.opts
78
+ };
79
+ }
80
+ return;
81
+ }
82
+ ]
83
+ });
84
+ });
85
+ this.routes = output(normalizeInputs(args.route ? { name: "default", route: args.route } : undefined, args.routes ? Object.entries(args.routes).map(([name2, route]) => ({ name: name2, route })) : undefined)).apply(async (routes) => {
86
+ if (routes.length === 0) {
87
+ return [];
88
+ }
89
+ return await Promise.all(routes.map(async ({ name: routeName, route }) => {
90
+ const { serviceName: _serviceName, rules: _rules, ...baseRoute } = route;
91
+ const accessPoint = route.accessPoint ?? args.accessPoint;
92
+ if (!accessPoint) {
93
+ throw new Error(`Access point is required for chart route "${name}-${routeName}". Set it on the route or on Chart args.accessPoint`);
94
+ }
95
+ const namespace2 = await toPromise(args.namespace);
96
+ const routeRules = await toPromise(route.rules);
97
+ const routeRuleValues = Object.values(routeRules ?? {});
98
+ const defaultServiceName = route.serviceName ?? args.serviceName ?? name;
99
+ const defaultServicePort = route.servicePort ?? args.servicePort;
100
+ const needsDefaultBackend = routeRuleValues.length === 0;
101
+ const defaultService = needsDefaultBackend ? await this.getService(defaultServiceName) : undefined;
102
+ const defaultServiceEndpoints = needsDefaultBackend && defaultService ? await this.resolveServiceEndpoints(defaultService, defaultServiceName, defaultServicePort, `${name}-${routeName}`) : undefined;
103
+ const resolvedRules = routeRules ? await Promise.all(Object.entries(routeRules).map(async ([ruleName, rule]) => {
104
+ const ruleServiceName = rule.serviceName ?? defaultServiceName;
105
+ const ruleService = await this.getService(ruleServiceName);
106
+ const ruleServicePort = rule.servicePort ?? route.servicePort ?? args.servicePort;
107
+ const ruleServiceEndpoints = await this.resolveServiceEndpoints(ruleService, ruleServiceName, ruleServicePort, `${name}-${routeName}:${ruleName}`);
108
+ return [
109
+ ruleName,
110
+ [
111
+ {
112
+ ...omit(rule, ["serviceName", "servicePort"]),
113
+ backend: {
114
+ endpoints: ruleServiceEndpoints
115
+ }
116
+ }
117
+ ]
118
+ ];
119
+ })) : undefined;
120
+ const resolvedRulesInput = resolvedRules ? Object.fromEntries(resolvedRules) : undefined;
121
+ return new AccessPointRoute(`${name}-${routeName}`, {
122
+ ...baseRoute,
123
+ accessPoint,
124
+ ...defaultService ? {
125
+ backend: {
126
+ endpoints: defaultServiceEndpoints
127
+ }
128
+ } : {},
129
+ rules: resolvedRulesInput,
130
+ metadata: {
131
+ ...route.metadata ?? {},
132
+ "k8s.namespace": namespace2
133
+ }
134
+ }, { ...opts, parent: this });
135
+ }));
136
+ });
137
+ this.networkPolicies = output(args).apply((args2) => {
138
+ const policies = normalize(args2.networkPolicy, args2.networkPolicies);
139
+ return output(policies.map((policy) => {
140
+ return new NetworkPolicy(name, {
141
+ ...policy,
142
+ namespace: args2.namespace,
143
+ description: `Network policy for Helm chart "${name}"`
144
+ }, { ...opts, parent: this });
145
+ }));
146
+ });
147
+ this.workloads = output(this.chart).apply((chart) => {
148
+ return output(chart.resources.apply((resources) => {
149
+ return resources.map((resource) => {
150
+ if (apps.v1.Deployment.isInstance(resource)) {
151
+ return resource.metadata.name.apply((name2) => {
152
+ return Deployment.wrap(name2, { namespace: args.namespace, deployment: resource, terminal: args.terminal }, this.opts);
153
+ });
154
+ }
155
+ if (apps.v1.StatefulSet.isInstance(resource)) {
156
+ return resource.metadata.name.apply((name2) => {
157
+ return StatefulSet.wrap(name2, {
158
+ namespace: args.namespace,
159
+ statefulSet: resource,
160
+ service: this.getServiceOutput(name2),
161
+ terminal: args.terminal
162
+ }, this.opts);
163
+ });
164
+ }
165
+ return;
166
+ }).filter(isNonNullish);
167
+ }));
168
+ });
169
+ }
170
+ set service(_value) {}
171
+ set terminals(_value) {}
172
+ get service() {
173
+ return this.getServiceOutput(undefined);
174
+ }
175
+ get deployment() {
176
+ return this.getDeploymentOutput(this.name);
177
+ }
178
+ get statefulSet() {
179
+ return this.getStatefulSetOutput(this.name);
180
+ }
181
+ get terminals() {
182
+ return this.workloads.apply((workloads) => {
183
+ const terminalsByWorkload = workloads.map((workload) => output({ terminals: workload.terminals, workloadName: workload.metadata.name }));
184
+ return output(terminalsByWorkload).apply((workloadTerminals) => {
185
+ const hasMultipleWorkloads = workloadTerminals.length > 1;
186
+ return workloadTerminals.flatMap(({ terminals, workloadName }) => {
187
+ if (!hasMultipleWorkloads) {
188
+ return terminals;
189
+ }
190
+ return terminals.map((terminal) => ({
191
+ ...terminal,
192
+ meta: {
193
+ ...terminal.meta,
194
+ title: `${terminal.meta.title} | ${workloadName}`
195
+ }
196
+ }));
197
+ });
198
+ });
199
+ });
200
+ }
201
+ services = new Map;
202
+ async resolveServiceEndpoints(service, serviceName, servicePort, routeName) {
203
+ const endpoints = await toPromise(service.endpoints);
204
+ const servicePorts = await toPromise(service.spec.ports);
205
+ if (endpoints.length === 0) {
206
+ throw new Error(`No endpoints found for service "${serviceName}" in chart route "${routeName}"`);
207
+ }
208
+ let resolvedServicePort;
209
+ if (servicePort != null) {
210
+ const requestedServicePort = await toPromise(servicePort);
211
+ if (typeof requestedServicePort === "string") {
212
+ const namedPort = servicePorts?.find((port) => port.name === requestedServicePort);
213
+ if (!namedPort) {
214
+ throw new Error(`Named port "${requestedServicePort}" not found for service "${serviceName}" in chart route "${routeName}"`);
215
+ }
216
+ resolvedServicePort = namedPort.port;
217
+ } else {
218
+ resolvedServicePort = requestedServicePort;
219
+ }
220
+ } else {
221
+ resolvedServicePort = endpoints[0]?.port;
222
+ }
223
+ if (resolvedServicePort == null) {
224
+ throw new Error(`Unable to resolve service port for service "${serviceName}" in chart route "${routeName}"`);
225
+ }
226
+ const filteredEndpoints = endpoints.filter((endpoint) => endpoint.port === resolvedServicePort);
227
+ if (filteredEndpoints.length === 0) {
228
+ throw new Error(`No endpoints with port ${resolvedServicePort} found for service "${serviceName}" in chart route "${routeName}"`);
229
+ }
230
+ return filteredEndpoints;
231
+ }
232
+ getServiceOutput(name) {
233
+ return output({ args: this.args, chart: this.chart }).apply(({ args, chart }) => {
234
+ const resolvedName = name ?? args.serviceName ?? this.name;
235
+ const existingService = this.services.get(resolvedName);
236
+ if (existingService) {
237
+ return existingService;
238
+ }
239
+ const service = getChartServiceOutput(chart, resolvedName);
240
+ const wrappedService = Service.wrap(resolvedName, { namespace: args.namespace, service }, { ...this.opts, parent: this });
241
+ this.services.set(resolvedName, wrappedService);
242
+ return wrappedService;
243
+ });
244
+ }
245
+ getWorkloadOutput(name) {
246
+ return this.workloads.apply(async (workloads) => {
247
+ const workloadsWithNames = await toPromise(workloads.map((workload) => output({ workload, name: workload.metadata.name })));
248
+ const item = workloadsWithNames.find((w) => w.name === name);
249
+ if (!item) {
250
+ throw new Error(`Workload with name '${name}' not found in the chart workloads`);
251
+ }
252
+ return item.workload;
253
+ });
254
+ }
255
+ getDeploymentOutput(name) {
256
+ return this.getWorkloadOutput(name).apply((workload) => {
257
+ if (workload instanceof Deployment) {
258
+ return workload;
259
+ }
260
+ throw new Error(`Workload with name '${name}' is not a Deployment`);
261
+ });
262
+ }
263
+ getStatefulSetOutput(name) {
264
+ return this.getWorkloadOutput(name).apply((workload) => {
265
+ if (workload instanceof StatefulSet) {
266
+ return workload;
267
+ }
268
+ throw new Error(`Workload with name '${name}' is not a StatefulSet`);
269
+ });
270
+ }
271
+ getService(name) {
272
+ return toPromise(this.getServiceOutput(name));
273
+ }
274
+ getWorkload(name) {
275
+ return toPromise(this.getWorkloadOutput(name));
276
+ }
277
+ getDeployment(name) {
278
+ return toPromise(this.getDeploymentOutput(name));
279
+ }
280
+ getStatefulSet(name) {
281
+ return toPromise(this.getStatefulSetOutput(name));
282
+ }
283
+ }
284
+
285
+ class RenderedChart extends ComponentResource {
286
+ manifest;
287
+ command;
288
+ constructor(name, args, opts) {
289
+ super("highstate:k8s:RenderedChart", name, args, opts);
290
+ this.command = output(args).apply((args2) => {
291
+ const values = args2.values ? Object.entries(args2.values).flatMap(([key, value]) => ["--set", `${key}="${value}"`]) : [];
292
+ return new local.Command(name, {
293
+ create: output([
294
+ "helm",
295
+ "template",
296
+ resolveHelmChart(args2.chart),
297
+ ...args2.namespace ? ["--namespace", getNamespaceName(args2.namespace)] : [],
298
+ ...values
299
+ ]).apply((command) => command.join(" ")),
300
+ logging: "stderr"
301
+ }, { parent: this, ...opts });
302
+ });
303
+ this.manifest = this.command.stdout;
304
+ this.registerOutputs({ manifest: this.manifest, command: this.command });
305
+ }
306
+ }
307
+ async function resolveHelmChart(manifest) {
308
+ if (!process.env.HIGHSTATE_CACHE_DIR) {
309
+ throw new Error("Environment variable HIGHSTATE_CACHE_DIR is not set");
310
+ }
311
+ const chartsDir = resolve(process.env.HIGHSTATE_CACHE_DIR, "charts");
312
+ await mkdir(chartsDir, { recursive: true });
313
+ const globPattern = `${manifest.name}-*.tgz`;
314
+ const targetFileName = `${manifest.name}-${manifest.version}.tgz`;
315
+ const files = await glob(globPattern, { cwd: chartsDir });
316
+ if (files.includes(targetFileName)) {
317
+ return resolve(chartsDir, targetFileName);
318
+ }
319
+ for (const file of files) {
320
+ await unlink(resolve(chartsDir, file));
321
+ }
322
+ const isOci = manifest.repo.startsWith("oci://");
323
+ const chartRef = isOci ? `${manifest.repo.replace(/\/$/, "")}/${manifest.name}` : manifest.name;
324
+ const pullArgs = ["pull", chartRef, "--version", manifest.version, "--destination", chartsDir];
325
+ if (!isOci) {
326
+ pullArgs.push("--repo", manifest.repo);
327
+ }
328
+ await spawn("helm", pullArgs);
329
+ const content = await readFile(resolve(chartsDir, targetFileName));
330
+ const actualSha256 = await sha256(content);
331
+ if (actualSha256 !== manifest.sha256) {
332
+ throw new Error(`SHA256 mismatch for chart '${manifest.name}'`);
333
+ }
334
+ return resolve(chartsDir, targetFileName);
335
+ }
336
+ function getChartServiceOutput(chart, name) {
337
+ const services = chart.resources.apply((resources) => {
338
+ return resources.filter((r) => core.v1.Service.isInstance(r)).map((service) => ({ name: service.metadata.name, service }));
339
+ });
340
+ return output(services).apply((services2) => {
341
+ const service = services2.find((s) => s.name === name)?.service;
342
+ if (!service) {
343
+ throw new Error(`Service with name '${name}' not found in the chart resources`);
344
+ }
345
+ return service;
346
+ });
347
+ }
348
+ function getChartService(chart, name) {
349
+ return toPromise(getChartServiceOutput(chart, name));
350
+ }
351
+
352
+ export { Chart, RenderedChart, resolveHelmChart, getChartServiceOutput, getChartService };