@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.
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 +1 @@
1
- {"version":3,"sources":["../../../../src/units/ssh/key-pair/index.ts"],"names":[],"mappings":";;;;AAIA,IAAM,EAAE,IAAA,EAAM,OAAA,EAAS,SAAQ,GAAI,OAAA,CAAQ,IAAI,OAAO,CAAA;AAEtD,IAAM,OAAA,GAAU,gBAAA,CAAiB,OAAA,CAAQ,UAAU,CAAA;AAEnD,IAAO,mBAAQ,OAAA,CAAQ;AAAA,EACrB,OAAA;AAAA,EACA,aAAA,EAAe;AAAA,IACb,IAAA,EAAM;AAAA,MACJ,IAAA,EAAM,GAAG,IAAI,CAAA,IAAA,CAAA;AAAA,MACb,IAAA,EAAM;AAAA,KACR;AAAA,IACA,OAAA,EAAS;AAAA,MACP,IAAA,EAAM,UAAA;AAAA,MACN,OAAO,OAAA,CAAQ;AAAA;AACjB,GACF;AAAA,EACA,aAAA,EAAe;AAAA,IACb,aAAa,OAAA,CAAQ,WAAA;AAAA,IACrB,WAAW,OAAA,CAAQ;AAAA;AAEvB,CAAC","file":"index.js","sourcesContent":["import { ssh } from \"@highstate/library\"\nimport { forUnit } from \"@highstate/pulumi\"\nimport { ensureSshKeyPair } from \"../../../shared\"\n\nconst { name, secrets, outputs } = forUnit(ssh.keyPair)\n\nconst keyPair = ensureSshKeyPair(secrets.privateKey)\n\nexport default outputs({\n keyPair,\n publicKeyFile: {\n meta: {\n name: `${name}.pub`,\n mode: 0o644,\n },\n content: {\n type: \"embedded\",\n value: keyPair.publicKey,\n },\n },\n $statusFields: {\n fingerprint: keyPair.fingerprint,\n publicKey: keyPair.publicKey,\n },\n})\n"]}
1
+ {"version":3,"sources":["../../../../src/units/ssh/key-pair/index.ts"],"names":[],"mappings":";;;;AAIA,IAAM,EAAE,IAAA,EAAM,SAAA,EAAW,SAAQ,GAAI,OAAA,CAAQ,IAAI,OAAO,CAAA;AAExD,IAAM,UAAA,GAAa,SAAA,CAAU,YAAA,EAAc,qBAAqB,CAAA;AAChE,IAAM,OAAA,GAAU,uBAAuB,UAAU,CAAA;AAEjD,IAAO,mBAAQ,OAAA,CAAQ;AAAA,EACrB,OAAA;AAAA,EACA,aAAA,EAAe;AAAA,IACb,IAAA,EAAM;AAAA,MACJ,IAAA,EAAM,GAAG,IAAI,CAAA,IAAA,CAAA;AAAA,MACb,IAAA,EAAM;AAAA,KACR;AAAA,IACA,OAAA,EAAS;AAAA,MACP,IAAA,EAAM,UAAA;AAAA,MACN,OAAO,OAAA,CAAQ;AAAA;AACjB,GACF;AAAA,EACA,aAAA,EAAe;AAAA,IACb,aAAa,OAAA,CAAQ,WAAA;AAAA,IACrB,WAAW,OAAA,CAAQ;AAAA;AAEvB,CAAC","file":"index.js","sourcesContent":["import { ssh } from \"@highstate/library\"\nimport { forUnit } from \"@highstate/pulumi\"\nimport { generateSshPrivateKey, sshPrivateKeyToKeyPair } from \"../../../shared\"\n\nconst { name, getSecret, outputs } = forUnit(ssh.keyPair)\n\nconst privateKey = getSecret(\"privateKey\", generateSshPrivateKey)\nconst keyPair = sshPrivateKeyToKeyPair(privateKey)\n\nexport default outputs({\n keyPair,\n publicKeyFile: {\n meta: {\n name: `${name}.pub`,\n mode: 0o644,\n },\n content: {\n type: \"embedded\",\n value: keyPair.publicKey,\n },\n },\n $statusFields: {\n fingerprint: keyPair.fingerprint,\n publicKey: keyPair.publicKey,\n },\n})\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@highstate/common",
3
- "version": "0.9.18",
3
+ "version": "0.9.20",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -11,6 +11,54 @@
11
11
  ".": {
12
12
  "mode": "manual",
13
13
  "version": "1"
14
+ },
15
+ "./units/access-point": {
16
+ "mode": "manual",
17
+ "version": "1"
18
+ },
19
+ "./units/dns/record-set": {
20
+ "mode": "manual",
21
+ "version": "1"
22
+ },
23
+ "./units/network/l3-endpoint": {
24
+ "mode": "manual",
25
+ "version": "1"
26
+ },
27
+ "./units/network/l4-endpoint": {
28
+ "mode": "manual",
29
+ "version": "1"
30
+ },
31
+ "./units/existing-server": {
32
+ "mode": "manual",
33
+ "version": "1"
34
+ },
35
+ "./units/ssh/key-pair": {
36
+ "mode": "manual",
37
+ "version": "1"
38
+ },
39
+ "./units/script": {
40
+ "mode": "manual",
41
+ "version": "1"
42
+ },
43
+ "./units/server-dns": {
44
+ "mode": "manual",
45
+ "version": "1"
46
+ },
47
+ "./units/server-patch": {
48
+ "mode": "manual",
49
+ "version": "1"
50
+ },
51
+ "./units/databases/existing-mariadb": {
52
+ "mode": "manual",
53
+ "version": "1"
54
+ },
55
+ "./units/databases/existing-postgresql": {
56
+ "mode": "manual",
57
+ "version": "1"
58
+ },
59
+ "./units/databases/existing-mongodb": {
60
+ "mode": "manual",
61
+ "version": "1"
14
62
  }
15
63
  }
16
64
  },
@@ -19,6 +67,7 @@
19
67
  "types": "./src/index.ts",
20
68
  "default": "./dist/index.js"
21
69
  },
70
+ "./units/access-point": "./dist/units/access-point/index.js",
22
71
  "./units/dns/record-set": "./dist/units/dns/record-set/index.js",
23
72
  "./units/network/l3-endpoint": "./dist/units/network/l3-endpoint/index.js",
24
73
  "./units/network/l4-endpoint": "./dist/units/network/l4-endpoint/index.js",
@@ -26,7 +75,10 @@
26
75
  "./units/ssh/key-pair": "./dist/units/ssh/key-pair/index.js",
27
76
  "./units/script": "./dist/units/script/index.js",
28
77
  "./units/server-dns": "./dist/units/server-dns/index.js",
29
- "./units/server-patch": "./dist/units/server-patch/index.js"
78
+ "./units/server-patch": "./dist/units/server-patch/index.js",
79
+ "./units/databases/existing-mariadb": "./dist/units/databases/existing-mariadb/index.js",
80
+ "./units/databases/existing-postgresql": "./dist/units/databases/existing-postgresql/index.js",
81
+ "./units/databases/existing-mongodb": "./dist/units/databases/existing-mongodb/index.js"
30
82
  },
31
83
  "publishConfig": {
32
84
  "access": "public"
@@ -36,9 +88,9 @@
36
88
  "update-images": "../../scripts/update-images.sh ./assets/images.json"
37
89
  },
38
90
  "dependencies": {
39
- "@highstate/contract": "^0.9.18",
40
- "@highstate/library": "^0.9.18",
41
- "@highstate/pulumi": "^0.9.18",
91
+ "@highstate/contract": "^0.9.20",
92
+ "@highstate/library": "^0.9.20",
93
+ "@highstate/pulumi": "^0.9.20",
42
94
  "@noble/hashes": "^1.7.1",
43
95
  "@pulumi/command": "^1.0.2",
44
96
  "micro-key-producer": "^0.7.3",
@@ -48,9 +100,10 @@
48
100
  "unzipper": "^0.12.3"
49
101
  },
50
102
  "devDependencies": {
51
- "@highstate/cli": "^0.9.18",
103
+ "@highstate/cli": "^0.9.20",
52
104
  "@types/tar": "^6.1.13",
53
- "@types/unzipper": "^0.10.11"
105
+ "@types/unzipper": "^0.10.11",
106
+ "type-fest": "^4.41.0"
54
107
  },
55
- "gitHead": "9ebcd7da56b00b8ca08bf52cc8438f527338cd64"
108
+ "gitHead": "4bf9183450c2c6f51d6a99d77efc379ff5c7b7ef"
56
109
  }
@@ -0,0 +1,110 @@
1
+ import type { common } from "@highstate/library"
2
+ import type { Except } from "type-fest"
3
+ import {
4
+ ComponentResource,
5
+ type ComponentResourceOptions,
6
+ type Input,
7
+ type Output,
8
+ output,
9
+ toPromise,
10
+ } from "@highstate/pulumi"
11
+ import { DnsRecordSet } from "./dns"
12
+ import { GatewayRoute, type GatewayRouteSpec } from "./gateway"
13
+ import { TlsCertificate } from "./tls"
14
+
15
+ export type AccessPointRouteArgs = Except<GatewayRouteSpec, "nativeData"> & {
16
+ /**
17
+ * The access point to use to expose the route.
18
+ */
19
+ accessPoint: Input<common.AccessPoint>
20
+
21
+ /**
22
+ * The native data to pass to the gateway route implementation.
23
+ */
24
+ gatewayNativeData?: unknown
25
+
26
+ /**
27
+ * The native data to pass to the tls ceertificate implementation.
28
+ */
29
+ tlsCertificateNativeData?: unknown
30
+ }
31
+
32
+ export class AccessPointRoute extends ComponentResource {
33
+ /**
34
+ * The created gateway route.
35
+ */
36
+ readonly route: GatewayRoute
37
+
38
+ /**
39
+ * The DNS record set created for the route.
40
+ *
41
+ * May be shared between multiple routes with the same FQDN.
42
+ */
43
+ readonly dnsRecordSet?: Output<DnsRecordSet | undefined>
44
+
45
+ /**
46
+ * The TLS certificate created for the route.
47
+ *
48
+ * May be shared between multiple routes with the same FQDN.
49
+ */
50
+ readonly tlsCertificate?: Output<TlsCertificate | undefined>
51
+
52
+ constructor(name: string, args: AccessPointRouteArgs, opts?: ComponentResourceOptions) {
53
+ super("highstate:common:AccessPointRoute", name, args, opts)
54
+
55
+ // 1. create TLS certificate if the route is HTTPS and the access point has TLS issuers
56
+ if (args.fqdn && args.type === "http" && !args.insecure) {
57
+ this.tlsCertificate = output(args.accessPoint).apply(accessPoint => {
58
+ if (accessPoint.tlsIssuers.length === 0) {
59
+ return undefined
60
+ }
61
+
62
+ return TlsCertificate.createOnce(
63
+ name,
64
+ {
65
+ issuers: accessPoint.tlsIssuers,
66
+ dnsNames: args.fqdn ? [args.fqdn] : [],
67
+ nativeData: args.tlsCertificateNativeData,
68
+ },
69
+ { ...opts, parent: this },
70
+ )
71
+ })
72
+ }
73
+
74
+ // 2. create the route and resolve the gateway endpoints
75
+ this.route = new GatewayRoute(
76
+ name,
77
+ {
78
+ ...args,
79
+ gateway: output(args.accessPoint).gateway,
80
+ tlsCertificate: this.tlsCertificate,
81
+ nativeData: args.gatewayNativeData,
82
+ },
83
+ { ...opts, parent: this },
84
+ )
85
+
86
+ // 3. register DNS records if FQDN is provided and the access point has DNS providers
87
+ if (args.fqdn) {
88
+ this.dnsRecordSet = output(args.accessPoint).apply(async accessPoint => {
89
+ if (accessPoint.dnsProviders.length === 0) {
90
+ return undefined
91
+ }
92
+
93
+ const fqdn = await toPromise(args.fqdn)
94
+ if (!fqdn) {
95
+ return undefined
96
+ }
97
+
98
+ return DnsRecordSet.createOnce(
99
+ fqdn,
100
+ {
101
+ providers: output(args.accessPoint).dnsProviders,
102
+ values: this.route.endpoints,
103
+ waitAt: "local",
104
+ },
105
+ { ...opts, parent: this },
106
+ )
107
+ })
108
+ }
109
+ }
110
+ }
@@ -7,13 +7,13 @@ import {
7
7
  toPromise,
8
8
  type ComponentResourceOptions,
9
9
  type Input,
10
- type InputMap,
11
10
  type InputOrArray,
12
11
  type Output,
13
12
  } from "@highstate/pulumi"
14
- import { common, ssh } from "@highstate/library"
13
+ import type { common, ssh } from "@highstate/library"
15
14
  import { flat } from "remeda"
16
15
  import { l3EndpointToString } from "./network"
16
+ import { sha256 } from "@noble/hashes/sha2"
17
17
 
18
18
  /**
19
19
  * Creates a connection object for the given SSH credentials.
@@ -22,7 +22,7 @@ import { l3EndpointToString } from "./network"
22
22
  * @returns An output connection object for Pulumi remote commands.
23
23
  */
24
24
  export function getServerConnection(
25
- ssh: Input<ssh.Credentials>,
25
+ ssh: Input<ssh.Connection>,
26
26
  ): Output<types.input.remote.ConnectionArgs> {
27
27
  return output(ssh).apply(ssh => ({
28
28
  host: l3EndpointToString(ssh.endpoints[0]),
@@ -35,7 +35,7 @@ export function getServerConnection(
35
35
  }))
36
36
  }
37
37
 
38
- export type CommandHost = "local" | Input<common.Server> | Input<types.input.remote.ConnectionArgs>
38
+ export type CommandHost = "local" | Input<common.Server>
39
39
  export type CommandRunMode = "auto" | "prefer-host"
40
40
 
41
41
  export type CommandArgs = {
@@ -83,6 +83,16 @@ export type CommandArgs = {
83
83
  */
84
84
  triggers?: InputOrArray<unknown>
85
85
 
86
+ /**
87
+ * The update triggers for the command.
88
+ *
89
+ * Unlike `triggers`, which replace the entire resource on change, these will only trigger the `update` command.
90
+ *
91
+ * Under the hood, it is implemented using a hash of the provided values and passing it as environment variable.
92
+ * It is recommended to pass only primitive values (strings, numbers, booleans) or small objects/arrays for proper serialization.
93
+ */
94
+ updateTriggers?: InputOrArray<unknown>
95
+
86
96
  /**
87
97
  * The working directory for the command.
88
98
  *
@@ -93,7 +103,7 @@ export type CommandArgs = {
93
103
  /**
94
104
  * The environment variables to set for the command.
95
105
  */
96
- environment?: Input<InputMap<string>>
106
+ environment?: Input<Record<string, Input<string>>>
97
107
 
98
108
  /**
99
109
  * The run mode for the command.
@@ -168,12 +178,50 @@ function wrapWithWorkDir(dir?: Input<string>) {
168
178
  return (command: string) => interpolate`cd "${dir}" && ${command}`
169
179
  }
170
180
 
181
+ function wrapWithEnvironment(environment?: Input<Record<string, Input<string>>>) {
182
+ if (!environment) {
183
+ return (command: string) => output(command)
184
+ }
185
+
186
+ return (command: string) =>
187
+ output({ command, environment }).apply(({ command, environment }) => {
188
+ if (!environment || Object.keys(environment).length === 0) {
189
+ return command
190
+ }
191
+
192
+ const envExport = Object.entries(environment)
193
+ .map(([key, value]) => `export ${key}="${value}"`)
194
+ .join(" && ")
195
+
196
+ return `${envExport} && ${command}`
197
+ })
198
+ }
199
+
171
200
  function wrapWithWaitFor(timeout: Input<number> = 300, interval: Input<number> = 5) {
172
201
  return (command: string | string[]) =>
173
202
  // TOD: escape the command
174
203
  interpolate`timeout ${timeout} bash -c 'while ! ${createCommand(command)}; do sleep ${interval}; done'`
175
204
  }
176
205
 
206
+ function applyUpdateTriggers(
207
+ env: Input<Record<string, Input<string>>> | undefined,
208
+ triggers: InputOrArray<unknown> | undefined,
209
+ ) {
210
+ return output({ env, triggers }).apply(({ env, triggers }) => {
211
+ if (!triggers) {
212
+ return env
213
+ }
214
+
215
+ const hash = sha256(JSON.stringify(triggers))
216
+ const hashHex = Buffer.from(hash).toString("hex")
217
+
218
+ return {
219
+ ...env,
220
+ HIGHSTATE_UPDATE_TRIGGER_HASH: hashHex,
221
+ }
222
+ })
223
+ }
224
+
177
225
  export class Command extends ComponentResource {
178
226
  public readonly stdout: Output<string>
179
227
  public readonly stderr: Output<string>
@@ -181,6 +229,11 @@ export class Command extends ComponentResource {
181
229
  constructor(name: string, args: CommandArgs, opts?: ComponentResourceOptions) {
182
230
  super("highstate:common:Command", name, args, opts)
183
231
 
232
+ const environment = applyUpdateTriggers(
233
+ args.environment,
234
+ args.updateTriggers,
235
+ ) as local.CommandArgs["environment"]
236
+
184
237
  const command =
185
238
  args.host === "local"
186
239
  ? new local.Command(
@@ -192,7 +245,7 @@ export class Command extends ComponentResource {
192
245
  logging: args.logging,
193
246
  triggers: args.triggers ? output(args.triggers).apply(flat) : undefined,
194
247
  dir: args.cwd ?? homedir(),
195
- environment: args.environment as local.CommandArgs["environment"],
248
+ environment,
196
249
  stdin: args.stdin,
197
250
  },
198
251
  { ...opts, parent: this },
@@ -201,10 +254,6 @@ export class Command extends ComponentResource {
201
254
  name,
202
255
  {
203
256
  connection: output(args.host).apply(server => {
204
- if ("host" in server) {
205
- return output(server)
206
- }
207
-
208
257
  if (!server.ssh) {
209
258
  throw new Error(`The server "${server.hostname}" has no SSH credentials`)
210
259
  }
@@ -212,26 +261,44 @@ export class Command extends ComponentResource {
212
261
  return getServerConnection(server.ssh)
213
262
  }),
214
263
 
215
- create: output(args.create).apply(createCommand).apply(wrapWithWorkDir(args.cwd)),
264
+ create: output(args.create)
265
+ .apply(createCommand)
266
+ .apply(wrapWithWorkDir(args.cwd))
267
+ .apply(wrapWithEnvironment(environment)),
216
268
 
217
269
  update: args.update
218
- ? output(args.update).apply(createCommand).apply(wrapWithWorkDir(args.cwd))
270
+ ? output(args.update)
271
+ .apply(createCommand)
272
+ .apply(wrapWithWorkDir(args.cwd))
273
+ .apply(wrapWithEnvironment(environment))
219
274
  : undefined,
220
275
 
221
276
  delete: args.delete
222
- ? output(args.delete).apply(createCommand).apply(wrapWithWorkDir(args.cwd))
277
+ ? output(args.delete)
278
+ .apply(createCommand)
279
+ .apply(wrapWithWorkDir(args.cwd))
280
+ .apply(wrapWithEnvironment(environment))
223
281
  : undefined,
224
282
 
225
283
  logging: args.logging,
226
284
  triggers: args.triggers ? output(args.triggers).apply(flat) : undefined,
227
285
  stdin: args.stdin,
228
- environment: args.environment as remote.CommandArgs["environment"],
286
+
287
+ addPreviousOutputInEnv: false,
288
+
289
+ // TODO: does not work if server do not define AcceptEnv
290
+ // environment,
229
291
  },
230
292
  { ...opts, parent: this },
231
293
  )
232
294
 
233
295
  this.stdout = command.stdout
234
296
  this.stderr = command.stderr
297
+
298
+ this.registerOutputs({
299
+ stdout: this.stdout,
300
+ stderr: this.stderr,
301
+ })
235
302
  }
236
303
 
237
304
  /**