@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
package/src/shared/ssh.ts CHANGED
@@ -1,93 +1,96 @@
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
- getOrCreateSecret,
4
+ fileFromString,
4
5
  output,
5
- Output,
6
+ type Output,
6
7
  secret,
8
+ toPromise,
7
9
  type Input,
8
- type InstanceTerminal,
9
10
  } from "@highstate/pulumi"
10
11
  import getKeys, { PrivateExport } from "micro-key-producer/ssh.js"
11
12
  import { randomBytes } from "micro-key-producer/utils.js"
12
- import { local, remote } from "@pulumi/command"
13
+ import { remote } from "@pulumi/command"
13
14
  import * as images from "../../assets/images.json"
14
- import { l3EndpointToString, l3ToL4Endpoint } from "./network"
15
+ import { l3EndpointToString, l3EndpointToL4 } from "./network"
16
+ import { Command } from "./command"
15
17
 
16
- export function createSshTerminal(
17
- credentials: Input<ssh.Credentials | undefined>,
18
- ): Output<InstanceTerminal | undefined> {
19
- return output(credentials).apply(credentials => {
20
- if (!credentials) {
21
- return undefined
22
- }
18
+ export async function createSshTerminal(
19
+ credentials: Input<ssh.Connection>,
20
+ ): Promise<Output<UnitTerminal>> {
21
+ const resolvedCredentials = await toPromise(credentials)
23
22
 
24
- const command = ["ssh", "-tt", "-o", "UserKnownHostsFile=/known_hosts"]
23
+ const command = ["ssh", "-tt", "-o", "UserKnownHostsFile=/known_hosts"]
25
24
 
26
- // TODO: select best endpoint based on the environment
27
- const endpoint = credentials.endpoints[0]
25
+ // TODO: select best endpoint based on the environment
26
+ const endpoint = resolvedCredentials.endpoints[0]
28
27
 
29
- command.push("-p", endpoint.port.toString())
28
+ command.push("-p", endpoint.port.toString())
30
29
 
31
- if (credentials.keyPair) {
32
- command.push("-i", "/private_key")
33
- }
30
+ if (resolvedCredentials.keyPair) {
31
+ command.push("-i", "/private_key")
32
+ }
34
33
 
35
- command.push(`${credentials.user}@${l3EndpointToString(endpoint)}`)
34
+ command.push(`${resolvedCredentials.user}@${l3EndpointToString(endpoint)}`)
36
35
 
37
- if (credentials.password) {
38
- command.unshift("sshpass", "-f", "/password")
39
- }
36
+ if (resolvedCredentials.password) {
37
+ command.unshift("sshpass", "-f", "/password")
38
+ }
40
39
 
41
- return {
42
- name: "ssh",
40
+ return output({
41
+ name: "ssh",
43
42
 
44
- meta: {
45
- title: "Shell",
46
- description: "Connect to the server via SSH",
47
- icon: "gg:remote",
48
- },
43
+ meta: {
44
+ title: "Shell",
45
+ description: "Connect to the server via SSH.",
46
+ icon: "gg:remote",
47
+ },
49
48
 
50
- spec: {
51
- image: images["terminal-ssh"].image,
52
- command,
49
+ spec: {
50
+ image: images["terminal-ssh"].image,
51
+ command,
53
52
 
54
- files: {
55
- "/password": credentials.password,
53
+ files: stripNullish({
54
+ "/password": resolvedCredentials.password
55
+ ? fileFromString("password", resolvedCredentials.password, { isSecret: true })
56
+ : undefined,
56
57
 
57
- "/private_key": credentials.keyPair?.privateKey && {
58
- content: {
59
- type: "embedded",
60
- value: credentials.keyPair?.privateKey,
61
- },
62
- meta: {
63
- name: "private_key",
58
+ "/private_key": resolvedCredentials.keyPair?.privateKey
59
+ ? fileFromString("private_key", resolvedCredentials.keyPair.privateKey, {
60
+ isSecret: true,
64
61
  mode: 0o600,
65
- },
66
- },
67
-
68
- "/known_hosts": {
69
- content: {
70
- type: "embedded",
71
- value: `${l3EndpointToString(endpoint)} ${credentials.hostKey}`,
72
- },
73
- meta: {
74
- name: "known_hosts",
75
- mode: 0o644,
76
- },
77
- },
78
- },
79
- },
80
- } satisfies InstanceTerminal
62
+ })
63
+ : undefined,
64
+
65
+ "/known_hosts": fileFromString(
66
+ "known_hosts",
67
+ `${l3EndpointToString(endpoint)} ${resolvedCredentials.hostKey}`,
68
+ { mode: 0o644 },
69
+ ),
70
+ }),
71
+ },
81
72
  })
82
73
  }
83
74
 
84
- export function generatePrivateKey(): string {
75
+ /**
76
+ * Generates a secure random SSH private key.
77
+ * The key is generated using the Ed25519 algorithm.
78
+ *
79
+ * @returns The generated SSH private key in PEM format.
80
+ */
81
+ export function generateSshPrivateKey(): Output<string> {
85
82
  const seed = randomBytes(32)
86
83
 
87
- return getKeys(seed).privateKey
84
+ return secret(getKeys(seed).privateKey)
88
85
  }
89
86
 
90
- export function privateKeyToKeyPair(privateKeyString: Input<string>): Output<ssh.KeyPair> {
87
+ /**
88
+ * Converts a private SSH key string to a KeyPair object.
89
+ *
90
+ * @param privateKeyString The private key string to convert.
91
+ * @returns An Output of the KeyPair object.
92
+ */
93
+ export function sshPrivateKeyToKeyPair(privateKeyString: Input<string>): Output<ssh.KeyPair> {
91
94
  return output(privateKeyString).apply(privateKeyString => {
92
95
  const privateKeyStruct = PrivateExport.decode(privateKeyString)
93
96
 
@@ -105,87 +108,219 @@ export function privateKeyToKeyPair(privateKeyString: Input<string>): Output<ssh
105
108
  })
106
109
  }
107
110
 
108
- export type SshKeyPairInputs = {
111
+ export type ServerOptions = {
112
+ /**
113
+ * The local name of the server to namespace resources.
114
+ */
115
+ name: string
116
+
117
+ /**
118
+ * The fallback hostname to use if the server cannot be determined.
119
+ *
120
+ * If not provided, the `name` will be used as the fallback hostname.
121
+ */
122
+ fallbackHostname?: string
123
+
124
+ /**
125
+ * The L3 endpoints of the server.
126
+ */
127
+ endpoints: network.L3Endpoint[]
128
+
129
+ /**
130
+ * The arguments for the SSH connection.
131
+ */
132
+ sshArgs?: Partial<ssh.Args>
133
+
134
+ /**
135
+ * The password for the SSH connection.
136
+ */
137
+ sshPassword?: Input<string>
138
+
139
+ /**
140
+ * The private key for the SSH connection.
141
+ */
142
+ sshPrivateKey?: Input<string>
143
+
144
+ /**
145
+ * The SSH key pair for the server.
146
+ * If provided, it will take precedence over the `sshPrivateKey` argument.
147
+ */
109
148
  sshKeyPair?: Input<ssh.KeyPair>
149
+
150
+ /**
151
+ * Whether to wait for the server to respond to a ping command before returning.
152
+ *
153
+ * If true, the command will wait for a successful ping response before proceeding.
154
+ *
155
+ * By default, this is equal to `!waitForSsh`, so when `waitForSsh` is true, no extra ping is performed.
156
+ */
157
+ waitForPing?: boolean
158
+
159
+ /**
160
+ * The interval in seconds to wait between ping attempts.
161
+ *
162
+ * Only used if `waitForPing` is true.
163
+ * By default, it will wait 5 seconds between attempts.
164
+ */
165
+ pingInterval?: number
166
+
167
+ /**
168
+ * The timeout in seconds to wait for the server to respond to a ping command.
169
+ *
170
+ * Only used if `waitForPing` is true.
171
+ * By default, it will wait 5 minutes (300 seconds) before timing out.
172
+ */
173
+ pingTimeout?: number
174
+
175
+ /**
176
+ * Whether to wait for the SSH service to be available before returning.
177
+ *
178
+ * If true, the command will wait for the SSH service to respond before proceeding.
179
+ *
180
+ * By default, this is true if `sshArgs.enabled` is true, otherwise false.
181
+ */
182
+ waitForSsh?: boolean
183
+
184
+ /**
185
+ * The interval in seconds to wait between SSH connection attempts.
186
+ *
187
+ * Only used if `waitForSsh` is true.
188
+ * By default, it will wait 5 seconds between attempts.
189
+ */
190
+ sshCheckInterval?: number
191
+
192
+ /**
193
+ * The timeout in seconds to wait for the SSH service to respond.
194
+ *
195
+ * Only used if `waitForSsh` is true.
196
+ * By default, it will wait 5 minutes (300 seconds) before timing out.
197
+ */
198
+ sshCheckTimeout?: number
110
199
  }
111
200
 
112
- export type SshKeyPairSecrets = {
113
- sshPrivateKey?: Input<string>
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>
114
211
  }
115
212
 
116
- export function getOrCreateSshKeyPair(
117
- inputs: SshKeyPairInputs,
118
- secrets: Output<SshKeyPairSecrets>,
119
- ): Output<ssh.KeyPair> {
120
- if (inputs.sshKeyPair) {
121
- return output(inputs.sshKeyPair)
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,
122
228
  }
229
+ }
123
230
 
124
- const privateKey = getOrCreateSecret(secrets, "sshPrivateKey", generatePrivateKey)
231
+ /**
232
+ * Creates a server entity with the provided options.
233
+ * It will create a command to check the SSH service and return the server entity.
234
+ *
235
+ * @param options The options for creating the server entity.
236
+ * @returns A promise that resolves to the created server entity.
237
+ */
238
+ export async function createServerEntity({
239
+ name,
240
+ fallbackHostname,
241
+ endpoints,
242
+ sshArgs = { enabled: true, port: 22, user: "root" },
243
+ sshPassword,
244
+ sshPrivateKey,
245
+ sshKeyPair,
246
+ pingInterval,
247
+ pingTimeout,
248
+ waitForPing,
249
+ waitForSsh,
250
+ sshCheckInterval,
251
+ sshCheckTimeout,
252
+ }: ServerOptions): Promise<Output<common.Server>> {
253
+ if (endpoints.length === 0) {
254
+ throw new Error("At least one L3 endpoint is required to create a server entity")
255
+ }
125
256
 
126
- return privateKey.apply(privateKeyToKeyPair)
127
- }
257
+ fallbackHostname ??= name
258
+ waitForSsh ??= sshArgs.enabled
259
+ waitForPing ??= !waitForSsh
128
260
 
129
- export function createServerEntity(
130
- fallbackHostname: string,
131
- endpoint: network.L3Endpoint,
132
- sshPort = 22,
133
- sshUser = "root",
134
- sshPassword?: Input<string | undefined>,
135
- sshPrivateKey?: Input<string>,
136
- hasSsh = true,
137
- ): Output<common.Server> {
138
- const connection = output({
139
- host: l3EndpointToString(endpoint),
140
- port: sshPort,
141
- user: sshUser,
142
- password: sshPassword,
143
- privateKey: sshPrivateKey,
144
- dialErrorLimit: 3,
145
- })
261
+ if (waitForPing) {
262
+ await Command.waitFor(`${name}.ping`, {
263
+ host: "local",
264
+ create: `ping -c 1 ${l3EndpointToString(endpoints[0])}`,
265
+ timeout: pingTimeout ?? 300,
266
+ interval: pingInterval ?? 5,
267
+ triggers: [Date.now()],
268
+ }).wait()
269
+ }
146
270
 
147
- if (!hasSsh) {
271
+ if (!sshArgs.enabled) {
148
272
  return output({
149
- hostname: fallbackHostname,
150
- endpoints: [endpoint],
273
+ hostname: name,
274
+ endpoints,
151
275
  })
152
276
  }
153
277
 
154
- const command = new local.Command("check-ssh", {
155
- create: `nc -zv ${l3EndpointToString(endpoint)} ${sshPort} && echo "up" || echo "down"`,
156
- triggers: [Date.now()],
157
- })
278
+ const sshHost = sshArgs?.host ?? l3EndpointToString(endpoints[0])
158
279
 
159
- return command.stdout.apply(result => {
160
- if (result === "down") {
161
- return output({
162
- hostname: fallbackHostname,
163
- endpoints: [endpoint],
164
- })
165
- }
166
-
167
- const hostnameResult = new remote.Command("hostname", {
168
- connection,
169
- create: "hostname",
280
+ if (waitForSsh) {
281
+ await Command.waitFor(`${name}.ssh`, {
282
+ host: "local",
283
+ create: `nc -zv ${sshHost} ${sshArgs.port}`,
284
+ timeout: sshCheckTimeout ?? 300,
285
+ interval: sshCheckInterval ?? 5,
170
286
  triggers: [Date.now()],
171
- })
287
+ }).wait()
288
+ }
172
289
 
173
- const hostKeyResult = new remote.Command("host-key", {
174
- connection,
175
- create: "cat /etc/ssh/ssh_host_ed25519_key.pub",
176
- triggers: [Date.now()],
177
- })
290
+ const connection = output({
291
+ host: sshHost,
292
+ port: sshArgs.port,
293
+ user: sshArgs.user,
294
+ password: sshPassword,
295
+ privateKey: sshKeyPair ? output(sshKeyPair).privateKey : sshPrivateKey,
296
+ dialErrorLimit: 3,
297
+ })
178
298
 
179
- return output({
180
- endpoints: [endpoint],
181
- hostname: hostnameResult.stdout.apply(x => x.trim()),
182
- ssh: {
183
- endpoints: [l3ToL4Endpoint(endpoint, sshPort)],
184
- user: sshUser,
185
- hostKey: hostKeyResult.stdout.apply(x => x.trim()),
186
- password: sshPassword,
187
- keyPair: sshPrivateKey ? privateKeyToKeyPair(sshPrivateKey) : undefined,
188
- },
189
- })
299
+ const hostnameResult = new remote.Command("hostname", {
300
+ connection,
301
+ create: "hostname",
302
+ triggers: [Date.now()],
303
+ })
304
+
305
+ const hostKeyResult = new remote.Command("host-key", {
306
+ connection,
307
+ create: "cat /etc/ssh/ssh_host_ed25519_key.pub",
308
+ triggers: [Date.now()],
309
+ })
310
+
311
+ return output({
312
+ endpoints,
313
+ hostname: hostnameResult.stdout.apply(x => x.trim()),
314
+ ssh: {
315
+ endpoints: [l3EndpointToL4(sshHost, sshArgs.port ?? 22)],
316
+ user: sshArgs.user ?? "root",
317
+ hostKey: hostKeyResult.stdout.apply(x => x.trim()),
318
+ password: connection.password,
319
+ keyPair: sshKeyPair
320
+ ? sshKeyPair
321
+ : sshPrivateKey
322
+ ? sshPrivateKeyToKeyPair(sshPrivateKey)
323
+ : undefined,
324
+ },
190
325
  })
191
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
+ })
@@ -1,16 +1,26 @@
1
1
  import { dns } from "@highstate/library"
2
- import { forUnit, toPromise } from "@highstate/pulumi"
3
- import { DnsRecordSet } from "../../../shared"
2
+ import { forUnit } from "@highstate/pulumi"
3
+ import { updateEndpointsWithFqdn } from "../../../shared"
4
4
 
5
- const { name, args, inputs } = forUnit(dns.recordSet)
5
+ const { args, inputs, outputs } = forUnit(dns.recordSet)
6
6
 
7
- const endpoints = await toPromise(inputs.endpoints)
7
+ const { endpoints: l3Endpoints } = await updateEndpointsWithFqdn(
8
+ inputs.l3Endpoints ?? [],
9
+ args.fqdn,
10
+ args.endpointFilter,
11
+ args.patchMode,
12
+ inputs.dnsProviders,
13
+ )
8
14
 
9
- DnsRecordSet.create(name, {
10
- type: args.type,
11
- providers: inputs.dnsProviders,
12
- values: [...args.values, ...endpoints],
13
- ttl: args.ttl,
14
- priority: args.priority,
15
- proxied: args.proxied,
15
+ const { endpoints: l4Endpoints } = await updateEndpointsWithFqdn(
16
+ inputs.l4Endpoints ?? [],
17
+ args.fqdn,
18
+ args.endpointFilter,
19
+ args.patchMode,
20
+ inputs.dnsProviders,
21
+ )
22
+
23
+ export default outputs({
24
+ l3Endpoints,
25
+ l4Endpoints,
16
26
  })