@highstate/common 0.9.18 → 0.9.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-YYNV3MVT.js → chunk-WDYIUWYZ.js} +484 -176
- package/dist/chunk-WDYIUWYZ.js.map +1 -0
- package/dist/highstate.manifest.json +12 -8
- package/dist/index.js +1 -1
- package/dist/units/access-point/index.js +16 -0
- package/dist/units/access-point/index.js.map +1 -0
- package/dist/units/databases/existing-mariadb/index.js +17 -0
- package/dist/units/databases/existing-mariadb/index.js.map +1 -0
- package/dist/units/databases/existing-mongodb/index.js +17 -0
- package/dist/units/databases/existing-mongodb/index.js.map +1 -0
- package/dist/units/databases/existing-postgresql/index.js +17 -0
- package/dist/units/databases/existing-postgresql/index.js.map +1 -0
- package/dist/units/dns/record-set/index.js +22 -11
- package/dist/units/dns/record-set/index.js.map +1 -1
- package/dist/units/existing-server/index.js +9 -9
- package/dist/units/existing-server/index.js.map +1 -1
- package/dist/units/network/l3-endpoint/index.js +1 -1
- package/dist/units/network/l4-endpoint/index.js +1 -1
- package/dist/units/script/index.js +1 -1
- package/dist/units/server-dns/index.js +1 -1
- package/dist/units/server-patch/index.js +1 -1
- package/dist/units/ssh/key-pair/index.js +4 -3
- package/dist/units/ssh/key-pair/index.js.map +1 -1
- package/package.json +61 -8
- package/src/shared/access-point.ts +110 -0
- package/src/shared/command.ts +81 -14
- package/src/shared/dns.ts +150 -90
- package/src/shared/files.ts +23 -18
- package/src/shared/gateway.ts +117 -0
- package/src/shared/impl-ref.ts +123 -0
- package/src/shared/index.ts +4 -0
- package/src/shared/network.ts +39 -25
- package/src/shared/passwords.ts +3 -3
- package/src/shared/ssh.ts +109 -124
- package/src/shared/tls.ts +123 -0
- package/src/units/access-point/index.ts +12 -0
- package/src/units/databases/existing-mariadb/index.ts +14 -0
- package/src/units/databases/existing-mongodb/index.ts +14 -0
- package/src/units/databases/existing-postgresql/index.ts +14 -0
- package/src/units/dns/record-set/index.ts +21 -11
- package/src/units/existing-server/index.ts +9 -15
- package/src/units/ssh/key-pair/index.ts +4 -3
- package/dist/chunk-YYNV3MVT.js.map +0 -1
package/src/shared/network.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import type { ArrayPatchMode, network } from "@highstate/library"
|
2
|
-
import {
|
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<
|
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?:
|
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
|
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<
|
376
|
-
currentEndpoints: Input<
|
377
|
-
endpoints: string[],
|
378
|
-
inputEndpoints: Input<
|
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<
|
382
|
+
): Promise<TEndpoints[]> {
|
381
383
|
const resolvedCurrentEndpoints = await toPromise(currentEndpoints)
|
382
|
-
const
|
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
|
387
|
+
return newEndpoints as TEndpoints[]
|
392
388
|
}
|
393
389
|
|
394
390
|
return uniqueBy(
|
395
|
-
//
|
396
391
|
[...newEndpoints, ...resolvedCurrentEndpoints],
|
397
|
-
|
398
|
-
) as
|
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
|
}
|
package/src/shared/passwords.ts
CHANGED
@@ -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
|
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
|
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
|
1
|
+
import { stripNullish, type UnitTerminal } from "@highstate/contract"
|
2
|
+
import type { ssh, common, network } from "@highstate/library"
|
2
3
|
import {
|
3
|
-
|
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.
|
21
|
-
): Output<
|
22
|
-
|
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
|
-
|
23
|
+
const command = ["ssh", "-tt", "-o", "UserKnownHostsFile=/known_hosts"]
|
28
24
|
|
29
|
-
|
30
|
-
|
25
|
+
// TODO: select best endpoint based on the environment
|
26
|
+
const endpoint = resolvedCredentials.endpoints[0]
|
31
27
|
|
32
|
-
|
28
|
+
command.push("-p", endpoint.port.toString())
|
33
29
|
|
34
|
-
|
35
|
-
|
36
|
-
|
30
|
+
if (resolvedCredentials.keyPair) {
|
31
|
+
command.push("-i", "/private_key")
|
32
|
+
}
|
37
33
|
|
38
|
-
|
34
|
+
command.push(`${resolvedCredentials.user}@${l3EndpointToString(endpoint)}`)
|
39
35
|
|
40
|
-
|
41
|
-
|
42
|
-
|
36
|
+
if (resolvedCredentials.password) {
|
37
|
+
command.unshift("sshpass", "-f", "/password")
|
38
|
+
}
|
43
39
|
|
44
|
-
|
45
|
-
|
40
|
+
return output({
|
41
|
+
name: "ssh",
|
46
42
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
43
|
+
meta: {
|
44
|
+
title: "Shell",
|
45
|
+
description: "Connect to the server via SSH.",
|
46
|
+
icon: "gg:remote",
|
47
|
+
},
|
52
48
|
|
53
|
-
|
54
|
-
|
55
|
-
|
49
|
+
spec: {
|
50
|
+
image: images["terminal-ssh"].image,
|
51
|
+
command,
|
56
52
|
|
57
|
-
|
58
|
-
|
53
|
+
files: stripNullish({
|
54
|
+
"/password": resolvedCredentials.password
|
55
|
+
? fileFromString("password", resolvedCredentials.password, { isSecret: true })
|
56
|
+
: undefined,
|
59
57
|
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
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
|
-
|
132
|
+
sshArgs?: Partial<ssh.Args>
|
178
133
|
|
179
134
|
/**
|
180
|
-
* The
|
135
|
+
* The password for the SSH connection.
|
181
136
|
*/
|
182
|
-
sshPassword?: Input<string
|
137
|
+
sshPassword?: Input<string>
|
183
138
|
|
184
139
|
/**
|
185
|
-
* The
|
140
|
+
* The private key for the SSH connection.
|
186
141
|
*/
|
187
142
|
sshPrivateKey?: Input<string>
|
188
143
|
|
189
144
|
/**
|
190
|
-
*
|
191
|
-
* If
|
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
|
-
|
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 `
|
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
|
-
|
260
|
-
sshPort = 22,
|
261
|
-
sshUser = "root",
|
242
|
+
sshArgs = { enabled: true, port: 22, user: "root" },
|
262
243
|
sshPassword,
|
263
244
|
sshPrivateKey,
|
264
|
-
|
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 ??=
|
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 (!
|
291
|
-
return {
|
271
|
+
if (!sshArgs.enabled) {
|
272
|
+
return output({
|
292
273
|
hostname: name,
|
293
274
|
endpoints,
|
294
|
-
}
|
275
|
+
})
|
295
276
|
}
|
296
277
|
|
297
|
-
|
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 ${
|
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:
|
311
|
-
port:
|
312
|
-
user:
|
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
|
311
|
+
return output({
|
331
312
|
endpoints,
|
332
313
|
hostname: hostnameResult.stdout.apply(x => x.trim()),
|
333
314
|
ssh: {
|
334
|
-
endpoints: [
|
335
|
-
user:
|
315
|
+
endpoints: [l3EndpointToL4(sshHost, sshArgs.port ?? 22)],
|
316
|
+
user: sshArgs.user ?? "root",
|
336
317
|
hostKey: hostKeyResult.stdout.apply(x => x.trim()),
|
337
|
-
password:
|
338
|
-
keyPair:
|
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
|
+
})
|