@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.
- package/dist/chunk-23vn2rdc.js +11 -0
- package/dist/chunk-2pfx13ay.js +11 -0
- package/dist/chunk-46ntav0c.js +299 -0
- package/dist/chunk-556pc9e6.js +155 -0
- package/dist/chunk-7kgjgcft.js +170 -0
- package/dist/{chunk-ADHZK6V2.js → chunk-9hs97f1q.js} +13 -11
- package/dist/chunk-aame3x1b.js +11 -0
- package/dist/chunk-b05q6fm2.js +37 -0
- package/dist/chunk-bmvc9d2d.js +11 -0
- package/dist/chunk-de82bbp2.js +7 -0
- package/dist/chunk-facs31cb.js +624 -0
- package/dist/chunk-h1b79v66.js +1425 -0
- package/dist/chunk-k4w9zpn5.js +215 -0
- package/dist/chunk-pqc6w52f.js +352 -0
- package/dist/chunk-qyshvz32.js +176 -0
- package/dist/chunk-tpfyj6fe.js +199 -0
- package/dist/chunk-z6bmpnm7.js +180 -0
- package/dist/highstate.manifest.json +3 -3
- package/dist/impl/dynamic-endpoint-resolver.js +82 -81
- package/dist/impl/gateway-route.js +131 -168
- package/dist/impl/tls-certificate.js +31 -32
- package/dist/index.js +245 -201
- package/dist/units/cert-manager/index.js +19 -13
- package/dist/units/cluster-patch/index.js +9 -8
- package/dist/units/dns01-issuer/index.js +44 -41
- package/dist/units/existing-cluster/index.js +25 -13
- package/dist/units/gateway-api/index.js +15 -16
- package/dist/units/reduced-access-cluster/index.js +28 -32
- package/package.json +21 -21
- package/src/cron-job.ts +26 -1
- package/src/deployment.ts +17 -1
- package/src/job.ts +15 -1
- package/src/scripting/bundle.ts +21 -98
- package/src/scripting/environment.ts +2 -9
- package/src/shared.ts +1 -1
- package/src/stateful-set.ts +17 -1
- package/src/workload.ts +31 -14
- package/LICENSE +0 -21
- package/dist/chunk-23X5SXQG.js +0 -301
- package/dist/chunk-23X5SXQG.js.map +0 -1
- package/dist/chunk-ADHZK6V2.js.map +0 -1
- package/dist/chunk-BTAEFJ5N.js +0 -291
- package/dist/chunk-BTAEFJ5N.js.map +0 -1
- package/dist/chunk-HH2JJELM.js +0 -13
- package/dist/chunk-HH2JJELM.js.map +0 -1
- package/dist/chunk-IXE3OKB4.js +0 -249
- package/dist/chunk-IXE3OKB4.js.map +0 -1
- package/dist/chunk-OG2OPX7B.js +0 -333
- package/dist/chunk-OG2OPX7B.js.map +0 -1
- package/dist/chunk-P26SQ2ZB.js +0 -393
- package/dist/chunk-P26SQ2ZB.js.map +0 -1
- package/dist/chunk-PG27ZY2H.js +0 -319
- package/dist/chunk-PG27ZY2H.js.map +0 -1
- package/dist/chunk-PZYGZSN5.js +0 -54
- package/dist/chunk-PZYGZSN5.js.map +0 -1
- package/dist/chunk-S77TE7UC.js +0 -309
- package/dist/chunk-S77TE7UC.js.map +0 -1
- package/dist/chunk-SZKOAHNX.js +0 -1804
- package/dist/chunk-SZKOAHNX.js.map +0 -1
- package/dist/chunk-TOLFVF4S.js +0 -889
- package/dist/chunk-TOLFVF4S.js.map +0 -1
- package/dist/chunk-TVKT3ZYX.js +0 -423
- package/dist/chunk-TVKT3ZYX.js.map +0 -1
- package/dist/cron-job-RKB2HYTO.js +0 -7
- package/dist/cron-job-RKB2HYTO.js.map +0 -1
- package/dist/deployment-T35TUOL2.js +0 -7
- package/dist/deployment-T35TUOL2.js.map +0 -1
- package/dist/impl/dynamic-endpoint-resolver.js.map +0 -1
- package/dist/impl/gateway-route.js.map +0 -1
- package/dist/impl/tls-certificate.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/job-PE4AKOHB.js +0 -7
- package/dist/job-PE4AKOHB.js.map +0 -1
- package/dist/stateful-set-LUIRHQJY.js +0 -7
- package/dist/stateful-set-LUIRHQJY.js.map +0 -1
- package/dist/units/cert-manager/index.js.map +0 -1
- package/dist/units/cluster-patch/index.js.map +0 -1
- package/dist/units/dns01-issuer/index.js.map +0 -1
- package/dist/units/existing-cluster/index.js.map +0 -1
- package/dist/units/gateway-api/index.js.map +0 -1
- 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 };
|