@highstate/k8s 0.7.0 → 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.
@@ -1,46 +1,173 @@
1
1
  import { resolve } from 'path';
2
2
  import { mkdir, unlink, readFile } from 'node:fs/promises';
3
- import { ComponentResource, output, normalize, interpolate, all, toPromise } from '@highstate/pulumi';
3
+ import { ComponentResource, output, interpolate, normalize, toPromise } from '@highstate/pulumi';
4
4
  import { core, helm } from '@pulumi/kubernetes';
5
5
  import { ComponentResource as ComponentResource$1, output as output$1 } from '@pulumi/pulumi';
6
6
  import spawn from 'nano-spawn';
7
7
  import { sha256 } from 'crypto-hash';
8
- import { mergeDeep, omit, pipe, map } from 'remeda';
8
+ import { omit, pipe, map } from 'remeda';
9
9
  import { local } from '@pulumi/command';
10
10
  import { glob } from 'glob';
11
- import { m as mapMetadata, c as commonExtraArgs, d as mapNamespaceLikeToNamespaceName } from './shared-r5jBysdR.js';
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';
12
13
  import { gateway } from '@highstate/gateway-api';
13
14
 
14
- const serviceExtraArgs = [...commonExtraArgs, "port", "ports"];
15
+ const serviceExtraArgs = [...commonExtraArgs, "port", "ports", "patch"];
15
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
+ }
16
48
  /**
17
- * The underlying Kubernetes service.
49
+ * Returns the host of the service accessible from the cluster.
50
+ *
51
+ * The format is `name.namespace.svc`.
18
52
  */
19
- service;
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 {
20
99
  constructor(name, args, opts) {
21
- super("highstate:k8s:Service", name, args, opts);
22
- this.service = output(args).apply((args2) => {
23
- return new core.v1.Service(
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)(
24
103
  name,
25
104
  {
26
- metadata: mapMetadata(args2, name),
27
- spec: mergeDeep(
105
+ metadata: mapMetadata(args2.patch?.metadata ?? args2, name),
106
+ spec: deepmerge(
28
107
  {
29
108
  ports: normalize(args2.port, args2.ports)
30
109
  },
31
110
  omit(args2, serviceExtraArgs)
32
111
  )
33
112
  },
34
- { ...opts, parent: this }
113
+ { parent: this, ...opts }
35
114
  );
36
115
  });
37
- this.registerOutputs({
38
- service: this.service
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
+ );
39
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
+ );
40
166
  }
41
167
  }
42
168
  function mapContainerPortToServicePort(port) {
43
169
  return {
170
+ name: port.name,
44
171
  port: port.containerPort,
45
172
  targetPort: port.containerPort,
46
173
  protocol: port.protocol
@@ -51,21 +178,9 @@ function mapServiceToLabelSelector(service) {
51
178
  matchLabels: service.spec.selector
52
179
  };
53
180
  }
54
- function getServiceHost(service) {
55
- return interpolate`${service.metadata.name}.${service.metadata.namespace}.svc`;
56
- }
57
- function getRequiredServicePortByName(service, name) {
58
- return service.spec.ports.apply((ports) => {
59
- const port = ports.find((p) => p.name === name);
60
- if (!port) {
61
- throw new Error(`Service does not have a port with name ${name}`);
62
- }
63
- return port.port;
64
- });
65
- }
66
181
 
67
182
  function resolveBackendRef(ref) {
68
- if (core.v1.Service.isInstance(ref)) {
183
+ if (Service.isInstance(ref)) {
69
184
  return output({
70
185
  name: ref.metadata.name,
71
186
  namespace: ref.metadata.namespace,
@@ -77,9 +192,7 @@ function resolveBackendRef(ref) {
77
192
  return output({
78
193
  name: service.metadata.name,
79
194
  namespace: service.metadata.namespace,
80
- port: all([ref.service, ref.port]).apply(([service2, port]) => {
81
- return typeof port === "number" ? output(port) : getRequiredServicePortByName(service2, port);
82
- })
195
+ port: ref.port
83
196
  });
84
197
  }
85
198
  return output({
@@ -149,16 +262,18 @@ class Chart extends ComponentResource$1 {
149
262
  constructor(name, args, opts) {
150
263
  super("highstate:k8s:Chart", name, args, opts);
151
264
  this.name = name;
265
+ this.args = args;
266
+ this.opts = opts;
152
267
  this.chart = output$1(args).apply((args2) => {
153
268
  return new helm.v4.Chart(
154
269
  name,
155
270
  omit(
156
271
  {
157
272
  ...args2,
158
- chart: resolveHelmChart(args2.charts, args2.chart),
273
+ chart: resolveHelmChart(args2.chart),
159
274
  namespace: args2.namespace ? mapNamespaceLikeToNamespaceName(args2.namespace) : void 0
160
275
  },
161
- ["charts", "httpRoute"]
276
+ ["httpRoute"]
162
277
  ),
163
278
  { parent: this, ...opts }
164
279
  );
@@ -172,7 +287,7 @@ class Chart extends ComponentResource$1 {
172
287
  {
173
288
  ...httpRoute,
174
289
  rule: {
175
- backend: this.getServiceOutput(args.serviceName)
290
+ backend: this.service
176
291
  }
177
292
  },
178
293
  { ...opts, parent: this }
@@ -188,8 +303,28 @@ class Chart extends ComponentResource$1 {
188
303
  * The HTTP route associated with the deployment.
189
304
  */
190
305
  httpRoute;
306
+ get service() {
307
+ return this.getServiceOutput(void 0);
308
+ }
309
+ services = /* @__PURE__ */ new Map();
191
310
  getServiceOutput(name) {
192
- return this.chart.apply((chart) => getChartServiceOutput(chart, name ?? this.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
+ });
193
328
  }
194
329
  getService(name) {
195
330
  return toPromise(this.getServiceOutput(name));
@@ -214,7 +349,7 @@ class RenderedChart extends ComponentResource$1 {
214
349
  create: output$1([
215
350
  "helm",
216
351
  "template",
217
- resolveHelmChart(args2.charts, args2.chart),
352
+ resolveHelmChart(args2.chart),
218
353
  ...args2.namespace ? ["--namespace", mapNamespaceLikeToNamespaceName(args2.namespace)] : [],
219
354
  ...values
220
355
  ]).apply((command) => command.join(" ")),
@@ -227,18 +362,14 @@ class RenderedChart extends ComponentResource$1 {
227
362
  this.registerOutputs({ manifest: this.manifest, command: this.command });
228
363
  }
229
364
  }
230
- async function resolveHelmChart(charts, name) {
231
- const entry = charts[name];
232
- if (!entry) {
233
- throw new Error(`Chart '${name}' not found in the charts.json`);
234
- }
365
+ async function resolveHelmChart(manifest) {
235
366
  if (!process.env.HIGHSTATE_CACHE_DIR) {
236
367
  throw new Error("Environment variable HIGHSTATE_CACHE_DIR is not set");
237
368
  }
238
369
  const chartsDir = resolve(process.env.HIGHSTATE_CACHE_DIR, "charts");
239
370
  await mkdir(chartsDir, { recursive: true });
240
- const globPattern = `${name}-*.tgz`;
241
- const targetFileName = `${name}-${entry.version}.tgz`;
371
+ const globPattern = `${manifest.name}-*.tgz`;
372
+ const targetFileName = `${manifest.name}-${manifest.version}.tgz`;
242
373
  const files = await glob(globPattern, { cwd: chartsDir });
243
374
  if (files.includes(targetFileName)) {
244
375
  return resolve(chartsDir, targetFileName);
@@ -248,18 +379,18 @@ async function resolveHelmChart(charts, name) {
248
379
  }
249
380
  await spawn("helm", [
250
381
  "pull",
251
- entry.name,
382
+ manifest.name,
252
383
  "--version",
253
- entry.version,
384
+ manifest.version,
254
385
  "--repo",
255
- entry.repo,
386
+ manifest.repo,
256
387
  "--destination",
257
388
  chartsDir
258
389
  ]);
259
390
  const content = await readFile(resolve(chartsDir, targetFileName));
260
391
  const actualSha256 = await sha256(content);
261
- if (actualSha256 !== entry.sha256) {
262
- throw new Error(`SHA256 mismatch for chart '${name}'`);
392
+ if (actualSha256 !== manifest.sha256) {
393
+ throw new Error(`SHA256 mismatch for chart '${manifest.name}'`);
263
394
  }
264
395
  return resolve(chartsDir, targetFileName);
265
396
  }
@@ -279,4 +410,4 @@ function getChartService(chart, name) {
279
410
  return toPromise(getChartServiceOutput(chart, name));
280
411
  }
281
412
 
282
- export { Chart as C, HttpRoute as H, RenderedChart as R, Service as S, mapServiceToLabelSelector as a, getChartServiceOutput as b, getChartService as c, getServiceHost as g, mapContainerPortToServicePort as m, resolveHelmChart as r };
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 };