@highstate/common 0.9.16 → 0.9.19

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 (48) hide show
  1. package/dist/{chunk-HZBJ6LLS.js → chunk-WDYIUWYZ.js} +659 -267
  2. package/dist/chunk-WDYIUWYZ.js.map +1 -0
  3. package/dist/highstate.manifest.json +12 -8
  4. package/dist/index.js +1 -1
  5. package/dist/units/access-point/index.js +16 -0
  6. package/dist/units/access-point/index.js.map +1 -0
  7. package/dist/units/databases/existing-mariadb/index.js +17 -0
  8. package/dist/units/databases/existing-mariadb/index.js.map +1 -0
  9. package/dist/units/databases/existing-mongodb/index.js +17 -0
  10. package/dist/units/databases/existing-mongodb/index.js.map +1 -0
  11. package/dist/units/databases/existing-postgresql/index.js +17 -0
  12. package/dist/units/databases/existing-postgresql/index.js.map +1 -0
  13. package/dist/units/dns/record-set/index.js +22 -11
  14. package/dist/units/dns/record-set/index.js.map +1 -1
  15. package/dist/units/existing-server/index.js +12 -12
  16. package/dist/units/existing-server/index.js.map +1 -1
  17. package/dist/units/network/l3-endpoint/index.js +1 -1
  18. package/dist/units/network/l3-endpoint/index.js.map +1 -1
  19. package/dist/units/network/l4-endpoint/index.js +1 -1
  20. package/dist/units/network/l4-endpoint/index.js.map +1 -1
  21. package/dist/units/script/index.js +1 -1
  22. package/dist/units/script/index.js.map +1 -1
  23. package/dist/units/server-dns/index.js +1 -1
  24. package/dist/units/server-dns/index.js.map +1 -1
  25. package/dist/units/server-patch/index.js +1 -1
  26. package/dist/units/server-patch/index.js.map +1 -1
  27. package/dist/units/ssh/key-pair/index.js +6 -6
  28. package/dist/units/ssh/key-pair/index.js.map +1 -1
  29. package/package.json +61 -8
  30. package/src/shared/access-point.ts +110 -0
  31. package/src/shared/command.ts +310 -69
  32. package/src/shared/dns.ts +150 -90
  33. package/src/shared/files.ts +34 -34
  34. package/src/shared/gateway.ts +117 -0
  35. package/src/shared/impl-ref.ts +123 -0
  36. package/src/shared/index.ts +4 -0
  37. package/src/shared/network.ts +41 -27
  38. package/src/shared/passwords.ts +38 -2
  39. package/src/shared/ssh.ts +261 -126
  40. package/src/shared/tls.ts +123 -0
  41. package/src/units/access-point/index.ts +12 -0
  42. package/src/units/databases/existing-mariadb/index.ts +14 -0
  43. package/src/units/databases/existing-mongodb/index.ts +14 -0
  44. package/src/units/databases/existing-postgresql/index.ts +14 -0
  45. package/src/units/dns/record-set/index.ts +21 -11
  46. package/src/units/existing-server/index.ts +12 -17
  47. package/src/units/ssh/key-pair/index.ts +6 -6
  48. package/dist/chunk-HZBJ6LLS.js.map +0 -1
@@ -1,22 +1,23 @@
1
- import { toPromise, output, ComponentResource, interpolate, normalize, secret, getOrCreateSecret, asset } from '@highstate/pulumi';
2
- import { uniqueBy, capitalize, groupBy } from 'remeda';
1
+ import { ComponentResource, Resource, toPromise, output, interpolate, normalizeInputsAndMap, fileFromString, secret, asset, normalizeInputs } from '@highstate/pulumi';
2
+ import { uniqueBy, flat, groupBy } from 'remeda';
3
+ import { homedir, tmpdir } from 'node:os';
3
4
  import { local, remote } from '@pulumi/command';
4
- import '@highstate/library';
5
- import { randomBytes } from '@noble/hashes/utils';
5
+ import { sha256 } from '@noble/hashes/sha2';
6
+ import { z, getOrCreate, stripNullish, HighstateSignature } from '@highstate/contract';
7
+ import { randomBytes, bytesToHex } from '@noble/hashes/utils';
6
8
  import { secureMask } from 'micro-key-producer/password.js';
7
9
  import getKeys, { PrivateExport } from 'micro-key-producer/ssh.js';
8
10
  import { randomBytes as randomBytes$1 } from 'micro-key-producer/utils.js';
9
- import { tmpdir } from 'node:os';
11
+ import { createHash } from 'node:crypto';
12
+ import { createReadStream } from 'node:fs';
10
13
  import { mkdtemp, writeFile, cp, rm, stat, rename, mkdir } from 'node:fs/promises';
11
14
  import { join, dirname, basename, extname } from 'node:path';
12
- import { createReadStream } from 'node:fs';
13
- import { pipeline } from 'node:stream/promises';
14
15
  import { Readable } from 'node:stream';
15
- import { createHash } from 'node:crypto';
16
+ import { pipeline } from 'node:stream/promises';
16
17
  import { minimatch } from 'minimatch';
17
- import { HighstateSignature } from '@highstate/contract';
18
18
  import * as tar from 'tar';
19
19
  import unzipper from 'unzipper';
20
+ import { network } from '@highstate/library';
20
21
 
21
22
  // src/shared/network.ts
22
23
  function l3EndpointToString(l3Endpoint) {
@@ -123,7 +124,7 @@ async function requireInputL4Endpoint(rawEndpoint, inputEndpoint) {
123
124
  }
124
125
  throw new Error("No endpoint provided");
125
126
  }
126
- function l3ToL4Endpoint(l3Endpoint, port, protocol = "tcp") {
127
+ function l3EndpointToL4(l3Endpoint, port, protocol = "tcp") {
127
128
  return {
128
129
  ...parseL3Endpoint(l3Endpoint),
129
130
  port,
@@ -138,7 +139,7 @@ function filterEndpoints(endpoints, filter, types) {
138
139
  } else if (endpoints.some((endpoint) => endpoint.visibility === "external")) {
139
140
  endpoints = endpoints.filter((endpoint) => endpoint.visibility === "external");
140
141
  }
141
- if (types && types.length) {
142
+ if (types?.length) {
142
143
  endpoints = endpoints.filter((endpoint) => types.includes(endpoint.type));
143
144
  }
144
145
  return endpoints;
@@ -184,30 +185,31 @@ function parseL7Endpoint(l7Endpoint) {
184
185
  }
185
186
  async function updateEndpoints(currentEndpoints, endpoints, inputEndpoints, mode = "prepend") {
186
187
  const resolvedCurrentEndpoints = await toPromise(currentEndpoints);
187
- const resolvedInputEndpoints = await toPromise(inputEndpoints);
188
- const newEndpoints = uniqueBy(
189
- //
190
- [...endpoints.map(parseL34Endpoint), ...resolvedInputEndpoints],
191
- (endpoint) => l34EndpointToString(endpoint)
192
- );
188
+ const newEndpoints = await parseEndpoints(endpoints, inputEndpoints);
193
189
  if (mode === "replace") {
194
190
  return newEndpoints;
195
191
  }
196
192
  return uniqueBy(
197
- //
198
193
  [...newEndpoints, ...resolvedCurrentEndpoints],
199
- (endpoint) => l34EndpointToString(endpoint)
194
+ l34EndpointToString
200
195
  );
201
196
  }
202
- function getServerConnection(ssh2) {
203
- return output(ssh2).apply((ssh3) => ({
204
- host: l3EndpointToString(ssh3.endpoints[0]),
205
- port: ssh3.endpoints[0].port,
206
- user: ssh3.user,
207
- password: ssh3.password,
208
- privateKey: ssh3.keyPair?.privateKey,
197
+ async function parseEndpoints(endpoints, inputEndpoints) {
198
+ const resolvedInputEndpoints = await toPromise(inputEndpoints);
199
+ return uniqueBy(
200
+ [...endpoints?.map(parseL34Endpoint) ?? [], ...resolvedInputEndpoints ?? []],
201
+ l34EndpointToString
202
+ );
203
+ }
204
+ function getServerConnection(ssh) {
205
+ return output(ssh).apply((ssh2) => ({
206
+ host: l3EndpointToString(ssh2.endpoints[0]),
207
+ port: ssh2.endpoints[0].port,
208
+ user: ssh2.user,
209
+ password: ssh2.password,
210
+ privateKey: ssh2.keyPair?.privateKey,
209
211
  dialErrorLimit: 3,
210
- hostKey: ssh3.hostKey
212
+ hostKey: ssh2.hostKey
211
213
  }));
212
214
  }
213
215
  function createCommand(command) {
@@ -216,76 +218,234 @@ function createCommand(command) {
216
218
  }
217
219
  return command;
218
220
  }
221
+ function wrapWithWorkDir(dir) {
222
+ if (!dir) {
223
+ return (command) => output(command);
224
+ }
225
+ return (command) => interpolate`cd "${dir}" && ${command}`;
226
+ }
227
+ function wrapWithEnvironment(environment) {
228
+ if (!environment) {
229
+ return (command) => output(command);
230
+ }
231
+ return (command) => output({ command, environment }).apply(({ command: command2, environment: environment2 }) => {
232
+ if (!environment2 || Object.keys(environment2).length === 0) {
233
+ return command2;
234
+ }
235
+ const envExport = Object.entries(environment2).map(([key, value]) => `export ${key}="${value}"`).join(" && ");
236
+ return `${envExport} && ${command2}`;
237
+ });
238
+ }
239
+ function wrapWithWaitFor(timeout = 300, interval = 5) {
240
+ return (command) => (
241
+ // TOD: escape the command
242
+ interpolate`timeout ${timeout} bash -c 'while ! ${createCommand(command)}; do sleep ${interval}; done'`
243
+ );
244
+ }
245
+ function applyUpdateTriggers(env, triggers) {
246
+ return output({ env, triggers }).apply(({ env: env2, triggers: triggers2 }) => {
247
+ if (!triggers2) {
248
+ return env2;
249
+ }
250
+ const hash = sha256(JSON.stringify(triggers2));
251
+ const hashHex = Buffer.from(hash).toString("hex");
252
+ return {
253
+ ...env2,
254
+ HIGHSTATE_UPDATE_TRIGGER_HASH: hashHex
255
+ };
256
+ });
257
+ }
219
258
  var Command = class _Command extends ComponentResource {
220
- command;
221
259
  stdout;
222
260
  stderr;
223
261
  constructor(name, args, opts) {
224
262
  super("highstate:common:Command", name, args, opts);
225
- this.command = output(args).apply((args2) => {
226
- if (args2.host === "local") {
227
- return new local.Command(
228
- name,
229
- {
230
- create: createCommand(args2.create),
231
- update: args2.update ? createCommand(args2.update) : void 0,
232
- delete: args2.delete ? createCommand(args2.delete) : void 0,
233
- logging: args2.logging,
234
- triggers: args2.triggers,
235
- dir: args2.cwd
236
- },
237
- { ...opts, parent: this }
238
- );
239
- }
240
- if (!args2.host.ssh) {
241
- throw new Error(`The host "${args2.host.hostname}" has no SSH credentials`);
242
- }
243
- return new remote.Command(
244
- name,
245
- {
246
- connection: getServerConnection(args2.host.ssh),
247
- create: createCommand(args2.create),
248
- update: args2.update ? createCommand(args2.update) : void 0,
249
- delete: args2.delete ? createCommand(args2.delete) : void 0,
250
- logging: args2.logging,
251
- triggers: args2.triggers
252
- },
253
- { ...opts, parent: this }
254
- );
263
+ const environment = applyUpdateTriggers(
264
+ args.environment,
265
+ args.updateTriggers
266
+ );
267
+ const command = args.host === "local" ? new local.Command(
268
+ name,
269
+ {
270
+ create: output(args.create).apply(createCommand),
271
+ update: args.update ? output(args.update).apply(createCommand) : void 0,
272
+ delete: args.delete ? output(args.delete).apply(createCommand) : void 0,
273
+ logging: args.logging,
274
+ triggers: args.triggers ? output(args.triggers).apply(flat) : void 0,
275
+ dir: args.cwd ?? homedir(),
276
+ environment,
277
+ stdin: args.stdin
278
+ },
279
+ { ...opts, parent: this }
280
+ ) : new remote.Command(
281
+ name,
282
+ {
283
+ connection: output(args.host).apply((server) => {
284
+ if (!server.ssh) {
285
+ throw new Error(`The server "${server.hostname}" has no SSH credentials`);
286
+ }
287
+ return getServerConnection(server.ssh);
288
+ }),
289
+ create: output(args.create).apply(createCommand).apply(wrapWithWorkDir(args.cwd)).apply(wrapWithEnvironment(environment)),
290
+ update: args.update ? output(args.update).apply(createCommand).apply(wrapWithWorkDir(args.cwd)).apply(wrapWithEnvironment(environment)) : void 0,
291
+ delete: args.delete ? output(args.delete).apply(createCommand).apply(wrapWithWorkDir(args.cwd)).apply(wrapWithEnvironment(environment)) : void 0,
292
+ logging: args.logging,
293
+ triggers: args.triggers ? output(args.triggers).apply(flat) : void 0,
294
+ stdin: args.stdin,
295
+ addPreviousOutputInEnv: false
296
+ // TODO: does not work if server do not define AcceptEnv
297
+ // environment,
298
+ },
299
+ { ...opts, parent: this }
300
+ );
301
+ this.stdout = command.stdout;
302
+ this.stderr = command.stderr;
303
+ this.registerOutputs({
304
+ stdout: this.stdout,
305
+ stderr: this.stderr
255
306
  });
256
- this.stdout = this.command.stdout;
257
- this.stderr = this.command.stderr;
258
307
  }
308
+ /**
309
+ * Waits for the command to complete and returns its output.
310
+ * The standard output will be returned.
311
+ */
312
+ async wait() {
313
+ return await toPromise(this.stdout);
314
+ }
315
+ /**
316
+ * Creates a command that writes the given content to a file on the host.
317
+ * The file will be created if it does not exist, and overwritten if it does.
318
+ *
319
+ * Use for small text files like configuration files.
320
+ */
259
321
  static createTextFile(name, options, opts) {
260
- return output(options).apply((options2) => {
261
- const escapedContent = options2.content.replace(/"/g, '\\"');
262
- const command = new _Command(
263
- name,
264
- {
265
- host: options2.host,
266
- create: interpolate`mkdir -p $(dirname ${options2.path}) && echo "${escapedContent}" > ${options2.path}`,
267
- delete: interpolate`rm -rf ${options2.path}`
268
- },
269
- opts
270
- );
271
- return command;
272
- });
322
+ return new _Command(
323
+ name,
324
+ {
325
+ host: options.host,
326
+ create: interpolate`mkdir -p $(dirname "${options.path}") && cat > ${options.path}`,
327
+ delete: interpolate`rm -rf ${options.path}`,
328
+ stdin: options.content
329
+ },
330
+ opts
331
+ );
273
332
  }
333
+ /**
334
+ * Creates a command that waits for a file to be created and then reads its content.
335
+ * This is useful for waiting for a file to be generated by another process.
336
+ *
337
+ * Use for small text files like configuration files.
338
+ */
274
339
  static receiveTextFile(name, options, opts) {
275
- return output(options).apply((options2) => {
276
- const command = new _Command(
277
- name,
278
- {
279
- host: options2.host,
280
- create: interpolate`while ! test -f ${options2.path}; do sleep 1; done; cat ${options2.path}`,
281
- logging: "stderr"
282
- },
283
- opts
340
+ return new _Command(
341
+ name,
342
+ {
343
+ host: options.host,
344
+ create: interpolate`while ! test -f "${options.path}"; do sleep 1; done; cat "${options.path}"`,
345
+ logging: "stderr"
346
+ },
347
+ opts
348
+ );
349
+ }
350
+ /**
351
+ * Creates a command that waits for a condition to be met.
352
+ * The command will run until the condition is met or the timeout is reached.
353
+ *
354
+ * The condition is considered met if the command returns a zero exit code.
355
+ *
356
+ * @param name The name of the command resource.
357
+ * @param args The arguments for the command, including the condition to check.
358
+ * @param opts Optional resource options.
359
+ */
360
+ static waitFor(name, args, opts) {
361
+ return new _Command(
362
+ name,
363
+ {
364
+ ...args,
365
+ create: output(args.create).apply(wrapWithWaitFor(args.timeout, args.interval)),
366
+ update: args.update ? output(args.update).apply(wrapWithWaitFor(args.timeout, args.interval)) : void 0,
367
+ delete: args.delete ? output(args.delete).apply(wrapWithWaitFor(args.timeout, args.interval)) : void 0
368
+ },
369
+ opts
370
+ );
371
+ }
372
+ };
373
+ var ImplementationMediator = class {
374
+ constructor(path, inputSchema, outputSchema) {
375
+ this.path = path;
376
+ this.inputSchema = inputSchema;
377
+ this.outputSchema = outputSchema;
378
+ }
379
+ implement(dataSchema, func) {
380
+ return async (input, data) => {
381
+ const parsedInput = this.inputSchema.safeParse(input);
382
+ if (!parsedInput.success) {
383
+ throw new Error(
384
+ `Invalid input for implementation "${this.path}": ${parsedInput.error.message}`
385
+ );
386
+ }
387
+ const parsedData = dataSchema.safeParse(data);
388
+ if (!parsedData.success) {
389
+ throw new Error(
390
+ `Invalid data for implementation "${this.path}": ${parsedData.error.message}`
391
+ );
392
+ }
393
+ const result = await func(parsedInput.data, parsedData.data);
394
+ const parsedResult = this.outputSchema.safeParse(result);
395
+ if (!parsedResult.success) {
396
+ throw new Error(
397
+ `Invalid output from implementation "${this.path}": ${parsedResult.error.message}`
398
+ );
399
+ }
400
+ return parsedResult.data;
401
+ };
402
+ }
403
+ async call(implRef, input) {
404
+ const resolvedImplRef = await toPromise(implRef);
405
+ const resolvedInput = await toPromise(input);
406
+ const importPath = `${resolvedImplRef.package}/impl/${this.path}`;
407
+ let impl;
408
+ try {
409
+ impl = await import(importPath);
410
+ } catch (error) {
411
+ throw new Error(`Failed to import module "${importPath}" required by implementation.`, {
412
+ cause: error
413
+ });
414
+ }
415
+ const funcs = Object.entries(impl).filter((value) => typeof value[1] === "function");
416
+ if (funcs.length === 0) {
417
+ throw new Error(`No implementation functions found in module "${importPath}".`);
418
+ }
419
+ if (funcs.length > 1) {
420
+ throw new Error(
421
+ `Multiple implementation functions found in module "${importPath}": ${funcs.map((func) => func[0]).join(", ")}. Ensure only one function is exported.`
284
422
  );
285
- return command;
286
- });
423
+ }
424
+ const [funcName, implFunc] = funcs[0];
425
+ let result;
426
+ try {
427
+ result = await implFunc(resolvedInput, resolvedImplRef.data);
428
+ } catch (error) {
429
+ console.error(`Error in implementation function "${funcName}":`, error);
430
+ throw new Error(`Implementation function "${funcName}" failed`);
431
+ }
432
+ const parsedResult = this.outputSchema.safeParse(result);
433
+ if (!parsedResult.success) {
434
+ throw new Error(
435
+ `Implementation function "${funcName}" returned invalid result: ${parsedResult.error.message}`
436
+ );
437
+ }
438
+ return parsedResult.data;
439
+ }
440
+ callOutput(implRef, input) {
441
+ return output(this.call(implRef, input));
287
442
  }
288
443
  };
444
+ var dnsRecordMediator = new ImplementationMediator(
445
+ "dns-record",
446
+ z.object({ name: z.string(), args: z.custom() }),
447
+ z.instanceof(ComponentResource)
448
+ );
289
449
  function getTypeByEndpoint(endpoint) {
290
450
  switch (endpoint.type) {
291
451
  case "ipv4":
@@ -302,53 +462,73 @@ var DnsRecord = class extends ComponentResource {
302
462
  */
303
463
  dnsRecord;
304
464
  /**
305
- * The wait commands to be executed after the DNS record is created/updated.
465
+ * The commands to be executed after the DNS record is created/updated.
306
466
  *
307
- * Use this field as a dependency for other resources.
467
+ * These commands will wait for the DNS record to be resolved to the specified value.
308
468
  */
309
469
  waitCommands;
310
470
  constructor(name, args, opts) {
311
471
  super("highstate:common:DnsRecord", name, args, opts);
312
- this.dnsRecord = output(args).apply((args2) => {
313
- const l3Endpoint = parseL3Endpoint(args2.value);
314
- const type = args2.type ?? getTypeByEndpoint(l3Endpoint);
315
- return output(
316
- this.create(
317
- name,
318
- {
319
- ...args2,
320
- type,
321
- value: l3EndpointToString(l3Endpoint)
322
- },
323
- { ...opts, parent: this }
324
- )
325
- );
472
+ const l3Endpoint = output(args.value).apply((value) => parseL3Endpoint(value));
473
+ const resolvedValue = l3Endpoint.apply(l3EndpointToString);
474
+ const resolvedType = args.type ? output(args.type) : l3Endpoint.apply(getTypeByEndpoint);
475
+ this.dnsRecord = output(args.provider).apply((provider) => {
476
+ return dnsRecordMediator.call(provider.implRef, {
477
+ name,
478
+ args: {
479
+ name: args.name,
480
+ priority: args.priority,
481
+ ttl: args.ttl,
482
+ value: resolvedValue,
483
+ type: resolvedType
484
+ }
485
+ });
326
486
  });
327
- this.waitCommands = output(args).apply((args2) => {
328
- const waitAt = args2.waitAt ? Array.isArray(args2.waitAt) ? args2.waitAt : [args2.waitAt] : [];
329
- return waitAt.map((host) => {
487
+ this.waitCommands = output({
488
+ waitAt: args.waitAt,
489
+ resolvedType,
490
+ proxied: args.proxied
491
+ }).apply(({ waitAt, resolvedType: resolvedType2, proxied }) => {
492
+ if (resolvedType2 === "CNAME") {
493
+ return [];
494
+ }
495
+ const resolvedHosts = waitAt ? [waitAt].flat() : [];
496
+ if (proxied) {
497
+ return resolvedHosts.map((host) => {
498
+ const hostname = host === "local" ? "local" : host.hostname;
499
+ return new Command(
500
+ `${name}.wait-for-dns.${hostname}`,
501
+ {
502
+ host,
503
+ create: [
504
+ interpolate`while ! getent hosts "${args.name}";`,
505
+ interpolate`do echo "Waiting for DNS record \"${args.name}\" to be available...";`,
506
+ `sleep 5;`,
507
+ `done`
508
+ ]
509
+ },
510
+ { parent: this }
511
+ );
512
+ });
513
+ }
514
+ return resolvedHosts.map((host) => {
330
515
  const hostname = host === "local" ? "local" : host.hostname;
331
516
  return new Command(
332
- `${name}-wait-${hostname}`,
517
+ `${name}.wait-for-dns.${hostname}`,
333
518
  {
334
519
  host,
335
- create: `while ! getent hosts ${args2.name} >/dev/null; do echo "Waiting for DNS record ${args2.name} to be created"; sleep 5; done`,
336
- triggers: [args2.type, args2.ttl, args2.priority, args2.proxied]
520
+ create: [
521
+ interpolate`while ! getent hosts "${args.name}" | grep "${resolvedValue}";`,
522
+ interpolate`do echo "Waiting for DNS record \"${args.name}" to resolve to "${resolvedValue}"...";`,
523
+ `sleep 5;`,
524
+ `done`
525
+ ]
337
526
  },
338
527
  { parent: this }
339
528
  );
340
529
  });
341
530
  });
342
531
  }
343
- static create(name, args, opts) {
344
- return output(args).apply(async (args2) => {
345
- const providerType = args2.provider.type;
346
- const implName = `${capitalize(providerType)}DnsRecord`;
347
- const implModule = await import(`@highstate/${providerType}`);
348
- const implClass = implModule[implName];
349
- return new implClass(name, args2, opts);
350
- });
351
- }
352
532
  };
353
533
  var DnsRecordSet = class _DnsRecordSet extends ComponentResource {
354
534
  /**
@@ -356,34 +536,63 @@ var DnsRecordSet = class _DnsRecordSet extends ComponentResource {
356
536
  */
357
537
  dnsRecords;
358
538
  /**
359
- * The wait commands to be executed after the DNS records are created/updated.
539
+ * The flat list of all wait commands for the DNS records.
360
540
  */
361
541
  waitCommands;
362
- constructor(name, records, opts) {
363
- super("highstate:common:DnsRecordSet", name, records, opts);
364
- this.dnsRecords = records;
365
- this.waitCommands = records.apply(
366
- (records2) => records2.flatMap((record) => record.waitCommands)
367
- );
368
- }
369
- static create(name, args, opts) {
370
- const records = output(args).apply((args2) => {
371
- const recordName = args2.name ?? name;
372
- const values = normalize(args2.value, args2.values);
373
- return output(
374
- args2.providers.filter((provider) => recordName.endsWith(provider.domain)).flatMap((provider) => {
375
- return values.map((value) => {
376
- const l3Endpoint = parseL3Endpoint(value);
377
- return DnsRecord.create(
378
- `${provider.type}-from-${recordName}-to-${l3EndpointToString(l3Endpoint)}`,
379
- { name: recordName, ...args2, value: l3Endpoint, provider },
380
- opts
381
- );
382
- });
383
- })
384
- );
542
+ constructor(name, args, opts) {
543
+ super("highstate:common:DnsRecordSet", name, args, opts);
544
+ const matchedProviders = output({
545
+ providers: args.providers,
546
+ name: args.name ?? name
547
+ }).apply(({ providers }) => {
548
+ const matchedProviders2 = providers.filter((provider) => name.endsWith(provider.domain));
549
+ if (matchedProviders2.length === 0) {
550
+ throw new Error(`No DNS provider matched the domain "${name}"`);
551
+ }
552
+ return matchedProviders2;
385
553
  });
386
- return new _DnsRecordSet(name, records, opts);
554
+ this.dnsRecords = normalizeInputsAndMap(args.value, args.values, (value) => {
555
+ return output({
556
+ name: args.name ?? name,
557
+ providers: matchedProviders
558
+ }).apply(({ name: name2, providers }) => {
559
+ return providers.flatMap((provider) => {
560
+ const l3Endpoint = parseL3Endpoint(value);
561
+ return new DnsRecord(
562
+ `${name2}.${provider.id}.${l3EndpointToString(l3Endpoint)}`,
563
+ {
564
+ name: name2,
565
+ provider,
566
+ value: l3Endpoint,
567
+ type: args.type ?? getTypeByEndpoint(l3Endpoint),
568
+ proxied: args.proxied,
569
+ ttl: args.ttl,
570
+ priority: args.priority,
571
+ waitAt: args.waitAt
572
+ },
573
+ { parent: this }
574
+ );
575
+ });
576
+ });
577
+ }).apply(flat);
578
+ this.waitCommands = this.dnsRecords.apply((records) => records.flatMap((record) => record.waitCommands)).apply(flat);
579
+ }
580
+ static dnsRecordSetCache = /* @__PURE__ */ new Map();
581
+ /**
582
+ * Creates a DNS record set for the specified endpoints and waits for it to be resolved.
583
+ *
584
+ * If a DNS record set with the same name already exists, it will be reused.
585
+ *
586
+ * @param name The name of the DNS record set.
587
+ * @param args The arguments for the DNS record set.
588
+ * @param opts The options for the resource.
589
+ */
590
+ static createOnce(name, args, opts) {
591
+ return getOrCreate(
592
+ _DnsRecordSet.dnsRecordSetCache,
593
+ name,
594
+ () => new _DnsRecordSet(name, args, opts)
595
+ );
387
596
  }
388
597
  };
389
598
  async function updateEndpointsWithFqdn(endpoints, fqdn, fqdnEndpointFilter, patchMode, dnsProviders) {
@@ -395,7 +604,7 @@ async function updateEndpointsWithFqdn(endpoints, fqdn, fqdnEndpointFilter, patc
395
604
  };
396
605
  }
397
606
  const filteredEndpoints = filterEndpoints(resolvedEndpoints, fqdnEndpointFilter);
398
- const dnsRecordSet = DnsRecordSet.create(fqdn, {
607
+ const dnsRecordSet = new DnsRecordSet(fqdn, {
399
608
  providers: dnsProviders,
400
609
  values: filteredEndpoints,
401
610
  waitAt: "local"
@@ -435,6 +644,16 @@ async function updateEndpointsWithFqdn(endpoints, fqdn, fqdnEndpointFilter, patc
435
644
  function generatePassword() {
436
645
  return secureMask.apply(randomBytes(32)).password;
437
646
  }
647
+ function generateKey(format = "hex") {
648
+ const bytes = randomBytes(32);
649
+ if (format === "raw") {
650
+ return bytes;
651
+ }
652
+ if (format === "base64") {
653
+ return Buffer.from(bytes).toString("base64");
654
+ }
655
+ return bytesToHex(bytes);
656
+ }
438
657
 
439
658
  // assets/images.json
440
659
  var terminal_ssh = {
@@ -442,63 +661,48 @@ var terminal_ssh = {
442
661
  };
443
662
 
444
663
  // src/shared/ssh.ts
445
- function createSshTerminal(credentials) {
446
- return output(credentials).apply((credentials2) => {
447
- if (!credentials2) {
448
- return void 0;
449
- }
450
- const command = ["ssh", "-tt", "-o", "UserKnownHostsFile=/known_hosts"];
451
- const endpoint = credentials2.endpoints[0];
452
- command.push("-p", endpoint.port.toString());
453
- if (credentials2.keyPair) {
454
- command.push("-i", "/private_key");
455
- }
456
- command.push(`${credentials2.user}@${l3EndpointToString(endpoint)}`);
457
- if (credentials2.password) {
458
- command.unshift("sshpass", "-f", "/password");
664
+ async function createSshTerminal(credentials) {
665
+ const resolvedCredentials = await toPromise(credentials);
666
+ const command = ["ssh", "-tt", "-o", "UserKnownHostsFile=/known_hosts"];
667
+ const endpoint = resolvedCredentials.endpoints[0];
668
+ command.push("-p", endpoint.port.toString());
669
+ if (resolvedCredentials.keyPair) {
670
+ command.push("-i", "/private_key");
671
+ }
672
+ command.push(`${resolvedCredentials.user}@${l3EndpointToString(endpoint)}`);
673
+ if (resolvedCredentials.password) {
674
+ command.unshift("sshpass", "-f", "/password");
675
+ }
676
+ return output({
677
+ name: "ssh",
678
+ meta: {
679
+ title: "Shell",
680
+ description: "Connect to the server via SSH.",
681
+ icon: "gg:remote"
682
+ },
683
+ spec: {
684
+ image: terminal_ssh.image,
685
+ command,
686
+ files: stripNullish({
687
+ "/password": resolvedCredentials.password ? fileFromString("password", resolvedCredentials.password, { isSecret: true }) : void 0,
688
+ "/private_key": resolvedCredentials.keyPair?.privateKey ? fileFromString("private_key", resolvedCredentials.keyPair.privateKey, {
689
+ isSecret: true,
690
+ mode: 384
691
+ }) : void 0,
692
+ "/known_hosts": fileFromString(
693
+ "known_hosts",
694
+ `${l3EndpointToString(endpoint)} ${resolvedCredentials.hostKey}`,
695
+ { mode: 420 }
696
+ )
697
+ })
459
698
  }
460
- return {
461
- name: "ssh",
462
- meta: {
463
- title: "Shell",
464
- description: "Connect to the server via SSH",
465
- icon: "gg:remote"
466
- },
467
- spec: {
468
- image: terminal_ssh.image,
469
- command,
470
- files: {
471
- "/password": credentials2.password,
472
- "/private_key": credentials2.keyPair?.privateKey && {
473
- content: {
474
- type: "embedded",
475
- value: credentials2.keyPair?.privateKey
476
- },
477
- meta: {
478
- name: "private_key",
479
- mode: 384
480
- }
481
- },
482
- "/known_hosts": {
483
- content: {
484
- type: "embedded",
485
- value: `${l3EndpointToString(endpoint)} ${credentials2.hostKey}`
486
- },
487
- meta: {
488
- name: "known_hosts",
489
- mode: 420
490
- }
491
- }
492
- }
493
- }
494
- };
495
699
  });
496
700
  }
497
- function generatePrivateKey() {
701
+ function generateSshPrivateKey() {
498
702
  const seed = randomBytes$1(32);
499
- return getKeys(seed).privateKey;
703
+ return secret(getKeys(seed).privateKey);
500
704
  }
501
- function privateKeyToKeyPair(privateKeyString) {
705
+ function sshPrivateKeyToKeyPair(privateKeyString) {
502
706
  return output(privateKeyString).apply((privateKeyString2) => {
503
707
  const privateKeyStruct = PrivateExport.decode(privateKeyString2);
504
708
  const privKey = privateKeyStruct.keys[0].privKey.privKey;
@@ -511,60 +715,88 @@ function privateKeyToKeyPair(privateKeyString) {
511
715
  });
512
716
  });
513
717
  }
514
- function getOrCreateSshKeyPair(inputs, secrets) {
515
- if (inputs.sshKeyPair) {
516
- return output(inputs.sshKeyPair);
517
- }
518
- const privateKey = getOrCreateSecret(secrets, "sshPrivateKey", generatePrivateKey);
519
- return privateKey.apply(privateKeyToKeyPair);
718
+ async function createServerBundle(options) {
719
+ const server = await createServerEntity(options);
720
+ const ssh = await toPromise(server.ssh);
721
+ return {
722
+ server,
723
+ terminal: ssh ? await createSshTerminal(ssh) : void 0
724
+ };
520
725
  }
521
- function createServerEntity(fallbackHostname, endpoint, sshPort = 22, sshUser = "root", sshPassword, sshPrivateKey, hasSsh = true) {
726
+ async function createServerEntity({
727
+ name,
728
+ fallbackHostname,
729
+ endpoints,
730
+ sshArgs = { enabled: true, port: 22, user: "root" },
731
+ sshPassword,
732
+ sshPrivateKey,
733
+ sshKeyPair,
734
+ pingInterval,
735
+ pingTimeout,
736
+ waitForPing,
737
+ waitForSsh,
738
+ sshCheckInterval,
739
+ sshCheckTimeout
740
+ }) {
741
+ if (endpoints.length === 0) {
742
+ throw new Error("At least one L3 endpoint is required to create a server entity");
743
+ }
744
+ fallbackHostname ??= name;
745
+ waitForSsh ??= sshArgs.enabled;
746
+ waitForPing ??= !waitForSsh;
747
+ if (waitForPing) {
748
+ await Command.waitFor(`${name}.ping`, {
749
+ host: "local",
750
+ create: `ping -c 1 ${l3EndpointToString(endpoints[0])}`,
751
+ timeout: pingTimeout ?? 300,
752
+ interval: pingInterval ?? 5,
753
+ triggers: [Date.now()]
754
+ }).wait();
755
+ }
756
+ if (!sshArgs.enabled) {
757
+ return output({
758
+ hostname: name,
759
+ endpoints
760
+ });
761
+ }
762
+ const sshHost = sshArgs?.host ?? l3EndpointToString(endpoints[0]);
763
+ if (waitForSsh) {
764
+ await Command.waitFor(`${name}.ssh`, {
765
+ host: "local",
766
+ create: `nc -zv ${sshHost} ${sshArgs.port}`,
767
+ timeout: sshCheckTimeout ?? 300,
768
+ interval: sshCheckInterval ?? 5,
769
+ triggers: [Date.now()]
770
+ }).wait();
771
+ }
522
772
  const connection = output({
523
- host: l3EndpointToString(endpoint),
524
- port: sshPort,
525
- user: sshUser,
773
+ host: sshHost,
774
+ port: sshArgs.port,
775
+ user: sshArgs.user,
526
776
  password: sshPassword,
527
- privateKey: sshPrivateKey,
777
+ privateKey: sshKeyPair ? output(sshKeyPair).privateKey : sshPrivateKey,
528
778
  dialErrorLimit: 3
529
779
  });
530
- if (!hasSsh) {
531
- return output({
532
- hostname: fallbackHostname,
533
- endpoints: [endpoint]
534
- });
535
- }
536
- const command = new local.Command("check-ssh", {
537
- create: `nc -zv ${l3EndpointToString(endpoint)} ${sshPort} && echo "up" || echo "down"`,
780
+ const hostnameResult = new remote.Command("hostname", {
781
+ connection,
782
+ create: "hostname",
538
783
  triggers: [Date.now()]
539
784
  });
540
- return command.stdout.apply((result) => {
541
- if (result === "down") {
542
- return output({
543
- hostname: fallbackHostname,
544
- endpoints: [endpoint]
545
- });
785
+ const hostKeyResult = new remote.Command("host-key", {
786
+ connection,
787
+ create: "cat /etc/ssh/ssh_host_ed25519_key.pub",
788
+ triggers: [Date.now()]
789
+ });
790
+ return output({
791
+ endpoints,
792
+ hostname: hostnameResult.stdout.apply((x) => x.trim()),
793
+ ssh: {
794
+ endpoints: [l3EndpointToL4(sshHost, sshArgs.port ?? 22)],
795
+ user: sshArgs.user ?? "root",
796
+ hostKey: hostKeyResult.stdout.apply((x) => x.trim()),
797
+ password: connection.password,
798
+ keyPair: sshKeyPair ? sshKeyPair : sshPrivateKey ? sshPrivateKeyToKeyPair(sshPrivateKey) : void 0
546
799
  }
547
- const hostnameResult = new remote.Command("hostname", {
548
- connection,
549
- create: "hostname",
550
- triggers: [Date.now()]
551
- });
552
- const hostKeyResult = new remote.Command("host-key", {
553
- connection,
554
- create: "cat /etc/ssh/ssh_host_ed25519_key.pub",
555
- triggers: [Date.now()]
556
- });
557
- return output({
558
- endpoints: [endpoint],
559
- hostname: hostnameResult.stdout.apply((x) => x.trim()),
560
- ssh: {
561
- endpoints: [l3ToL4Endpoint(endpoint, sshPort)],
562
- user: sshUser,
563
- hostKey: hostKeyResult.stdout.apply((x) => x.trim()),
564
- password: sshPassword,
565
- keyPair: sshPrivateKey ? privateKeyToKeyPair(sshPrivateKey) : void 0
566
- }
567
- });
568
800
  });
569
801
  }
570
802
  function assetFromFile(file) {
@@ -579,7 +811,7 @@ function assetFromFile(file) {
579
811
  "Artifact-based files cannot be converted to Pulumi assets directly. Use MaterializedFile instead."
580
812
  );
581
813
  }
582
- if (file.meta.isBinary) {
814
+ if (file.content.isBinary) {
583
815
  throw new Error(
584
816
  "Cannot create asset from inline binary file content. Please open an issue if you need this feature."
585
817
  );
@@ -646,11 +878,14 @@ var MaterializedFile = class _MaterializedFile {
646
878
  constructor(entity, parent) {
647
879
  this.entity = entity;
648
880
  this.parent = parent;
881
+ this.artifactMeta = {
882
+ title: `Materialized file "${entity.meta.name}"`
883
+ };
649
884
  }
650
885
  _tmpPath;
651
886
  _path;
652
887
  _disposed = false;
653
- artifactMeta = {};
888
+ artifactMeta;
654
889
  get path() {
655
890
  return this._path;
656
891
  }
@@ -664,7 +899,7 @@ var MaterializedFile = class _MaterializedFile {
664
899
  }
665
900
  switch (this.entity.content.type) {
666
901
  case "embedded": {
667
- const content = this.entity.meta.isBinary ? Buffer.from(this.entity.content.value, "base64") : this.entity.content.value;
902
+ const content = this.entity.content.isBinary ? Buffer.from(this.entity.content.value, "base64") : this.entity.content.value;
668
903
  await writeFile(this._path, content, { mode: this.entity.meta.mode });
669
904
  break;
670
905
  }
@@ -673,21 +908,20 @@ var MaterializedFile = class _MaterializedFile {
673
908
  break;
674
909
  }
675
910
  case "remote": {
676
- const response = await load(l7EndpointToString(this.entity.content.endpoint));
911
+ const response = await fetch(l7EndpointToString(this.entity.content.endpoint));
677
912
  if (!response.ok) throw new Error(`Failed to fetch: ${response.statusText}`);
678
913
  const arrayBuffer = await response.arrayBuffer();
679
914
  await writeFile(this._path, Buffer.from(arrayBuffer), { mode: this.entity.meta.mode });
680
915
  break;
681
916
  }
682
917
  case "artifact": {
683
- const artifactData = this.entity.content[HighstateSignature.Artifact];
684
918
  const artifactPath = process.env.HIGHSTATE_ARTIFACT_READ_PATH;
685
919
  if (!artifactPath) {
686
920
  throw new Error(
687
921
  "HIGHSTATE_ARTIFACT_READ_PATH environment variable is not set but required for artifact content"
688
922
  );
689
923
  }
690
- const tgzPath = join(artifactPath, `${artifactData.hash}.tgz`);
924
+ const tgzPath = join(artifactPath, `${this.entity.content.hash}.tgz`);
691
925
  const readStream = createReadStream(tgzPath);
692
926
  await unarchiveFromStream(readStream, dirname(this._path), "tar");
693
927
  break;
@@ -743,18 +977,15 @@ var MaterializedFile = class _MaterializedFile {
743
977
  name: this.entity.meta.name,
744
978
  mode: fileStats.mode & 511,
745
979
  // extract only permission bits
746
- size: fileStats.size,
747
- isBinary: this.entity.meta.isBinary
748
- // keep original binary flag as we can't reliably detect this from filesystem
980
+ size: fileStats.size
749
981
  };
750
982
  return {
751
983
  meta: newMeta,
752
984
  content: {
753
985
  type: "artifact",
754
- [HighstateSignature.Artifact]: {
755
- hash: hashValue,
756
- meta: await toPromise(this.artifactMeta)
757
- }
986
+ [HighstateSignature.Artifact]: true,
987
+ hash: hashValue,
988
+ meta: await toPromise(this.artifactMeta)
758
989
  }
759
990
  };
760
991
  } finally {
@@ -777,8 +1008,7 @@ var MaterializedFile = class _MaterializedFile {
777
1008
  meta: {
778
1009
  name,
779
1010
  mode,
780
- size: 0,
781
- isBinary: false
1011
+ size: 0
782
1012
  },
783
1013
  content: {
784
1014
  type: "embedded",
@@ -809,12 +1039,15 @@ var MaterializedFolder = class _MaterializedFolder {
809
1039
  constructor(entity, parent) {
810
1040
  this.entity = entity;
811
1041
  this.parent = parent;
1042
+ this.artifactMeta = {
1043
+ title: `Materialized folder "${entity.meta.name}"`
1044
+ };
812
1045
  }
813
1046
  _tmpPath;
814
1047
  _path;
815
1048
  _disposed = false;
816
1049
  _disposables = [];
817
- artifactMeta = {};
1050
+ artifactMeta;
818
1051
  get path() {
819
1052
  return this._path;
820
1053
  }
@@ -853,7 +1086,7 @@ var MaterializedFolder = class _MaterializedFolder {
853
1086
  break;
854
1087
  }
855
1088
  case "remote": {
856
- const response = await load(l7EndpointToString(this.entity.content.endpoint));
1089
+ const response = await fetch(l7EndpointToString(this.entity.content.endpoint));
857
1090
  if (!response.ok) throw new Error(`Failed to fetch: ${response.statusText}`);
858
1091
  if (!response.body) throw new Error("Response body is empty");
859
1092
  const url = new URL(l7EndpointToString(this.entity.content.endpoint));
@@ -886,14 +1119,13 @@ var MaterializedFolder = class _MaterializedFolder {
886
1119
  break;
887
1120
  }
888
1121
  case "artifact": {
889
- const artifactData = this.entity.content[HighstateSignature.Artifact];
890
1122
  const artifactPath = process.env.HIGHSTATE_ARTIFACT_READ_PATH;
891
1123
  if (!artifactPath) {
892
1124
  throw new Error(
893
1125
  "HIGHSTATE_ARTIFACT_READ_PATH environment variable is not set but required for artifact content"
894
1126
  );
895
1127
  }
896
- const tgzPath = join(artifactPath, `${artifactData.hash}.tgz`);
1128
+ const tgzPath = join(artifactPath, `${this.entity.content.hash}.tgz`);
897
1129
  const readStream = createReadStream(tgzPath);
898
1130
  await unarchiveFromStream(readStream, dirname(this._path), "tar");
899
1131
  break;
@@ -972,11 +1204,10 @@ var MaterializedFolder = class _MaterializedFolder {
972
1204
  return {
973
1205
  meta: newMeta,
974
1206
  content: {
1207
+ [HighstateSignature.Artifact]: true,
975
1208
  type: "artifact",
976
- [HighstateSignature.Artifact]: {
977
- hash: hashValue,
978
- meta: await toPromise(this.artifactMeta)
979
- }
1209
+ hash: hashValue,
1210
+ meta: await toPromise(this.artifactMeta)
980
1211
  }
981
1212
  };
982
1213
  } finally {
@@ -1033,7 +1264,7 @@ async function fetchFileSize(endpoint) {
1033
1264
  );
1034
1265
  }
1035
1266
  const url = l7EndpointToString(endpoint);
1036
- const response = await load(url, { method: "HEAD" });
1267
+ const response = await fetch(url, { method: "HEAD" });
1037
1268
  if (!response.ok) {
1038
1269
  throw new Error(`Failed to fetch file size: ${response.statusText}`);
1039
1270
  }
@@ -1042,7 +1273,7 @@ async function fetchFileSize(endpoint) {
1042
1273
  throw new Error("Content-Length header is missing in the response");
1043
1274
  }
1044
1275
  const size = parseInt(contentLength, 10);
1045
- if (isNaN(size)) {
1276
+ if (Number.isNaN(size)) {
1046
1277
  throw new Error(`Invalid Content-Length value: ${contentLength}`);
1047
1278
  }
1048
1279
  return size;
@@ -1051,7 +1282,168 @@ function getNameByEndpoint(endpoint) {
1051
1282
  const parsedEndpoint = parseL7Endpoint(endpoint);
1052
1283
  return parsedEndpoint.resource ? basename(parsedEndpoint.resource) : "";
1053
1284
  }
1285
+ var gatewayRouteMediator = new ImplementationMediator(
1286
+ "gateway-route",
1287
+ z.object({
1288
+ name: z.string(),
1289
+ spec: z.custom(),
1290
+ opts: z.custom().optional()
1291
+ }),
1292
+ z.object({
1293
+ resource: z.instanceof(Resource),
1294
+ endpoints: network.l3EndpointEntity.schema.array()
1295
+ })
1296
+ );
1297
+ var GatewayRoute = class extends ComponentResource {
1298
+ /**
1299
+ * The underlying resource created by the implementation.
1300
+ */
1301
+ resource;
1302
+ /**
1303
+ * The endpoints of the gateway which serve this route.
1304
+ *
1305
+ * In most cases, this will be a single endpoint of the gateway shared for all routes.
1306
+ */
1307
+ endpoints;
1308
+ constructor(name, args, opts) {
1309
+ super("highstate:common:GatewayRoute", name, args, opts);
1310
+ const { resource, endpoints } = gatewayRouteMediator.callOutput(output(args.gateway).implRef, {
1311
+ name,
1312
+ spec: args,
1313
+ opts: { ...opts, parent: this }
1314
+ });
1315
+ this.resource = resource;
1316
+ this.endpoints = endpoints;
1317
+ }
1318
+ };
1319
+ var tlsCertificateMediator = new ImplementationMediator(
1320
+ "tls-certificate",
1321
+ z.object({
1322
+ name: z.string(),
1323
+ spec: z.custom(),
1324
+ opts: z.custom().optional()
1325
+ }),
1326
+ z.instanceof(Resource)
1327
+ );
1328
+ var TlsCertificate = class _TlsCertificate extends ComponentResource {
1329
+ /**
1330
+ * The underlying resource created by the implementation.
1331
+ */
1332
+ resource;
1333
+ constructor(name, args, opts) {
1334
+ super("highstate:common:TlsCertificate", name, args, opts);
1335
+ const issuers = normalizeInputs(args.issuer, args.issuers);
1336
+ this.resource = output({
1337
+ issuers,
1338
+ commonName: args.commonName,
1339
+ dnsNames: args.dnsNames
1340
+ }).apply(async ({ issuers: issuers2, commonName, dnsNames }) => {
1341
+ const matchedIssuer = issuers2.find((issuer) => {
1342
+ if (commonName && !commonName.endsWith(issuer.domain)) {
1343
+ return false;
1344
+ }
1345
+ if (dnsNames && !dnsNames.every((name2) => name2.endsWith(issuer.domain))) {
1346
+ return false;
1347
+ }
1348
+ return true;
1349
+ });
1350
+ if (!matchedIssuer) {
1351
+ throw new Error(
1352
+ `No TLS issuer matched the common name "${commonName}" and DNS names "${dnsNames?.join(", ") ?? ""}"`
1353
+ );
1354
+ }
1355
+ return await tlsCertificateMediator.call(matchedIssuer.implRef, {
1356
+ name,
1357
+ spec: args
1358
+ });
1359
+ });
1360
+ }
1361
+ static tlsCertificateCache = /* @__PURE__ */ new Map();
1362
+ /**
1363
+ * Creates a TLS certificate for the specified common name and DNS names.
1364
+ *
1365
+ * If a TLS certificate with the same name already exists, it will be reused.
1366
+ *
1367
+ * @param name The name of the TLS certificate.
1368
+ * @param args The arguments for the TLS certificate.
1369
+ * @param opts The options for the resource.
1370
+ */
1371
+ static createOnce(name, args, opts) {
1372
+ return getOrCreate(
1373
+ _TlsCertificate.tlsCertificateCache,
1374
+ name,
1375
+ () => new _TlsCertificate(name, args, opts)
1376
+ );
1377
+ }
1378
+ };
1379
+ var AccessPointRoute = class extends ComponentResource {
1380
+ /**
1381
+ * The created gateway route.
1382
+ */
1383
+ route;
1384
+ /**
1385
+ * The DNS record set created for the route.
1386
+ *
1387
+ * May be shared between multiple routes with the same FQDN.
1388
+ */
1389
+ dnsRecordSet;
1390
+ /**
1391
+ * The TLS certificate created for the route.
1392
+ *
1393
+ * May be shared between multiple routes with the same FQDN.
1394
+ */
1395
+ tlsCertificate;
1396
+ constructor(name, args, opts) {
1397
+ super("highstate:common:AccessPointRoute", name, args, opts);
1398
+ if (args.fqdn && args.type === "http" && !args.insecure) {
1399
+ this.tlsCertificate = output(args.accessPoint).apply((accessPoint) => {
1400
+ if (accessPoint.tlsIssuers.length === 0) {
1401
+ return void 0;
1402
+ }
1403
+ return TlsCertificate.createOnce(
1404
+ name,
1405
+ {
1406
+ issuers: accessPoint.tlsIssuers,
1407
+ dnsNames: args.fqdn ? [args.fqdn] : [],
1408
+ nativeData: args.tlsCertificateNativeData
1409
+ },
1410
+ { ...opts, parent: this }
1411
+ );
1412
+ });
1413
+ }
1414
+ this.route = new GatewayRoute(
1415
+ name,
1416
+ {
1417
+ ...args,
1418
+ gateway: output(args.accessPoint).gateway,
1419
+ tlsCertificate: this.tlsCertificate,
1420
+ nativeData: args.gatewayNativeData
1421
+ },
1422
+ { ...opts, parent: this }
1423
+ );
1424
+ if (args.fqdn) {
1425
+ this.dnsRecordSet = output(args.accessPoint).apply(async (accessPoint) => {
1426
+ if (accessPoint.dnsProviders.length === 0) {
1427
+ return void 0;
1428
+ }
1429
+ const fqdn = await toPromise(args.fqdn);
1430
+ if (!fqdn) {
1431
+ return void 0;
1432
+ }
1433
+ return DnsRecordSet.createOnce(
1434
+ fqdn,
1435
+ {
1436
+ providers: output(args.accessPoint).dnsProviders,
1437
+ values: this.route.endpoints,
1438
+ waitAt: "local"
1439
+ },
1440
+ { ...opts, parent: this }
1441
+ );
1442
+ });
1443
+ }
1444
+ }
1445
+ };
1054
1446
 
1055
- export { Command, DnsRecord, DnsRecordSet, MaterializedFile, MaterializedFolder, archiveFromFolder, assetFromFile, createServerEntity, createSshTerminal, fetchFileSize, filterEndpoints, generatePassword, generatePrivateKey, getNameByEndpoint, getOrCreateSshKeyPair, getServerConnection, l34EndpointToString, l3EndpointToCidr, l3EndpointToString, l3ToL4Endpoint, l4EndpointToString, l4EndpointWithProtocolToString, l7EndpointToString, parseL34Endpoint, parseL3Endpoint, parseL4Endpoint, parseL7Endpoint, privateKeyToKeyPair, requireInputL3Endpoint, requireInputL4Endpoint, updateEndpoints, updateEndpointsWithFqdn };
1056
- //# sourceMappingURL=chunk-HZBJ6LLS.js.map
1057
- //# sourceMappingURL=chunk-HZBJ6LLS.js.map
1447
+ export { AccessPointRoute, Command, DnsRecord, DnsRecordSet, GatewayRoute, ImplementationMediator, MaterializedFile, MaterializedFolder, TlsCertificate, archiveFromFolder, assetFromFile, createServerBundle, createServerEntity, createSshTerminal, dnsRecordMediator, fetchFileSize, filterEndpoints, gatewayRouteMediator, generateKey, generatePassword, generateSshPrivateKey, getNameByEndpoint, getServerConnection, l34EndpointToString, l3EndpointToCidr, l3EndpointToL4, l3EndpointToString, l4EndpointToString, l4EndpointWithProtocolToString, l7EndpointToString, parseEndpoints, parseL34Endpoint, parseL3Endpoint, parseL4Endpoint, parseL7Endpoint, requireInputL3Endpoint, requireInputL4Endpoint, sshPrivateKeyToKeyPair, tlsCertificateMediator, updateEndpoints, updateEndpointsWithFqdn };
1448
+ //# sourceMappingURL=chunk-WDYIUWYZ.js.map
1449
+ //# sourceMappingURL=chunk-WDYIUWYZ.js.map