@highstate/k8s 0.6.2 → 0.7.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/assets/charts.json +8 -0
- package/dist/helm-wPTgVV1N.js +413 -0
- package/dist/index.d.ts +412 -52
- package/dist/index.js +518 -335
- package/dist/shared-Clzbl5K-.js +89 -0
- package/dist/units/cert-manager/index.js +25 -7
- package/dist/units/dns01-issuer/index.js +3 -5
- package/dist/units/existing-cluster/index.js +21 -5
- package/package.json +12 -8
- package/assets/cert-manager-v1.16.3.tgz +0 -0
- package/assets/gateway-api.yaml +0 -14974
- package/dist/shared-hajqPzR4.js +0 -64
@@ -0,0 +1,413 @@
|
|
1
|
+
import { resolve } from 'path';
|
2
|
+
import { mkdir, unlink, readFile } from 'node:fs/promises';
|
3
|
+
import { ComponentResource, output, interpolate, normalize, toPromise } from '@highstate/pulumi';
|
4
|
+
import { core, helm } from '@pulumi/kubernetes';
|
5
|
+
import { ComponentResource as ComponentResource$1, output as output$1 } from '@pulumi/pulumi';
|
6
|
+
import spawn from 'nano-spawn';
|
7
|
+
import { sha256 } from 'crypto-hash';
|
8
|
+
import { omit, pipe, map } from 'remeda';
|
9
|
+
import { local } from '@pulumi/command';
|
10
|
+
import { glob } from 'glob';
|
11
|
+
import { deepmerge } from 'deepmerge-ts';
|
12
|
+
import { v as verifyProvider, m as mapMetadata, c as commonExtraArgs, r as resourceIdToString, d as mapNamespaceLikeToNamespaceName } from './shared-Clzbl5K-.js';
|
13
|
+
import { gateway } from '@highstate/gateway-api';
|
14
|
+
|
15
|
+
const serviceExtraArgs = [...commonExtraArgs, "port", "ports", "patch"];
|
16
|
+
class Service extends ComponentResource {
|
17
|
+
constructor(type, name, args, opts, clusterInfo, metadata, spec, status, resources) {
|
18
|
+
super(type, name, args, opts);
|
19
|
+
this.clusterInfo = clusterInfo;
|
20
|
+
this.metadata = metadata;
|
21
|
+
this.spec = spec;
|
22
|
+
this.status = status;
|
23
|
+
this.resources = resources;
|
24
|
+
}
|
25
|
+
/**
|
26
|
+
* The Highstate service entity.
|
27
|
+
*/
|
28
|
+
get entity() {
|
29
|
+
return output({
|
30
|
+
type: "k8s.service",
|
31
|
+
clusterInfo: this.clusterInfo,
|
32
|
+
metadata: this.metadata,
|
33
|
+
spec: this.spec
|
34
|
+
});
|
35
|
+
}
|
36
|
+
static create(name, args, opts) {
|
37
|
+
return new CreatedService(name, args, opts);
|
38
|
+
}
|
39
|
+
static wrap(name, service, clusterInfo, opts) {
|
40
|
+
return new WrappedService(name, service, clusterInfo, opts);
|
41
|
+
}
|
42
|
+
static external(name, id, clusterInfo, opts) {
|
43
|
+
return new ExternalService(name, id, clusterInfo, opts);
|
44
|
+
}
|
45
|
+
static of(name, entity, opts) {
|
46
|
+
return new ExternalService(name, output(entity).metadata, output(entity).clusterInfo, opts);
|
47
|
+
}
|
48
|
+
/**
|
49
|
+
* Returns the host of the service accessible from the cluster.
|
50
|
+
*
|
51
|
+
* The format is `name.namespace.svc`.
|
52
|
+
*/
|
53
|
+
get clusterHost() {
|
54
|
+
return interpolate`${this.metadata.name}.${this.metadata.namespace}.svc`;
|
55
|
+
}
|
56
|
+
/**
|
57
|
+
* Returns the external IP of the service.
|
58
|
+
*
|
59
|
+
* If the load balancer is available, the external IP is returned, otherwise the IP of the node.
|
60
|
+
*
|
61
|
+
* If the service has no external IP, `undefined` is returned.
|
62
|
+
*/
|
63
|
+
get externalIp() {
|
64
|
+
return output({ spec: this.spec, status: this.status }).apply(({ spec, status }) => {
|
65
|
+
const loadBalancerIp = status.loadBalancer?.ingress?.[0]?.ip;
|
66
|
+
if (loadBalancerIp) {
|
67
|
+
return loadBalancerIp;
|
68
|
+
}
|
69
|
+
const nodeIp = spec.externalIPs?.[0];
|
70
|
+
if (nodeIp) {
|
71
|
+
return nodeIp;
|
72
|
+
}
|
73
|
+
return void 0;
|
74
|
+
});
|
75
|
+
}
|
76
|
+
/**
|
77
|
+
* The "most public" IP of the service.
|
78
|
+
*
|
79
|
+
* Resolves to the external IP if available, otherwise the cluster IP.
|
80
|
+
*
|
81
|
+
* If the service is headless, an error is thrown.
|
82
|
+
*/
|
83
|
+
get ip() {
|
84
|
+
return output({ spec: this.spec, externalIp: this.externalIp }).apply(
|
85
|
+
({ spec, externalIp }) => {
|
86
|
+
if (externalIp) {
|
87
|
+
return externalIp;
|
88
|
+
}
|
89
|
+
const clusterIp = spec.clusterIP;
|
90
|
+
if (clusterIp && clusterIp !== "None") {
|
91
|
+
return clusterIp;
|
92
|
+
}
|
93
|
+
throw new Error("The service does not have neither an external IP nor a cluster IP.");
|
94
|
+
}
|
95
|
+
);
|
96
|
+
}
|
97
|
+
}
|
98
|
+
class CreatedService extends Service {
|
99
|
+
constructor(name, args, opts) {
|
100
|
+
const service = output(args).apply(async (args2) => {
|
101
|
+
await verifyProvider(opts.provider, args2.cluster.info);
|
102
|
+
return new (args2.patch ? core.v1.ServicePatch : core.v1.Service)(
|
103
|
+
name,
|
104
|
+
{
|
105
|
+
metadata: mapMetadata(args2.patch?.metadata ?? args2, name),
|
106
|
+
spec: deepmerge(
|
107
|
+
{
|
108
|
+
ports: normalize(args2.port, args2.ports)
|
109
|
+
},
|
110
|
+
omit(args2, serviceExtraArgs)
|
111
|
+
)
|
112
|
+
},
|
113
|
+
{ parent: this, ...opts }
|
114
|
+
);
|
115
|
+
});
|
116
|
+
super(
|
117
|
+
"highstate:k8s:Service",
|
118
|
+
name,
|
119
|
+
args,
|
120
|
+
opts,
|
121
|
+
output(args.cluster).info,
|
122
|
+
service.metadata,
|
123
|
+
service.spec,
|
124
|
+
service.status,
|
125
|
+
[service]
|
126
|
+
);
|
127
|
+
}
|
128
|
+
}
|
129
|
+
class WrappedService extends Service {
|
130
|
+
constructor(name, service, clusterInfo, opts) {
|
131
|
+
super(
|
132
|
+
"highstate:k8s:WrappedService",
|
133
|
+
name,
|
134
|
+
{ service, clusterInfo },
|
135
|
+
opts,
|
136
|
+
output(clusterInfo),
|
137
|
+
output(service).metadata,
|
138
|
+
output(service).spec,
|
139
|
+
output(service).status,
|
140
|
+
[service]
|
141
|
+
);
|
142
|
+
}
|
143
|
+
}
|
144
|
+
class ExternalService extends Service {
|
145
|
+
constructor(name, id, clusterInfo, opts) {
|
146
|
+
const service = output(id).apply(async (id2) => {
|
147
|
+
await verifyProvider(opts.provider, this.clusterInfo);
|
148
|
+
return core.v1.Service.get(
|
149
|
+
//
|
150
|
+
name,
|
151
|
+
resourceIdToString(id2),
|
152
|
+
{ parent: this, provider: opts.provider }
|
153
|
+
);
|
154
|
+
});
|
155
|
+
super(
|
156
|
+
"highstate:k8s:ExternalService",
|
157
|
+
name,
|
158
|
+
{ id, clusterInfo },
|
159
|
+
opts,
|
160
|
+
output(clusterInfo),
|
161
|
+
service.metadata,
|
162
|
+
service.spec,
|
163
|
+
service.status,
|
164
|
+
[service]
|
165
|
+
);
|
166
|
+
}
|
167
|
+
}
|
168
|
+
function mapContainerPortToServicePort(port) {
|
169
|
+
return {
|
170
|
+
name: port.name,
|
171
|
+
port: port.containerPort,
|
172
|
+
targetPort: port.containerPort,
|
173
|
+
protocol: port.protocol
|
174
|
+
};
|
175
|
+
}
|
176
|
+
function mapServiceToLabelSelector(service) {
|
177
|
+
return {
|
178
|
+
matchLabels: service.spec.selector
|
179
|
+
};
|
180
|
+
}
|
181
|
+
|
182
|
+
function resolveBackendRef(ref) {
|
183
|
+
if (Service.isInstance(ref)) {
|
184
|
+
return output({
|
185
|
+
name: ref.metadata.name,
|
186
|
+
namespace: ref.metadata.namespace,
|
187
|
+
port: ref.spec.ports[0].port
|
188
|
+
});
|
189
|
+
}
|
190
|
+
if ("service" in ref) {
|
191
|
+
const service = output(ref.service);
|
192
|
+
return output({
|
193
|
+
name: service.metadata.name,
|
194
|
+
namespace: service.metadata.namespace,
|
195
|
+
port: ref.port
|
196
|
+
});
|
197
|
+
}
|
198
|
+
return output({
|
199
|
+
name: ref.name,
|
200
|
+
namespace: ref.namespace,
|
201
|
+
port: ref.port
|
202
|
+
});
|
203
|
+
}
|
204
|
+
|
205
|
+
class HttpRoute extends ComponentResource {
|
206
|
+
/**
|
207
|
+
* The underlying Kubernetes resource.
|
208
|
+
*/
|
209
|
+
route;
|
210
|
+
constructor(name, args, opts) {
|
211
|
+
super("highstate:k8s:HttpRoute", name, args, opts);
|
212
|
+
this.route = output({
|
213
|
+
args,
|
214
|
+
gatewayNamespace: output(args.gateway).metadata.namespace
|
215
|
+
}).apply(({ args: args2, gatewayNamespace }) => {
|
216
|
+
return new gateway.v1.HTTPRoute(
|
217
|
+
name,
|
218
|
+
{
|
219
|
+
metadata: mapMetadata(
|
220
|
+
{
|
221
|
+
...args2,
|
222
|
+
namespace: gatewayNamespace
|
223
|
+
},
|
224
|
+
name
|
225
|
+
),
|
226
|
+
spec: {
|
227
|
+
hostnames: normalize(args2.hostname, args2.hostnames),
|
228
|
+
parentRefs: [
|
229
|
+
{
|
230
|
+
name: args2.gateway.metadata.name
|
231
|
+
}
|
232
|
+
],
|
233
|
+
rules: normalize(args2.rule, args2.rules).map((rule) => ({
|
234
|
+
timeouts: rule.timeouts,
|
235
|
+
matches: pipe(
|
236
|
+
normalize(rule.match, rule.matches),
|
237
|
+
map(mapHttpRouteRuleMatch),
|
238
|
+
addDefaultPathMatch
|
239
|
+
),
|
240
|
+
filters: normalize(rule.filter, rule.filters),
|
241
|
+
backendRefs: rule.backend ? [resolveBackendRef(rule.backend)] : void 0
|
242
|
+
}))
|
243
|
+
}
|
244
|
+
},
|
245
|
+
{ parent: this, ...opts }
|
246
|
+
);
|
247
|
+
});
|
248
|
+
this.registerOutputs({ route: this.route });
|
249
|
+
}
|
250
|
+
}
|
251
|
+
function addDefaultPathMatch(matches) {
|
252
|
+
return matches.length ? matches : [{ path: { type: "PathPrefix", value: "/" } }];
|
253
|
+
}
|
254
|
+
function mapHttpRouteRuleMatch(match) {
|
255
|
+
if (typeof match === "string") {
|
256
|
+
return { path: { type: "PathPrefix", value: match } };
|
257
|
+
}
|
258
|
+
return match;
|
259
|
+
}
|
260
|
+
|
261
|
+
class Chart extends ComponentResource$1 {
|
262
|
+
constructor(name, args, opts) {
|
263
|
+
super("highstate:k8s:Chart", name, args, opts);
|
264
|
+
this.name = name;
|
265
|
+
this.args = args;
|
266
|
+
this.opts = opts;
|
267
|
+
this.chart = output$1(args).apply((args2) => {
|
268
|
+
return new helm.v4.Chart(
|
269
|
+
name,
|
270
|
+
omit(
|
271
|
+
{
|
272
|
+
...args2,
|
273
|
+
chart: resolveHelmChart(args2.chart),
|
274
|
+
namespace: args2.namespace ? mapNamespaceLikeToNamespaceName(args2.namespace) : void 0
|
275
|
+
},
|
276
|
+
["httpRoute"]
|
277
|
+
),
|
278
|
+
{ parent: this, ...opts }
|
279
|
+
);
|
280
|
+
});
|
281
|
+
this.httpRoute = output$1(args.httpRoute).apply((httpRoute) => {
|
282
|
+
if (!httpRoute) {
|
283
|
+
return void 0;
|
284
|
+
}
|
285
|
+
return new HttpRoute(
|
286
|
+
name,
|
287
|
+
{
|
288
|
+
...httpRoute,
|
289
|
+
rule: {
|
290
|
+
backend: this.service
|
291
|
+
}
|
292
|
+
},
|
293
|
+
{ ...opts, parent: this }
|
294
|
+
);
|
295
|
+
});
|
296
|
+
this.registerOutputs({ chart: this.chart });
|
297
|
+
}
|
298
|
+
/**
|
299
|
+
* The underlying Helm chart.
|
300
|
+
*/
|
301
|
+
chart;
|
302
|
+
/**
|
303
|
+
* The HTTP route associated with the deployment.
|
304
|
+
*/
|
305
|
+
httpRoute;
|
306
|
+
get service() {
|
307
|
+
return this.getServiceOutput(void 0);
|
308
|
+
}
|
309
|
+
services = /* @__PURE__ */ new Map();
|
310
|
+
getServiceOutput(name) {
|
311
|
+
return output$1({ args: this.args, chart: this.chart }).apply(({ args, chart }) => {
|
312
|
+
const resolvedName = name ?? args.serviceName ?? this.name;
|
313
|
+
const existingService = this.services.get(resolvedName);
|
314
|
+
if (existingService) {
|
315
|
+
return existingService;
|
316
|
+
}
|
317
|
+
const service = getChartServiceOutput(chart, resolvedName);
|
318
|
+
const wrappedService = Service.wrap(
|
319
|
+
//
|
320
|
+
resolvedName,
|
321
|
+
service,
|
322
|
+
args.cluster.info,
|
323
|
+
{ parent: this, ...this.opts }
|
324
|
+
);
|
325
|
+
this.services.set(resolvedName, wrappedService);
|
326
|
+
return wrappedService;
|
327
|
+
});
|
328
|
+
}
|
329
|
+
getService(name) {
|
330
|
+
return toPromise(this.getServiceOutput(name));
|
331
|
+
}
|
332
|
+
}
|
333
|
+
class RenderedChart extends ComponentResource$1 {
|
334
|
+
/**
|
335
|
+
* The rendered manifest of the Helm chart.
|
336
|
+
*/
|
337
|
+
manifest;
|
338
|
+
/**
|
339
|
+
* The underlying command used to render the chart.
|
340
|
+
*/
|
341
|
+
command;
|
342
|
+
constructor(name, args, opts) {
|
343
|
+
super("highstate:k8s:RenderedChart", name, args, opts);
|
344
|
+
this.command = output$1(args).apply((args2) => {
|
345
|
+
const values = args2.values ? Object.entries(args2.values).flatMap(([key, value]) => ["--set", `${key}="${value}"`]) : [];
|
346
|
+
return new local.Command(
|
347
|
+
name,
|
348
|
+
{
|
349
|
+
create: output$1([
|
350
|
+
"helm",
|
351
|
+
"template",
|
352
|
+
resolveHelmChart(args2.chart),
|
353
|
+
...args2.namespace ? ["--namespace", mapNamespaceLikeToNamespaceName(args2.namespace)] : [],
|
354
|
+
...values
|
355
|
+
]).apply((command) => command.join(" ")),
|
356
|
+
logging: "stderr"
|
357
|
+
},
|
358
|
+
{ parent: this, ...opts }
|
359
|
+
);
|
360
|
+
});
|
361
|
+
this.manifest = this.command.stdout;
|
362
|
+
this.registerOutputs({ manifest: this.manifest, command: this.command });
|
363
|
+
}
|
364
|
+
}
|
365
|
+
async function resolveHelmChart(manifest) {
|
366
|
+
if (!process.env.HIGHSTATE_CACHE_DIR) {
|
367
|
+
throw new Error("Environment variable HIGHSTATE_CACHE_DIR is not set");
|
368
|
+
}
|
369
|
+
const chartsDir = resolve(process.env.HIGHSTATE_CACHE_DIR, "charts");
|
370
|
+
await mkdir(chartsDir, { recursive: true });
|
371
|
+
const globPattern = `${manifest.name}-*.tgz`;
|
372
|
+
const targetFileName = `${manifest.name}-${manifest.version}.tgz`;
|
373
|
+
const files = await glob(globPattern, { cwd: chartsDir });
|
374
|
+
if (files.includes(targetFileName)) {
|
375
|
+
return resolve(chartsDir, targetFileName);
|
376
|
+
}
|
377
|
+
for (const file of files) {
|
378
|
+
await unlink(resolve(chartsDir, file));
|
379
|
+
}
|
380
|
+
await spawn("helm", [
|
381
|
+
"pull",
|
382
|
+
manifest.name,
|
383
|
+
"--version",
|
384
|
+
manifest.version,
|
385
|
+
"--repo",
|
386
|
+
manifest.repo,
|
387
|
+
"--destination",
|
388
|
+
chartsDir
|
389
|
+
]);
|
390
|
+
const content = await readFile(resolve(chartsDir, targetFileName));
|
391
|
+
const actualSha256 = await sha256(content);
|
392
|
+
if (actualSha256 !== manifest.sha256) {
|
393
|
+
throw new Error(`SHA256 mismatch for chart '${manifest.name}'`);
|
394
|
+
}
|
395
|
+
return resolve(chartsDir, targetFileName);
|
396
|
+
}
|
397
|
+
function getChartServiceOutput(chart, name) {
|
398
|
+
const services = chart.resources.apply((resources) => {
|
399
|
+
return resources.filter((r) => core.v1.Service.isInstance(r)).map((service) => ({ name: service.metadata.name, service }));
|
400
|
+
});
|
401
|
+
return output$1(services).apply((services2) => {
|
402
|
+
const service = services2.find((s) => s.name === name)?.service;
|
403
|
+
if (!service) {
|
404
|
+
throw new Error(`Service with name '${name}' not found in the chart resources`);
|
405
|
+
}
|
406
|
+
return service;
|
407
|
+
});
|
408
|
+
}
|
409
|
+
function getChartService(chart, name) {
|
410
|
+
return toPromise(getChartServiceOutput(chart, name));
|
411
|
+
}
|
412
|
+
|
413
|
+
export { Chart as C, HttpRoute as H, RenderedChart as R, Service as S, mapServiceToLabelSelector as a, getChartService as b, getChartServiceOutput as g, mapContainerPortToServicePort as m, resolveHelmChart as r };
|