@highstate/common 0.9.18 → 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 (43) hide show
  1. package/dist/{chunk-YYNV3MVT.js → chunk-WDYIUWYZ.js} +484 -176
  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 +9 -9
  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/l4-endpoint/index.js +1 -1
  19. package/dist/units/script/index.js +1 -1
  20. package/dist/units/server-dns/index.js +1 -1
  21. package/dist/units/server-patch/index.js +1 -1
  22. package/dist/units/ssh/key-pair/index.js +4 -3
  23. package/dist/units/ssh/key-pair/index.js.map +1 -1
  24. package/package.json +61 -8
  25. package/src/shared/access-point.ts +110 -0
  26. package/src/shared/command.ts +81 -14
  27. package/src/shared/dns.ts +150 -90
  28. package/src/shared/files.ts +23 -18
  29. package/src/shared/gateway.ts +117 -0
  30. package/src/shared/impl-ref.ts +123 -0
  31. package/src/shared/index.ts +4 -0
  32. package/src/shared/network.ts +39 -25
  33. package/src/shared/passwords.ts +3 -3
  34. package/src/shared/ssh.ts +109 -124
  35. package/src/shared/tls.ts +123 -0
  36. package/src/units/access-point/index.ts +12 -0
  37. package/src/units/databases/existing-mariadb/index.ts +14 -0
  38. package/src/units/databases/existing-mongodb/index.ts +14 -0
  39. package/src/units/databases/existing-postgresql/index.ts +14 -0
  40. package/src/units/dns/record-set/index.ts +21 -11
  41. package/src/units/existing-server/index.ts +9 -15
  42. package/src/units/ssh/key-pair/index.ts +4 -3
  43. package/dist/chunk-YYNV3MVT.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import type { ArrayPatchMode, network } from "@highstate/library"
2
- import { toPromise, type Input } from "@highstate/pulumi"
2
+ import { type Input, toPromise } from "@highstate/pulumi"
3
3
  import { uniqueBy } from "remeda"
4
4
 
5
5
  /**
@@ -276,11 +276,14 @@ export function l3EndpointToL4(
276
276
  *
277
277
  * @returns The filtered list of endpoints.
278
278
  */
279
- export function filterEndpoints<TEndpoint extends network.L34Endpoint>(
279
+ export function filterEndpoints<
280
+ TEndpoint extends network.L34Endpoint,
281
+ TType extends network.L34Endpoint["type"],
282
+ >(
280
283
  endpoints: TEndpoint[],
281
284
  filter?: network.EndpointFilter,
282
- types?: network.L34Endpoint["type"][],
283
- ): TEndpoint[] {
285
+ types?: TType[],
286
+ ): (TEndpoint & { type: TType })[] {
284
287
  if (filter?.length) {
285
288
  endpoints = endpoints.filter(endpoint => filter.includes(endpoint.visibility))
286
289
  } else if (endpoints.some(endpoint => endpoint.visibility === "public")) {
@@ -289,11 +292,11 @@ export function filterEndpoints<TEndpoint extends network.L34Endpoint>(
289
292
  endpoints = endpoints.filter(endpoint => endpoint.visibility === "external")
290
293
  }
291
294
 
292
- if (types && types.length) {
293
- endpoints = endpoints.filter(endpoint => types.includes(endpoint.type))
295
+ if (types?.length) {
296
+ endpoints = endpoints.filter(endpoint => types.includes(endpoint.type as TType))
294
297
  }
295
298
 
296
- return endpoints
299
+ return endpoints as (TEndpoint & { type: TType })[]
297
300
  }
298
301
 
299
302
  /**
@@ -369,31 +372,42 @@ export function parseL7Endpoint(l7Endpoint: InputL7Endpoint): network.L7Endpoint
369
372
  * @param endpoints The new endpoints to add in string format.
370
373
  * @param inputEndpoints The input endpoints to add in object format.
371
374
  * @param mode The mode to use when updating the endpoints. Can be "replace" or "prepend". Defaults to "prepend".
372
- *
373
- * @returns The updated list of endpoints.
375
+ * @returns The updated list of endpoints with duplicates removed.
374
376
  */
375
- export async function updateEndpoints<TEdnpoints extends network.L34Endpoint>(
376
- currentEndpoints: Input<TEdnpoints[]>,
377
- endpoints: string[],
378
- inputEndpoints: Input<TEdnpoints[]>,
377
+ export async function updateEndpoints<TEndpoints extends network.L34Endpoint>(
378
+ currentEndpoints: Input<TEndpoints[]>,
379
+ endpoints: string[] | undefined,
380
+ inputEndpoints: Input<TEndpoints[]> | undefined,
379
381
  mode: ArrayPatchMode = "prepend",
380
- ): Promise<TEdnpoints[]> {
382
+ ): Promise<TEndpoints[]> {
381
383
  const resolvedCurrentEndpoints = await toPromise(currentEndpoints)
382
- const resolvedInputEndpoints = await toPromise(inputEndpoints)
383
-
384
- const newEndpoints = uniqueBy(
385
- //
386
- [...endpoints.map(parseL34Endpoint), ...resolvedInputEndpoints],
387
- endpoint => l34EndpointToString(endpoint),
388
- )
384
+ const newEndpoints = await parseEndpoints(endpoints, inputEndpoints)
389
385
 
390
386
  if (mode === "replace") {
391
- return newEndpoints as TEdnpoints[]
387
+ return newEndpoints as TEndpoints[]
392
388
  }
393
389
 
394
390
  return uniqueBy(
395
- //
396
391
  [...newEndpoints, ...resolvedCurrentEndpoints],
397
- endpoint => l34EndpointToString(endpoint),
398
- ) as TEdnpoints[]
392
+ l34EndpointToString,
393
+ ) as TEndpoints[]
394
+ }
395
+
396
+ /**
397
+ * Parses a list of endpoints from strings and input objects.
398
+ *
399
+ * @param endpoints The list of endpoint strings to parse.
400
+ * @param inputEndpoints The list of input endpoint objects to use.
401
+ * @returns The parsed list of endpoint objects with duplicates removed.
402
+ */
403
+ export async function parseEndpoints<TEndpoints extends network.L34Endpoint>(
404
+ endpoints: string[] | undefined,
405
+ inputEndpoints: Input<TEndpoints[]> | undefined,
406
+ ): Promise<TEndpoints[]> {
407
+ const resolvedInputEndpoints = await toPromise(inputEndpoints)
408
+
409
+ return uniqueBy(
410
+ [...(endpoints?.map(parseL34Endpoint) ?? []), ...(resolvedInputEndpoints ?? [])],
411
+ l34EndpointToString,
412
+ ) as TEndpoints[]
399
413
  }
@@ -6,7 +6,7 @@ import { secureMask } from "micro-key-producer/password.js"
6
6
  *
7
7
  * It uses "Safari Keychain Secure Password" format.
8
8
  *
9
- * The approximate entropy is 71 bits (https://support.apple.com/guide/security/automatic-strong-passwords-secc84c811c4/web).
9
+ * The approximate entropy is [71 bits](https://support.apple.com/guide/security/automatic-strong-passwords-secc84c811c4/web).
10
10
  */
11
11
  export function generatePassword(): string {
12
12
  return secureMask.apply(randomBytes(32)).password
@@ -19,13 +19,13 @@ type KeyFormatMap = {
19
19
  }
20
20
 
21
21
  /**
22
- * Generates a secure random key strong enough for online use such as encryption.
22
+ * Generates a secure random key strong enough for offline use such as encryption.
23
23
  *
24
24
  * The strong entropy is 256 bits.
25
25
  *
26
26
  * @param format The format of the generated key. By default, it is "hex".
27
27
  */
28
- export function generateKey<TFormat extends keyof KeyFormatMap>(
28
+ export function generateKey<TFormat extends keyof KeyFormatMap = "hex">(
29
29
  format: TFormat = "hex" as TFormat,
30
30
  ): KeyFormatMap[TFormat] {
31
31
  const bytes = randomBytes(32)
package/src/shared/ssh.ts CHANGED
@@ -1,13 +1,12 @@
1
- import type { common, network, ssh } from "@highstate/library"
1
+ import { stripNullish, type UnitTerminal } from "@highstate/contract"
2
+ import type { ssh, common, network } from "@highstate/library"
2
3
  import {
3
- ensureSecretValue,
4
+ fileFromString,
4
5
  output,
5
- Output,
6
+ type Output,
6
7
  secret,
7
8
  toPromise,
8
9
  type Input,
9
- type InstanceTerminal,
10
- type UnitSecret,
11
10
  } from "@highstate/pulumi"
12
11
  import getKeys, { PrivateExport } from "micro-key-producer/ssh.js"
13
12
  import { randomBytes } from "micro-key-producer/utils.js"
@@ -16,71 +15,60 @@ import * as images from "../../assets/images.json"
16
15
  import { l3EndpointToString, l3EndpointToL4 } from "./network"
17
16
  import { Command } from "./command"
18
17
 
19
- export function createSshTerminal(
20
- credentials: Input<ssh.Credentials | undefined>,
21
- ): Output<InstanceTerminal | undefined> {
22
- return output(credentials).apply(credentials => {
23
- if (!credentials) {
24
- return undefined
25
- }
18
+ export async function createSshTerminal(
19
+ credentials: Input<ssh.Connection>,
20
+ ): Promise<Output<UnitTerminal>> {
21
+ const resolvedCredentials = await toPromise(credentials)
26
22
 
27
- const command = ["ssh", "-tt", "-o", "UserKnownHostsFile=/known_hosts"]
23
+ const command = ["ssh", "-tt", "-o", "UserKnownHostsFile=/known_hosts"]
28
24
 
29
- // TODO: select best endpoint based on the environment
30
- const endpoint = credentials.endpoints[0]
25
+ // TODO: select best endpoint based on the environment
26
+ const endpoint = resolvedCredentials.endpoints[0]
31
27
 
32
- command.push("-p", endpoint.port.toString())
28
+ command.push("-p", endpoint.port.toString())
33
29
 
34
- if (credentials.keyPair) {
35
- command.push("-i", "/private_key")
36
- }
30
+ if (resolvedCredentials.keyPair) {
31
+ command.push("-i", "/private_key")
32
+ }
37
33
 
38
- command.push(`${credentials.user}@${l3EndpointToString(endpoint)}`)
34
+ command.push(`${resolvedCredentials.user}@${l3EndpointToString(endpoint)}`)
39
35
 
40
- if (credentials.password) {
41
- command.unshift("sshpass", "-f", "/password")
42
- }
36
+ if (resolvedCredentials.password) {
37
+ command.unshift("sshpass", "-f", "/password")
38
+ }
43
39
 
44
- return {
45
- name: "ssh",
40
+ return output({
41
+ name: "ssh",
46
42
 
47
- meta: {
48
- title: "Shell",
49
- description: "Connect to the server via SSH",
50
- icon: "gg:remote",
51
- },
43
+ meta: {
44
+ title: "Shell",
45
+ description: "Connect to the server via SSH.",
46
+ icon: "gg:remote",
47
+ },
52
48
 
53
- spec: {
54
- image: images["terminal-ssh"].image,
55
- command,
49
+ spec: {
50
+ image: images["terminal-ssh"].image,
51
+ command,
56
52
 
57
- files: {
58
- "/password": credentials.password,
53
+ files: stripNullish({
54
+ "/password": resolvedCredentials.password
55
+ ? fileFromString("password", resolvedCredentials.password, { isSecret: true })
56
+ : undefined,
59
57
 
60
- "/private_key": credentials.keyPair?.privateKey && {
61
- content: {
62
- type: "embedded",
63
- value: credentials.keyPair?.privateKey,
64
- },
65
- meta: {
66
- name: "private_key",
58
+ "/private_key": resolvedCredentials.keyPair?.privateKey
59
+ ? fileFromString("private_key", resolvedCredentials.keyPair.privateKey, {
60
+ isSecret: true,
67
61
  mode: 0o600,
68
- },
69
- },
70
-
71
- "/known_hosts": {
72
- content: {
73
- type: "embedded",
74
- value: `${l3EndpointToString(endpoint)} ${credentials.hostKey}`,
75
- },
76
- meta: {
77
- name: "known_hosts",
78
- mode: 0o644,
79
- },
80
- },
81
- },
82
- },
83
- } satisfies InstanceTerminal
62
+ })
63
+ : undefined,
64
+
65
+ "/known_hosts": fileFromString(
66
+ "known_hosts",
67
+ `${l3EndpointToString(endpoint)} ${resolvedCredentials.hostKey}`,
68
+ { mode: 0o644 },
69
+ ),
70
+ }),
71
+ },
84
72
  })
85
73
  }
86
74
 
@@ -90,10 +78,10 @@ export function createSshTerminal(
90
78
  *
91
79
  * @returns The generated SSH private key in PEM format.
92
80
  */
93
- export function generateSshPrivateKey(): string {
81
+ export function generateSshPrivateKey(): Output<string> {
94
82
  const seed = randomBytes(32)
95
83
 
96
- return getKeys(seed).privateKey
84
+ return secret(getKeys(seed).privateKey)
97
85
  }
98
86
 
99
87
  /**
@@ -120,25 +108,6 @@ export function sshPrivateKeyToKeyPair(privateKeyString: Input<string>): Output<
120
108
  })
121
109
  }
122
110
 
123
- /**
124
- * Ensures that the provided SSH key is available and generates a new key pair if not.
125
- * If an existing key pair is provided, it will be used instead of generating a new one.
126
- *
127
- * @param privateKey The secret containing the private key.
128
- * @param existingKeyPair An optional existing key pair to use if available.
129
- * @returns An Output of the KeyPair object.
130
- */
131
- export function ensureSshKeyPair(
132
- privateKey: UnitSecret<string | undefined>,
133
- existingKeyPair?: Input<ssh.KeyPair>,
134
- ): Output<ssh.KeyPair> {
135
- if (existingKeyPair) {
136
- return output(existingKeyPair)
137
- }
138
-
139
- return ensureSecretValue(privateKey, generateSshPrivateKey).value.apply(sshPrivateKeyToKeyPair)
140
- }
141
-
142
111
  export type ServerOptions = {
143
112
  /**
144
113
  * The local name of the server to namespace resources.
@@ -158,41 +127,25 @@ export type ServerOptions = {
158
127
  endpoints: network.L3Endpoint[]
159
128
 
160
129
  /**
161
- * The L4 endpoint of the server.
162
- *
163
- * If not provided, the first endpoint will be used + `sshPort`.
164
- */
165
- sshEndpoint?: network.L4Endpoint
166
-
167
- /**
168
- * The SSH port to use for the server.
169
- *
170
- * Will be ignored if `sshEndpoint` is provided.
171
- */
172
- sshPort?: number
173
-
174
- /**
175
- * The SSH user to use for the server.
130
+ * The arguments for the SSH connection.
176
131
  */
177
- sshUser?: string
132
+ sshArgs?: Partial<ssh.Args>
178
133
 
179
134
  /**
180
- * The SSH password to use for the server.
135
+ * The password for the SSH connection.
181
136
  */
182
- sshPassword?: Input<string | undefined>
137
+ sshPassword?: Input<string>
183
138
 
184
139
  /**
185
- * The SSH private key to use for the server.
140
+ * The private key for the SSH connection.
186
141
  */
187
142
  sshPrivateKey?: Input<string>
188
143
 
189
144
  /**
190
- * Whether the server is expected to have SSH access.
191
- * If false, the server will be created without SSH credentials.
192
- *
193
- * By default, this is true.
145
+ * The SSH key pair for the server.
146
+ * If provided, it will take precedence over the `sshPrivateKey` argument.
194
147
  */
195
- hasSsh?: boolean
148
+ sshKeyPair?: Input<ssh.KeyPair>
196
149
 
197
150
  /**
198
151
  * Whether to wait for the server to respond to a ping command before returning.
@@ -224,7 +177,7 @@ export type ServerOptions = {
224
177
  *
225
178
  * If true, the command will wait for the SSH service to respond before proceeding.
226
179
  *
227
- * By default, this is true if `hasSsh` is true.
180
+ * By default, this is true if `sshArgs.enabled` is true, otherwise false.
228
181
  */
229
182
  waitForSsh?: boolean
230
183
 
@@ -245,6 +198,36 @@ export type ServerOptions = {
245
198
  sshCheckTimeout?: number
246
199
  }
247
200
 
201
+ export type ServerBundle = {
202
+ /**
203
+ * The server entity created with the provided options.
204
+ */
205
+ server: Output<common.Server>
206
+
207
+ /**
208
+ * The SSH terminal created for the server.
209
+ */
210
+ terminal?: Output<UnitTerminal>
211
+ }
212
+
213
+ /**
214
+ * Creates a server entity with the provided options and returns a bundle containing the server entity and terminal.
215
+ *
216
+ * Basically, it just a convenience function that calls `createServerEntity` and `createSshTerminal`.
217
+ *
218
+ * @param options The options for creating the server entity.
219
+ * @returns A promise that resolves to a ServerBundle containing the server entity and terminal.
220
+ */
221
+ export async function createServerBundle(options: ServerOptions): Promise<ServerBundle> {
222
+ const server = await createServerEntity(options)
223
+ const ssh = await toPromise(server.ssh)
224
+
225
+ return {
226
+ server,
227
+ terminal: ssh ? await createSshTerminal(ssh) : undefined,
228
+ }
229
+ }
230
+
248
231
  /**
249
232
  * Creates a server entity with the provided options.
250
233
  * It will create a command to check the SSH service and return the server entity.
@@ -256,25 +239,23 @@ export async function createServerEntity({
256
239
  name,
257
240
  fallbackHostname,
258
241
  endpoints,
259
- sshEndpoint,
260
- sshPort = 22,
261
- sshUser = "root",
242
+ sshArgs = { enabled: true, port: 22, user: "root" },
262
243
  sshPassword,
263
244
  sshPrivateKey,
264
- hasSsh = true,
245
+ sshKeyPair,
265
246
  pingInterval,
266
247
  pingTimeout,
267
248
  waitForPing,
268
249
  waitForSsh,
269
250
  sshCheckInterval,
270
251
  sshCheckTimeout,
271
- }: ServerOptions): Promise<common.Server> {
252
+ }: ServerOptions): Promise<Output<common.Server>> {
272
253
  if (endpoints.length === 0) {
273
254
  throw new Error("At least one L3 endpoint is required to create a server entity")
274
255
  }
275
256
 
276
257
  fallbackHostname ??= name
277
- waitForSsh ??= hasSsh
258
+ waitForSsh ??= sshArgs.enabled
278
259
  waitForPing ??= !waitForSsh
279
260
 
280
261
  if (waitForPing) {
@@ -287,19 +268,19 @@ export async function createServerEntity({
287
268
  }).wait()
288
269
  }
289
270
 
290
- if (!hasSsh) {
291
- return {
271
+ if (!sshArgs.enabled) {
272
+ return output({
292
273
  hostname: name,
293
274
  endpoints,
294
- }
275
+ })
295
276
  }
296
277
 
297
- sshEndpoint ??= l3EndpointToL4(endpoints[0], sshPort)
278
+ const sshHost = sshArgs?.host ?? l3EndpointToString(endpoints[0])
298
279
 
299
280
  if (waitForSsh) {
300
281
  await Command.waitFor(`${name}.ssh`, {
301
282
  host: "local",
302
- create: `nc -zv ${l3EndpointToString(sshEndpoint)} ${sshPort}`,
283
+ create: `nc -zv ${sshHost} ${sshArgs.port}`,
303
284
  timeout: sshCheckTimeout ?? 300,
304
285
  interval: sshCheckInterval ?? 5,
305
286
  triggers: [Date.now()],
@@ -307,11 +288,11 @@ export async function createServerEntity({
307
288
  }
308
289
 
309
290
  const connection = output({
310
- host: l3EndpointToString(sshEndpoint),
311
- port: sshEndpoint.port,
312
- user: sshUser,
291
+ host: sshHost,
292
+ port: sshArgs.port,
293
+ user: sshArgs.user,
313
294
  password: sshPassword,
314
- privateKey: sshPrivateKey,
295
+ privateKey: sshKeyPair ? output(sshKeyPair).privateKey : sshPrivateKey,
315
296
  dialErrorLimit: 3,
316
297
  })
317
298
 
@@ -327,15 +308,19 @@ export async function createServerEntity({
327
308
  triggers: [Date.now()],
328
309
  })
329
310
 
330
- return await toPromise({
311
+ return output({
331
312
  endpoints,
332
313
  hostname: hostnameResult.stdout.apply(x => x.trim()),
333
314
  ssh: {
334
- endpoints: [sshEndpoint],
335
- user: sshUser,
315
+ endpoints: [l3EndpointToL4(sshHost, sshArgs.port ?? 22)],
316
+ user: sshArgs.user ?? "root",
336
317
  hostKey: hostKeyResult.stdout.apply(x => x.trim()),
337
- password: sshPassword,
338
- keyPair: sshPrivateKey ? sshPrivateKeyToKeyPair(sshPrivateKey) : undefined,
318
+ password: connection.password,
319
+ keyPair: sshKeyPair
320
+ ? sshKeyPair
321
+ : sshPrivateKey
322
+ ? sshPrivateKeyToKeyPair(sshPrivateKey)
323
+ : undefined,
339
324
  },
340
325
  })
341
326
  }
@@ -0,0 +1,123 @@
1
+ import type { common } from "@highstate/library"
2
+ import { getOrCreate, z } from "@highstate/contract"
3
+ import {
4
+ ComponentResource,
5
+ type ComponentResourceOptions,
6
+ type Input,
7
+ type InputArray,
8
+ normalizeInputs,
9
+ type Output,
10
+ output,
11
+ Resource,
12
+ } from "@highstate/pulumi"
13
+ import { ImplementationMediator } from "./impl-ref"
14
+
15
+ export const tlsCertificateMediator = new ImplementationMediator(
16
+ "tls-certificate",
17
+ z.object({
18
+ name: z.string(),
19
+ spec: z.custom<TlsCertificateSpec>(),
20
+ opts: z.custom<ComponentResourceOptions>().optional(),
21
+ }),
22
+ z.instanceof(Resource),
23
+ )
24
+
25
+ export type TlsCertificateSpec = {
26
+ /**
27
+ * The common name for the certificate.
28
+ */
29
+ commonName?: Input<string>
30
+
31
+ /**
32
+ * The alternative DNS names for the certificate.
33
+ */
34
+ dnsNames?: InputArray<string>
35
+
36
+ /**
37
+ * The native data to pass to the implementation.
38
+ *
39
+ * This is used for data which implementation may natively understand
40
+ * and may use this data to create certificates using native resources.
41
+ */
42
+ nativeData?: unknown
43
+ }
44
+
45
+ export type TlsCertificateArgs = TlsCertificateSpec & {
46
+ /**
47
+ * The issuer to use for the certificate.
48
+ */
49
+ issuer?: Input<common.TlsIssuer>
50
+
51
+ /**
52
+ * The issuers to use for the certificate.
53
+ *
54
+ * If multiple issuers are provided, the certificate will be created using the first issuer that supports the requested common name.
55
+ */
56
+ issuers?: InputArray<common.TlsIssuer>
57
+ }
58
+
59
+ export class TlsCertificate extends ComponentResource {
60
+ /**
61
+ * The underlying resource created by the implementation.
62
+ */
63
+ readonly resource: Output<Resource>
64
+
65
+ constructor(name: string, args: TlsCertificateArgs, opts?: ComponentResourceOptions) {
66
+ super("highstate:common:TlsCertificate", name, args, opts)
67
+
68
+ const issuers = normalizeInputs(args.issuer, args.issuers)
69
+
70
+ this.resource = output({
71
+ issuers,
72
+ commonName: args.commonName,
73
+ dnsNames: args.dnsNames,
74
+ }).apply(async ({ issuers, commonName, dnsNames }) => {
75
+ // for now, we require single issuer to match all requested names
76
+ const matchedIssuer = issuers.find(issuer => {
77
+ if (commonName && !commonName.endsWith(issuer.domain)) {
78
+ return false
79
+ }
80
+
81
+ if (dnsNames && !dnsNames.every(name => name.endsWith(issuer.domain))) {
82
+ return false
83
+ }
84
+
85
+ return true
86
+ })
87
+
88
+ if (!matchedIssuer) {
89
+ throw new Error(
90
+ `No TLS issuer matched the common name "${commonName}" and DNS names "${dnsNames?.join(", ") ?? ""}"`,
91
+ )
92
+ }
93
+
94
+ return await tlsCertificateMediator.call(matchedIssuer.implRef, {
95
+ name,
96
+ spec: args,
97
+ })
98
+ })
99
+ }
100
+
101
+ private static readonly tlsCertificateCache = new Map<string, TlsCertificate>()
102
+
103
+ /**
104
+ * Creates a TLS certificate for the specified common name and DNS names.
105
+ *
106
+ * If a TLS certificate with the same name already exists, it will be reused.
107
+ *
108
+ * @param name The name of the TLS certificate.
109
+ * @param args The arguments for the TLS certificate.
110
+ * @param opts The options for the resource.
111
+ */
112
+ static createOnce(
113
+ name: string,
114
+ args: TlsCertificateArgs,
115
+ opts?: ComponentResourceOptions,
116
+ ): TlsCertificate {
117
+ return getOrCreate(
118
+ TlsCertificate.tlsCertificateCache,
119
+ name,
120
+ () => new TlsCertificate(name, args, opts),
121
+ )
122
+ }
123
+ }
@@ -0,0 +1,12 @@
1
+ import { common } from "@highstate/library"
2
+ import { forUnit } from "@highstate/pulumi"
3
+
4
+ const { inputs, outputs } = forUnit(common.accessPoint)
5
+
6
+ export default outputs({
7
+ accessPoint: {
8
+ gateway: inputs.gateway,
9
+ tlsIssuers: inputs.tlsIssuers ?? [],
10
+ dnsProviders: inputs.dnsProviders ?? [],
11
+ },
12
+ })
@@ -0,0 +1,14 @@
1
+ import { databases } from "@highstate/library"
2
+ import { forUnit } from "@highstate/pulumi"
3
+ import { parseEndpoints } from "../../../shared"
4
+
5
+ const { args, secrets, inputs, outputs } = forUnit(databases.existingMariadb)
6
+
7
+ export default outputs({
8
+ mariadb: {
9
+ endpoints: parseEndpoints(args.endpoints, inputs.endpoints),
10
+ username: args.username,
11
+ password: secrets.password,
12
+ database: args.database,
13
+ },
14
+ })
@@ -0,0 +1,14 @@
1
+ import { databases } from "@highstate/library"
2
+ import { forUnit } from "@highstate/pulumi"
3
+ import { parseEndpoints } from "../../../shared"
4
+
5
+ const { args, secrets, inputs, outputs } = forUnit(databases.existingMongodb)
6
+
7
+ export default outputs({
8
+ mongodb: {
9
+ endpoints: parseEndpoints(args.endpoints, inputs.endpoints),
10
+ username: args.username,
11
+ password: secrets.password,
12
+ database: args.database,
13
+ },
14
+ })
@@ -0,0 +1,14 @@
1
+ import { databases } from "@highstate/library"
2
+ import { forUnit } from "@highstate/pulumi"
3
+ import { parseEndpoints } from "../../../shared"
4
+
5
+ const { args, secrets, inputs, outputs } = forUnit(databases.existingPostgresql)
6
+
7
+ export default outputs({
8
+ postgresql: {
9
+ endpoints: parseEndpoints(args.endpoints, inputs.endpoints),
10
+ username: args.username,
11
+ password: secrets.password,
12
+ database: args.database,
13
+ },
14
+ })