@homeflare/alchemy 0.39.0 → 0.41.0
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/README.md +10 -8
- package/dist/caddy/index.js.map +2 -2
- package/dist/ceph/auth-entity.d.ts +34 -0
- package/dist/ceph/auth-entity.d.ts.map +1 -0
- package/dist/ceph/ceph-argv-config.d.ts +10 -0
- package/dist/ceph/ceph-argv-config.d.ts.map +1 -0
- package/dist/ceph/ceph-argv.d.ts +19 -0
- package/dist/ceph/ceph-argv.d.ts.map +1 -0
- package/dist/ceph/ceph-auth-form.d.ts +54 -0
- package/dist/ceph/ceph-auth-form.d.ts.map +1 -0
- package/dist/ceph/ceph-auth-parse.d.ts +56 -0
- package/dist/ceph/ceph-auth-parse.d.ts.map +1 -0
- package/dist/ceph/ceph-auth-reconcile.d.ts +35 -0
- package/dist/ceph/ceph-auth-reconcile.d.ts.map +1 -0
- package/dist/ceph/ceph-config-options.d.ts +23 -0
- package/dist/ceph/ceph-config-options.d.ts.map +1 -0
- package/dist/ceph/ceph-key-leak-assert.d.ts +25 -0
- package/dist/ceph/ceph-key-leak-assert.d.ts.map +1 -0
- package/dist/ceph/ceph-transport.d.ts +37 -0
- package/dist/ceph/ceph-transport.d.ts.map +1 -0
- package/dist/ceph/fake-ceph-dial.d.ts +35 -0
- package/dist/ceph/fake-ceph-dial.d.ts.map +1 -0
- package/dist/ceph/index.d.ts +13 -0
- package/dist/ceph/index.d.ts.map +1 -0
- package/dist/ceph/index.js +1610 -0
- package/dist/ceph/index.js.map +38 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/launchd/fake-runner.d.ts.map +1 -1
- package/dist/launchd/index.d.ts +1 -1
- package/dist/launchd/index.d.ts.map +1 -1
- package/dist/launchd/index.js +7 -1
- package/dist/launchd/index.js.map +5 -5
- package/dist/launchd/local-runner.d.ts.map +1 -1
- package/dist/launchd/runner.d.ts +21 -0
- package/dist/launchd/runner.d.ts.map +1 -1
- package/dist/launchd/sudo-runner.d.ts.map +1 -1
- package/dist/linux/directory-lifecycle.d.ts.map +1 -1
- package/dist/linux/fake-linux-host.d.ts.map +1 -1
- package/dist/linux/index.d.ts +1 -1
- package/dist/linux/index.d.ts.map +1 -1
- package/dist/linux/index.js +16 -4
- package/dist/linux/index.js.map +7 -7
- package/dist/linux/ssh-runner.d.ts.map +1 -1
- package/dist/linux/sudo-runner.d.ts.map +1 -1
- package/dist/opnsense/alias-form.d.ts +14 -1
- package/dist/opnsense/alias-form.d.ts.map +1 -1
- package/dist/opnsense/alias.d.ts +1 -1
- package/dist/opnsense/alias.d.ts.map +1 -1
- package/dist/opnsense/group-form.d.ts +8 -2
- package/dist/opnsense/group-form.d.ts.map +1 -1
- package/dist/opnsense/group.d.ts +1 -1
- package/dist/opnsense/group.d.ts.map +1 -1
- package/dist/opnsense/index.js +15 -7
- package/dist/opnsense/index.js.map +7 -7
- package/dist/opnsense/wire.d.ts +42 -12
- package/dist/opnsense/wire.d.ts.map +1 -1
- package/dist/proxmox/index.js +62 -39
- package/dist/proxmox/index.js.map +8 -7
- package/dist/proxmox/qemu-volume.d.ts +12 -2
- package/dist/proxmox/qemu-volume.d.ts.map +1 -1
- package/dist/proxmox/storage-form.d.ts +28 -3
- package/dist/proxmox/storage-form.d.ts.map +1 -1
- package/dist/proxmox/storage-plugin-options.d.ts +2 -0
- package/dist/proxmox/storage-plugin-options.d.ts.map +1 -0
- package/dist/proxmox/storage-wire.d.ts +28 -1
- package/dist/proxmox/storage-wire.d.ts.map +1 -1
- package/dist/proxmox/storage.d.ts +6 -1
- package/dist/proxmox/storage.d.ts.map +1 -1
- package/dist/release/index.d.ts +1 -1
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js.map +2 -2
- package/dist/talos/credentials-write.d.ts +46 -0
- package/dist/talos/credentials-write.d.ts.map +1 -0
- package/dist/talos/credentials.d.ts +51 -9
- package/dist/talos/credentials.d.ts.map +1 -1
- package/dist/talos/fake-process.d.ts +56 -0
- package/dist/talos/fake-process.d.ts.map +1 -0
- package/dist/talos/index.d.ts +19 -4
- package/dist/talos/index.d.ts.map +1 -1
- package/dist/talos/index.js +357 -173
- package/dist/talos/index.js.map +16 -10
- package/dist/talos/kubeconfig-attrs.d.ts +4 -0
- package/dist/talos/kubeconfig-attrs.d.ts.map +1 -0
- package/dist/talos/kubeconfig.d.ts +41 -23
- package/dist/talos/kubeconfig.d.ts.map +1 -1
- package/dist/talos/machine-config-fixtures.d.ts +60 -0
- package/dist/talos/machine-config-fixtures.d.ts.map +1 -0
- package/dist/talos/machine-config-poll.d.ts +43 -0
- package/dist/talos/machine-config-poll.d.ts.map +1 -0
- package/dist/talos/machine-config-read.d.ts +49 -0
- package/dist/talos/machine-config-read.d.ts.map +1 -0
- package/dist/talos/resource.d.ts +18 -0
- package/dist/talos/resource.d.ts.map +1 -1
- package/dist/talos/talos-bootstrap-etcd.d.ts +38 -0
- package/dist/talos/talos-bootstrap-etcd.d.ts.map +1 -0
- package/dist/talos/talos-bootstrap.d.ts +87 -0
- package/dist/talos/talos-bootstrap.d.ts.map +1 -1
- package/dist/talos/talos-cluster-health.d.ts +43 -1
- package/dist/talos/talos-cluster-health.d.ts.map +1 -1
- package/dist/talos/talos-errors.d.ts +68 -0
- package/dist/talos/talos-errors.d.ts.map +1 -0
- package/dist/talos/talos-machine-config.d.ts +51 -15
- package/dist/talos/talos-machine-config.d.ts.map +1 -1
- package/dist/talos/talosctl.d.ts +7 -0
- package/dist/talos/talosctl.d.ts.map +1 -1
- package/dist/talos/values.d.ts +23 -3
- package/dist/talos/values.d.ts.map +1 -1
- package/dist/unifi/acl-rule-drift.d.ts +22 -0
- package/dist/unifi/acl-rule-drift.d.ts.map +1 -0
- package/dist/unifi/acl-rule-form.d.ts +90 -0
- package/dist/unifi/acl-rule-form.d.ts.map +1 -0
- package/dist/unifi/acl-rule-ordering-form.d.ts +49 -0
- package/dist/unifi/acl-rule-ordering-form.d.ts.map +1 -0
- package/dist/unifi/acl-rule-ordering.d.ts +54 -0
- package/dist/unifi/acl-rule-ordering.d.ts.map +1 -0
- package/dist/unifi/acl-rule.d.ts +49 -0
- package/dist/unifi/acl-rule.d.ts.map +1 -0
- package/dist/unifi/dns-policy-drift.d.ts +21 -0
- package/dist/unifi/dns-policy-drift.d.ts.map +1 -0
- package/dist/unifi/dns-policy-form.d.ts +97 -0
- package/dist/unifi/dns-policy-form.d.ts.map +1 -0
- package/dist/unifi/dns-policy.d.ts +55 -0
- package/dist/unifi/dns-policy.d.ts.map +1 -0
- package/dist/unifi/firewall-policy-drift.d.ts +26 -0
- package/dist/unifi/firewall-policy-drift.d.ts.map +1 -0
- package/dist/unifi/firewall-policy-form.d.ts +102 -0
- package/dist/unifi/firewall-policy-form.d.ts.map +1 -0
- package/dist/unifi/firewall-policy-ordering-form.d.ts +66 -0
- package/dist/unifi/firewall-policy-ordering-form.d.ts.map +1 -0
- package/dist/unifi/firewall-policy-ordering.d.ts +57 -0
- package/dist/unifi/firewall-policy-ordering.d.ts.map +1 -0
- package/dist/unifi/firewall-policy.d.ts +57 -0
- package/dist/unifi/firewall-policy.d.ts.map +1 -0
- package/dist/unifi/index.d.ts +7 -0
- package/dist/unifi/index.d.ts.map +1 -1
- package/dist/unifi/index.js +588 -3
- package/dist/unifi/index.js.map +24 -4
- package/dist/unifi/paginate.d.ts +0 -12
- package/dist/unifi/paginate.d.ts.map +1 -1
- package/dist/unifi/providers.d.ts +1 -1
- package/dist/unifi/providers.d.ts.map +1 -1
- package/dist/unifi/scan-source.d.ts +11 -0
- package/dist/unifi/scan-source.d.ts.map +1 -0
- package/dist/unifi/traffic-matching-list-drift.d.ts +22 -0
- package/dist/unifi/traffic-matching-list-drift.d.ts.map +1 -0
- package/dist/unifi/traffic-matching-list-form.d.ts +56 -0
- package/dist/unifi/traffic-matching-list-form.d.ts.map +1 -0
- package/dist/unifi/traffic-matching-list.d.ts +51 -0
- package/dist/unifi/traffic-matching-list.d.ts.map +1 -0
- package/dist/unifi/wifi-broadcast-drift.d.ts +22 -0
- package/dist/unifi/wifi-broadcast-drift.d.ts.map +1 -0
- package/dist/unifi/wifi-broadcast-form.d.ts +127 -0
- package/dist/unifi/wifi-broadcast-form.d.ts.map +1 -0
- package/dist/unifi/wifi-broadcast.d.ts +73 -0
- package/dist/unifi/wifi-broadcast.d.ts.map +1 -0
- package/docs/distilled-interim.md +7 -0
- package/docs/launchd.md +5 -3
- package/docs/provider-standard.md +5 -1
- package/docs/talos-argocd-dogfood.md +160 -0
- package/docs/unifi-acl-rule.md +122 -0
- package/docs/unifi-dns-policy.md +75 -0
- package/docs/unifi-firewall-policy.md +109 -0
- package/docs/unifi-traffic-matching-list.md +82 -0
- package/docs/unifi-wifi.md +120 -0
- package/docs/unifi.md +10 -5
- package/package.json +7 -3
- package/src/ceph/auth-entity.ts +74 -0
- package/src/ceph/ceph-argv-config.ts +78 -0
- package/src/ceph/ceph-argv.test.ts +226 -0
- package/src/ceph/ceph-argv.ts +189 -0
- package/src/ceph/ceph-auth-form.test.ts +103 -0
- package/src/ceph/ceph-auth-form.ts +107 -0
- package/src/ceph/ceph-auth-parse.test.ts +155 -0
- package/src/ceph/ceph-auth-parse.ts +121 -0
- package/src/ceph/ceph-auth-reconcile-secrecy.test.ts +82 -0
- package/src/ceph/ceph-auth-reconcile.test.ts +233 -0
- package/src/ceph/ceph-auth-reconcile.ts +119 -0
- package/src/ceph/ceph-config-options.test.ts +44 -0
- package/src/ceph/ceph-config-options.ts +36 -0
- package/src/ceph/ceph-key-leak-assert.ts +35 -0
- package/src/ceph/ceph-no-output-logging.test.ts +52 -0
- package/src/ceph/ceph-transport.test.ts +125 -0
- package/src/ceph/ceph-transport.ts +117 -0
- package/src/ceph/fake-ceph-dial.ts +55 -0
- package/src/ceph/index.ts +12 -0
- package/src/index.ts +1 -0
- package/src/launchd/fake-runner.ts +2 -0
- package/src/launchd/index.ts +8 -1
- package/src/launchd/local-runner.ts +16 -1
- package/src/launchd/runner.ts +22 -0
- package/src/launchd/sudo-runner.ts +3 -0
- package/src/linux/directory-lifecycle.ts +18 -3
- package/src/linux/directory-sudo.test.ts +59 -0
- package/src/linux/directory.test.ts +34 -8
- package/src/linux/fake-linux-host.ts +2 -0
- package/src/linux/index.ts +1 -0
- package/src/linux/ssh-runner.ts +4 -0
- package/src/linux/sudo-runner.ts +3 -0
- package/src/opnsense/alias-form.test.ts +24 -12
- package/src/opnsense/alias-form.ts +20 -7
- package/src/opnsense/alias.test.ts +13 -6
- package/src/opnsense/alias.ts +16 -10
- package/src/opnsense/group-form.test.ts +12 -8
- package/src/opnsense/group-form.ts +13 -4
- package/src/opnsense/group.test.ts +18 -7
- package/src/opnsense/group.ts +10 -3
- package/src/opnsense/wire.ts +57 -12
- package/src/proxmox/qemu-volume.test.ts +153 -4
- package/src/proxmox/qemu-volume.ts +71 -14
- package/src/proxmox/storage-form.ts +48 -8
- package/src/proxmox/storage-plugin-options.ts +45 -0
- package/src/proxmox/storage-update.test.ts +183 -0
- package/src/proxmox/storage-wire.ts +54 -7
- package/src/proxmox/storage.ts +18 -6
- package/src/release/index.ts +1 -0
- package/src/talos/credentials-write.test.ts +81 -0
- package/src/talos/credentials-write.ts +95 -0
- package/src/talos/credentials.test.ts +157 -0
- package/src/talos/credentials.ts +160 -90
- package/src/talos/fake-process.ts +94 -0
- package/src/talos/index.ts +39 -4
- package/src/talos/kubeconfig-attrs.ts +55 -0
- package/src/talos/kubeconfig.test.ts +256 -0
- package/src/talos/kubeconfig.ts +170 -90
- package/src/talos/machine-config-fixtures.ts +86 -0
- package/src/talos/machine-config-poll.test.ts +57 -0
- package/src/talos/machine-config-poll.ts +82 -0
- package/src/talos/machine-config-read.ts +118 -0
- package/src/talos/resource.ts +19 -0
- package/src/talos/secrets-gate.test.ts +63 -0
- package/src/talos/talos-bootstrap-etcd.ts +71 -0
- package/src/talos/talos-bootstrap.test.ts +218 -0
- package/src/talos/talos-bootstrap.ts +151 -45
- package/src/talos/talos-cluster-health.test.ts +143 -0
- package/src/talos/talos-cluster-health.ts +91 -28
- package/src/talos/talos-errors.ts +85 -0
- package/src/talos/talos-machine-config.test.ts +189 -0
- package/src/talos/talos-machine-config.ts +102 -88
- package/src/talos/talosctl.ts +8 -0
- package/src/talos/values.test.ts +34 -6
- package/src/talos/values.ts +41 -7
- package/src/unifi/acl-rule-drift.ts +57 -0
- package/src/unifi/acl-rule-form.ts +135 -0
- package/src/unifi/acl-rule-ordering-form.ts +79 -0
- package/src/unifi/acl-rule-ordering.test.ts +141 -0
- package/src/unifi/acl-rule-ordering.ts +70 -0
- package/src/unifi/acl-rule.test.ts +183 -0
- package/src/unifi/acl-rule.ts +61 -0
- package/src/unifi/discriminated-filter-decode.test.ts +134 -0
- package/src/unifi/dns-policy-drift.ts +48 -0
- package/src/unifi/dns-policy-form.ts +142 -0
- package/src/unifi/dns-policy.test.ts +154 -0
- package/src/unifi/dns-policy.ts +67 -0
- package/src/unifi/firewall-policy-drift.test.ts +151 -0
- package/src/unifi/firewall-policy-drift.ts +55 -0
- package/src/unifi/firewall-policy-filter-roundtrip.test.ts +143 -0
- package/src/unifi/firewall-policy-form.ts +146 -0
- package/src/unifi/firewall-policy-ordering-form.ts +111 -0
- package/src/unifi/firewall-policy-ordering.test.ts +185 -0
- package/src/unifi/firewall-policy-ordering.ts +87 -0
- package/src/unifi/firewall-policy.test.ts +183 -0
- package/src/unifi/firewall-policy.ts +73 -0
- package/src/unifi/index.ts +63 -0
- package/src/unifi/paginate.test.ts +24 -0
- package/src/unifi/paginate.ts +15 -3
- package/src/unifi/providers.ts +19 -1
- package/src/unifi/scan-source.ts +64 -0
- package/src/unifi/traffic-matching-list-drift.ts +40 -0
- package/src/unifi/traffic-matching-list-form.ts +77 -0
- package/src/unifi/traffic-matching-list.test.ts +202 -0
- package/src/unifi/traffic-matching-list.ts +78 -0
- package/src/unifi/wifi-broadcast-details-call.test.ts +92 -0
- package/src/unifi/wifi-broadcast-drift.ts +69 -0
- package/src/unifi/wifi-broadcast-form.ts +211 -0
- package/src/unifi/wifi-broadcast-secrets.test.ts +145 -0
- package/src/unifi/wifi-broadcast.test.ts +155 -0
- package/src/unifi/wifi-broadcast.ts +90 -0
- package/src/unifi/write-op-reference.test.ts +8 -54
package/dist/talos/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// src/talos/kubeconfig.ts
|
|
2
|
+
import { chmodSync } from "node:fs";
|
|
2
3
|
import { Resource } from "alchemy";
|
|
3
4
|
import { isResolved } from "alchemy/Diff";
|
|
4
5
|
import * as Provider from "alchemy/Provider";
|
|
5
|
-
import * as
|
|
6
|
+
import * as Effect4 from "effect/Effect";
|
|
6
7
|
|
|
7
8
|
// src/talos/credentials.ts
|
|
8
9
|
import { closeSync, openSync, writeFileSync } from "node:fs";
|
|
@@ -10,96 +11,104 @@ import * as Effect from "effect/Effect";
|
|
|
10
11
|
import * as Stream from "effect/Stream";
|
|
11
12
|
import * as ChildProcess from "effect/unstable/process/ChildProcess";
|
|
12
13
|
import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";
|
|
13
|
-
var
|
|
14
|
-
var
|
|
14
|
+
var DEFAULT_TALOSCONFIG_KEY = "talosconfig";
|
|
15
|
+
var DEFAULT_KUBECONFIG_KEY = "kubeconfig";
|
|
16
|
+
var readKvValue = (mount, key, fields) => Effect.gen(function* () {
|
|
15
17
|
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
16
|
-
const
|
|
17
|
-
const result = yield* ChildProcess.make("bao", ["kv", "get", "-format=json", `${target.mount}/${key}`], { detached: false, extendEnv: true, stderr: "pipe", stdin: "ignore", stdout: "pipe" }).pipe(spawner.spawn, Effect.flatMap((child) => Effect.all({
|
|
18
|
+
const result = yield* ChildProcess.make("bao", ["kv", "get", "-format=json", `${mount}/${key}`], { detached: false, extendEnv: true, stderr: "pipe", stdin: "ignore", stdout: "pipe" }).pipe(spawner.spawn, Effect.flatMap((child) => Effect.all({
|
|
18
19
|
exitCode: child.exitCode,
|
|
19
20
|
stderr: child.stderr.pipe(Stream.decodeText, Stream.mkString, Effect.map((text) => text.trim())),
|
|
20
21
|
stdout: child.stdout.pipe(Stream.decodeText, Stream.mkString, Effect.map((text) => text.trim()))
|
|
21
22
|
}, { concurrency: "unbounded" })), Effect.scoped);
|
|
22
23
|
if (result.exitCode !== 0) {
|
|
23
|
-
return yield* Effect.fail(new Error(`bao kv get ${
|
|
24
|
+
return yield* Effect.fail(new Error(`bao kv get ${mount}/${key} exited ${String(result.exitCode)}: ${result.stderr}`));
|
|
24
25
|
}
|
|
25
26
|
const parsed = JSON.parse(result.stdout);
|
|
26
27
|
const data = parsed.data?.data;
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
for (const field of fields) {
|
|
29
|
+
const raw = data?.[field];
|
|
30
|
+
if (typeof raw === "string" && raw.trim() !== "")
|
|
31
|
+
return raw;
|
|
30
32
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
return yield* Effect.fail(new Error(`${mount}/${key} returned no ${fields.join("/")} field. Never store the value itself in ` + "an Alchemy prop or attribute — fix the KV entry, not the caller."));
|
|
34
|
+
});
|
|
35
|
+
var mintKvTempFile = (raw, label) => Effect.gen(function* () {
|
|
36
|
+
const path = `${Bun.env["TMPDIR"] ?? "/tmp"}/hf-talos-${label}-${Bun.randomUUIDv7()}.yaml`;
|
|
37
|
+
yield* Effect.acquireRelease(Effect.try({
|
|
33
38
|
try: () => {
|
|
34
|
-
const fd = openSync(
|
|
39
|
+
const fd = openSync(path, "wx", 384);
|
|
35
40
|
try {
|
|
36
41
|
writeFileSync(fd, raw);
|
|
37
42
|
} finally {
|
|
38
43
|
closeSync(fd);
|
|
39
44
|
}
|
|
40
45
|
},
|
|
41
|
-
catch: (cause) => new Error(`writing temp
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
catch: () => {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
}).pipe(Effect.orElseSucceed(() => {
|
|
46
|
+
catch: (cause) => new Error(`writing ${label} temp file: ${String(cause)}`)
|
|
47
|
+
}), () => Effect.tryPromise({ try: () => Bun.file(path).delete(), catch: () => {
|
|
48
|
+
return;
|
|
49
|
+
} }).pipe(Effect.orElseSucceed(() => {
|
|
49
50
|
return;
|
|
50
51
|
})));
|
|
51
|
-
return {
|
|
52
|
-
})
|
|
52
|
+
return { path };
|
|
53
|
+
});
|
|
54
|
+
var mintTalosconfig = (target) => readKvValue(target.mount, target.talosconfigKey ?? DEFAULT_TALOSCONFIG_KEY, [
|
|
55
|
+
"talosconfig",
|
|
56
|
+
"config"
|
|
57
|
+
]).pipe(Effect.flatMap((raw) => mintKvTempFile(raw, `${target.cluster}-talosconfig`)), Effect.map(({ path }) => ({ talosconfigPath: path })));
|
|
53
58
|
|
|
54
|
-
// src/talos/
|
|
59
|
+
// src/talos/credentials-write.ts
|
|
55
60
|
import * as Effect2 from "effect/Effect";
|
|
56
61
|
import * as Stream2 from "effect/Stream";
|
|
57
62
|
import * as ChildProcess2 from "effect/unstable/process/ChildProcess";
|
|
58
63
|
import * as ChildProcessSpawner3 from "effect/unstable/process/ChildProcessSpawner";
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
var talosctl = (args, options) => Effect2.gen(function* () {
|
|
64
|
+
var reservedTempPath = (label) => Effect2.gen(function* () {
|
|
65
|
+
const path = `${Bun.env["TMPDIR"] ?? "/tmp"}/hf-talos-${label}-${Bun.randomUUIDv7()}.yaml`;
|
|
66
|
+
yield* Effect2.addFinalizer(() => Effect2.tryPromise({ try: () => Bun.file(path).delete(), catch: () => {
|
|
67
|
+
return;
|
|
68
|
+
} }).pipe(Effect2.orElseSucceed(() => {
|
|
69
|
+
return;
|
|
70
|
+
})));
|
|
71
|
+
return { path };
|
|
72
|
+
});
|
|
73
|
+
var writeKvValue = (mount, key, field, value) => Effect2.gen(function* () {
|
|
71
74
|
const spawner = yield* ChildProcessSpawner3.ChildProcessSpawner;
|
|
72
|
-
const
|
|
73
|
-
...args,
|
|
74
|
-
"--talosconfig",
|
|
75
|
-
options.talosconfigPath,
|
|
76
|
-
...options.nodes === undefined ? [] : ["--nodes", options.nodes.join(",")],
|
|
77
|
-
...options.endpoints === undefined ? [] : ["--endpoints", options.endpoints.join(",")]
|
|
78
|
-
];
|
|
79
|
-
const result = yield* ChildProcess2.make("talosctl", argv, {
|
|
75
|
+
const result = yield* ChildProcess2.make("bao", ["kv", "put", `${mount}/${key}`, `${field}=-`], {
|
|
80
76
|
detached: false,
|
|
81
77
|
extendEnv: true,
|
|
82
78
|
stderr: "pipe",
|
|
83
|
-
stdin:
|
|
79
|
+
stdin: Stream2.fromIterable([new TextEncoder().encode(value)]),
|
|
84
80
|
stdout: "pipe"
|
|
85
81
|
}).pipe(spawner.spawn, Effect2.flatMap((child) => Effect2.all({
|
|
86
82
|
exitCode: child.exitCode,
|
|
87
|
-
stderr: child.stderr.pipe(Stream2.decodeText, Stream2.mkString, Effect2.map((text) => text.trim()))
|
|
88
|
-
stdout: child.stdout.pipe(Stream2.decodeText, Stream2.mkString, Effect2.map((text) => text.trim()))
|
|
83
|
+
stderr: child.stderr.pipe(Stream2.decodeText, Stream2.mkString, Effect2.map((text) => text.trim()))
|
|
89
84
|
}, { concurrency: "unbounded" })), Effect2.scoped);
|
|
90
|
-
const command = args.join(" ");
|
|
91
85
|
if (result.exitCode !== 0) {
|
|
92
|
-
return yield* Effect2.fail(new
|
|
86
|
+
return yield* Effect2.fail(new Error(`bao kv put ${mount}/${key} exited ${String(result.exitCode)}: ${result.stderr}`));
|
|
93
87
|
}
|
|
94
|
-
return result.stdout;
|
|
95
88
|
});
|
|
96
|
-
var talosctlOrAlready = (args, options) => talosctl(args, options).pipe(Effect2.catchIf((cause) => cause instanceof TalosError && cause.exitCode !== 0 && /already|exists|bootstrapped/i.test(cause.message), () => Effect2.succeed("")));
|
|
97
89
|
|
|
98
90
|
// src/talos/values.ts
|
|
99
91
|
import { createHash } from "node:crypto";
|
|
100
92
|
var canonicalText = (text) => text.trimEnd();
|
|
101
93
|
var sha256 = (text) => createHash("sha256").update(canonicalText(text)).digest("hex");
|
|
102
94
|
var configDigest = (text) => sha256(text);
|
|
95
|
+
var extractMachineConfigSpec = (wrapperYaml) => {
|
|
96
|
+
let doc;
|
|
97
|
+
try {
|
|
98
|
+
doc = Bun.YAML.parse(wrapperYaml);
|
|
99
|
+
} catch {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (Array.isArray(doc)) {
|
|
103
|
+
if (doc.length !== 1)
|
|
104
|
+
return;
|
|
105
|
+
doc = doc[0];
|
|
106
|
+
}
|
|
107
|
+
if (doc === null || typeof doc !== "object")
|
|
108
|
+
return;
|
|
109
|
+
const spec = doc.spec;
|
|
110
|
+
return typeof spec === "string" ? spec : undefined;
|
|
111
|
+
};
|
|
103
112
|
var kubeconfigMetadata = (yamlText, contextName) => {
|
|
104
113
|
let doc;
|
|
105
114
|
try {
|
|
@@ -125,27 +134,18 @@ var kubeconfigMetadata = (yamlText, contextName) => {
|
|
|
125
134
|
endpoint
|
|
126
135
|
};
|
|
127
136
|
};
|
|
128
|
-
var resolveConfigPath = (stackDir, configFile) => configFile.startsWith("/") ? configFile : `${stackDir}/${configFile}`;
|
|
129
137
|
|
|
130
|
-
// src/talos/kubeconfig.ts
|
|
131
|
-
var TalosKubeconfig = Resource("Talos.Kubeconfig");
|
|
138
|
+
// src/talos/kubeconfig-attrs.ts
|
|
132
139
|
var generation = (meta) => sha256(`${meta.context}
|
|
133
140
|
${meta.endpoint}
|
|
134
141
|
${meta.caFingerprint}
|
|
135
142
|
${meta.clientFingerprint}`);
|
|
143
|
+
var UNWIRED_KUBECONFIG_PATH = "/talos-first-boot-unwired/wire-Kubernetes.ClusterAdapter-to-mintKubeconfig";
|
|
136
144
|
var toConnection = (props) => ({
|
|
137
|
-
auth: { context: props.context, kind: "kubeconfig", path:
|
|
145
|
+
auth: { context: props.context, kind: "kubeconfig", path: UNWIRED_KUBECONFIG_PATH }
|
|
138
146
|
});
|
|
139
|
-
var
|
|
140
|
-
const
|
|
141
|
-
try: () => Bun.file(props.runtimePath).text(),
|
|
142
|
-
catch: (cause) => new Error(String(cause))
|
|
143
|
-
}).pipe(Effect3.orElseSucceed(() => {
|
|
144
|
-
return;
|
|
145
|
-
}));
|
|
146
|
-
if (text === undefined)
|
|
147
|
-
return;
|
|
148
|
-
const meta = kubeconfigMetadata(text, props.context);
|
|
147
|
+
var buildAttrs = (raw, props) => {
|
|
148
|
+
const meta = kubeconfigMetadata(raw, props.context);
|
|
149
149
|
if (meta === undefined)
|
|
150
150
|
return;
|
|
151
151
|
return {
|
|
@@ -156,80 +156,203 @@ var readFileMeta = (props) => Effect3.gen(function* () {
|
|
|
156
156
|
credentialGeneration: generation({ ...meta, context: props.context }),
|
|
157
157
|
endpoint: meta.endpoint
|
|
158
158
|
};
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// src/talos/talosctl.ts
|
|
162
|
+
import * as Effect3 from "effect/Effect";
|
|
163
|
+
import * as Stream3 from "effect/Stream";
|
|
164
|
+
import * as ChildProcess3 from "effect/unstable/process/ChildProcess";
|
|
165
|
+
import * as ChildProcessSpawner5 from "effect/unstable/process/ChildProcessSpawner";
|
|
166
|
+
|
|
167
|
+
class TalosError extends Error {
|
|
168
|
+
command;
|
|
169
|
+
exitCode;
|
|
170
|
+
constructor(command, exitCode, detail) {
|
|
171
|
+
super(`talosctl ${command} -> ${String(exitCode)}: ${detail}`);
|
|
172
|
+
this.command = command;
|
|
173
|
+
this.exitCode = exitCode;
|
|
174
|
+
this.name = "TalosError";
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
var talosctl = (args, options) => Effect3.gen(function* () {
|
|
178
|
+
const spawner = yield* ChildProcessSpawner5.ChildProcessSpawner;
|
|
179
|
+
const argv = [
|
|
180
|
+
...args,
|
|
181
|
+
"--talosconfig",
|
|
182
|
+
options.talosconfigPath,
|
|
183
|
+
...options.nodes === undefined ? [] : ["--nodes", options.nodes.join(",")],
|
|
184
|
+
...options.endpoints === undefined ? [] : ["--endpoints", options.endpoints.join(",")],
|
|
185
|
+
...options.insecure === true ? ["--insecure"] : []
|
|
186
|
+
];
|
|
187
|
+
const result = yield* ChildProcess3.make("talosctl", argv, {
|
|
188
|
+
detached: false,
|
|
189
|
+
extendEnv: true,
|
|
190
|
+
stderr: "pipe",
|
|
191
|
+
stdin: "ignore",
|
|
192
|
+
stdout: "pipe"
|
|
193
|
+
}).pipe(spawner.spawn, Effect3.flatMap((child) => Effect3.all({
|
|
194
|
+
exitCode: child.exitCode,
|
|
195
|
+
stderr: child.stderr.pipe(Stream3.decodeText, Stream3.mkString, Effect3.map((text) => text.trim())),
|
|
196
|
+
stdout: child.stdout.pipe(Stream3.decodeText, Stream3.mkString, Effect3.map((text) => text.trim()))
|
|
197
|
+
}, { concurrency: "unbounded" })), Effect3.scoped);
|
|
198
|
+
const command = args.join(" ");
|
|
199
|
+
if (result.exitCode !== 0) {
|
|
200
|
+
return yield* Effect3.fail(new TalosError(command, result.exitCode, result.stderr.slice(0, 300)));
|
|
201
|
+
}
|
|
202
|
+
return result.stdout;
|
|
203
|
+
});
|
|
204
|
+
var talosctlOrAlready = (args, options) => talosctl(args, options).pipe(Effect3.catchIf((cause) => cause instanceof TalosError && cause.exitCode !== 0 && /already|exists|bootstrapped/i.test(cause.message), () => Effect3.succeed("")));
|
|
205
|
+
|
|
206
|
+
// src/talos/kubeconfig.ts
|
|
207
|
+
var TalosKubeconfig = Resource("Talos.Kubeconfig");
|
|
208
|
+
var isVaultKeyAbsent = (error) => error instanceof Error && /no value found at/i.test(error.message);
|
|
209
|
+
var readVaultMeta = (props, key) => readKvValue(props.target.mount, key, ["kubeconfig", "config"]).pipe(Effect4.catchIf(isVaultKeyAbsent, () => Effect4.succeed(undefined)));
|
|
210
|
+
var readKubeconfig = (props) => Effect4.gen(function* () {
|
|
211
|
+
const key = props.kubeconfigKey ?? DEFAULT_KUBECONFIG_KEY;
|
|
212
|
+
const raw = yield* readVaultMeta(props, key);
|
|
213
|
+
if (raw === undefined)
|
|
214
|
+
return;
|
|
215
|
+
const meta = buildAttrs(raw, props);
|
|
216
|
+
if (meta === undefined) {
|
|
217
|
+
return yield* Effect4.fail(new Error(`${props.target.mount}/${key}: vault content exists but does not parse as a kubeconfig ` + `for context ${props.context} — not a cold start. Fix the KV entry or the pinned ` + "context rather than trusting an empty read."));
|
|
218
|
+
}
|
|
219
|
+
return meta;
|
|
159
220
|
});
|
|
160
|
-
var
|
|
161
|
-
certificateAuthorityFingerprint: "",
|
|
162
|
-
clientCertificateFingerprint: "",
|
|
163
|
-
connection: toConnection(props),
|
|
164
|
-
context: props.context,
|
|
165
|
-
credentialGeneration: "",
|
|
166
|
-
endpoint: ""
|
|
167
|
-
} : meta));
|
|
168
|
-
var diff = (news, output) => Effect3.gen(function* () {
|
|
221
|
+
var diffKubeconfig = (news, output) => Effect4.gen(function* () {
|
|
169
222
|
if (output === undefined || !isResolved(news))
|
|
170
223
|
return;
|
|
171
|
-
const live = yield*
|
|
172
|
-
if (live
|
|
224
|
+
const live = yield* readKubeconfig(news);
|
|
225
|
+
if (live !== undefined && live.credentialGeneration === output.credentialGeneration && live.context === output.context && live.endpoint === output.endpoint) {
|
|
173
226
|
return { action: "noop" };
|
|
174
227
|
}
|
|
175
228
|
return { action: "update" };
|
|
176
229
|
});
|
|
177
|
-
var
|
|
230
|
+
var reconcileKubeconfig = (props, output) => Effect4.scoped(Effect4.gen(function* () {
|
|
231
|
+
const key = props.kubeconfigKey ?? DEFAULT_KUBECONFIG_KEY;
|
|
232
|
+
if (output !== undefined && output.credentialGeneration !== "") {
|
|
233
|
+
const raw2 = yield* readKvValue(props.target.mount, key, ["kubeconfig", "config"]);
|
|
234
|
+
const meta2 = buildAttrs(raw2, props);
|
|
235
|
+
if (meta2 === undefined) {
|
|
236
|
+
return yield* Effect4.die(new Error(`${props.target.mount}/${key}: vault content no longer parses as a kubeconfig for ` + `context ${props.context}. Read back rather than trusting the write once.`));
|
|
237
|
+
}
|
|
238
|
+
return meta2;
|
|
239
|
+
}
|
|
178
240
|
const credential = yield* mintTalosconfig(props.target);
|
|
179
|
-
yield*
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
241
|
+
const { path: outPath } = yield* reservedTempPath(`${props.target.cluster}-${props.node}-kubeconfig-out`);
|
|
242
|
+
yield* talosctl(["kubeconfig", outPath, "--merge=false", "--force", "--force-context-name", props.context], { nodes: [props.node], talosconfigPath: credential.talosconfigPath });
|
|
243
|
+
const raw = yield* Effect4.tryPromise({
|
|
244
|
+
catch: (cause) => new Error(`${outPath}: reading generated kubeconfig: ${String(cause)}`),
|
|
245
|
+
try: () => Bun.file(outPath).text()
|
|
246
|
+
});
|
|
247
|
+
if (raw.trim() === "") {
|
|
248
|
+
return yield* Effect4.die(new Error(`${props.node}: talosctl kubeconfig returned no error but wrote an empty file. Read ` + "back rather than trusting the exit code alone."));
|
|
249
|
+
}
|
|
250
|
+
yield* Effect4.try({
|
|
251
|
+
catch: (cause) => new Error(`${outPath}: chmod 0600: ${String(cause)}`),
|
|
252
|
+
try: () => chmodSync(outPath, 384)
|
|
253
|
+
});
|
|
254
|
+
const meta = buildAttrs(raw, props);
|
|
188
255
|
if (meta === undefined) {
|
|
189
|
-
return yield*
|
|
256
|
+
return yield* Effect4.die(new Error(`${props.node}: talosctl kubeconfig wrote a file that does not parse for context ` + `${props.context}.`));
|
|
190
257
|
}
|
|
258
|
+
yield* writeKvValue(props.target.mount, key, "kubeconfig", raw);
|
|
191
259
|
return meta;
|
|
192
|
-
});
|
|
260
|
+
}));
|
|
193
261
|
var handlers = {
|
|
194
|
-
delete: () =>
|
|
262
|
+
delete: () => Effect4.void,
|
|
195
263
|
diff: ({
|
|
196
264
|
news,
|
|
197
265
|
output
|
|
198
|
-
}) =>
|
|
199
|
-
list: () =>
|
|
200
|
-
read: ({ olds }) =>
|
|
201
|
-
reconcile: ({
|
|
266
|
+
}) => diffKubeconfig(news, output),
|
|
267
|
+
list: () => Effect4.succeed([]),
|
|
268
|
+
read: ({ olds }) => readKubeconfig(olds),
|
|
269
|
+
reconcile: ({
|
|
270
|
+
news,
|
|
271
|
+
output
|
|
272
|
+
}) => reconcileKubeconfig(news, output)
|
|
202
273
|
};
|
|
203
|
-
var TalosKubeconfigProvider = () => Provider.effect(TalosKubeconfig,
|
|
274
|
+
var TalosKubeconfigProvider = () => Provider.effect(TalosKubeconfig, Effect4.succeed(TalosKubeconfig.Provider.of(handlers)));
|
|
204
275
|
// src/talos/talos-bootstrap.ts
|
|
205
276
|
import { Resource as Resource2 } from "alchemy";
|
|
206
277
|
import { isResolved as isResolved2 } from "alchemy/Diff";
|
|
207
278
|
import * as Provider2 from "alchemy/Provider";
|
|
208
|
-
import * as
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
279
|
+
import * as Effect6 from "effect/Effect";
|
|
280
|
+
|
|
281
|
+
// src/talos/talos-bootstrap-etcd.ts
|
|
282
|
+
import * as Effect5 from "effect/Effect";
|
|
283
|
+
|
|
284
|
+
// src/talos/talos-errors.ts
|
|
285
|
+
import * as Data from "effect/Data";
|
|
286
|
+
|
|
287
|
+
class TalosConfigDigestMismatch extends Data.TaggedError("TalosConfigDigestMismatch") {
|
|
288
|
+
get message() {
|
|
289
|
+
return `${this.mount}/${this.key}: live content digest ${this.got} does not match the pinned ` + `configDigest ${this.expected}. Fail closed — nothing was sent to talosctl. Either the KV ` + "value was rewritten without a matching PR, or the pinned digest is stale; check which one " + "moved before re-pinning either side.";
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
class TalosConvergenceTimeout extends Data.TaggedError("TalosConvergenceTimeout") {
|
|
294
|
+
get message() {
|
|
295
|
+
return `${this.node}: apply-config (--mode ${this.mode}) was accepted but the live machineconfig ` + `digest never matched the pin within ${this.attempts} bounded poll attempts. Refusing to ` + "report converged — hand this node to the operator rather than silently pass.";
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
class TalosReBootstrapRefused extends Data.TaggedError("TalosReBootstrapRefused") {
|
|
300
|
+
get message() {
|
|
301
|
+
return `${this.node}: state already recorded a successful bootstrap, and a live check could not ` + `reconfirm it (${this.detail}). Refusing to run 'talosctl bootstrap' again — that risks a ` + "second, isolated etcd cluster on a reset node. This needs an operator, not a reconcile.";
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// src/talos/talos-bootstrap-etcd.ts
|
|
212
306
|
var isBootstrapped = (props, talosconfigPath) => talosctl(["get", "etcdmembers", "-o", "json"], {
|
|
213
307
|
nodes: [props.node],
|
|
214
308
|
talosconfigPath
|
|
215
|
-
}).pipe(
|
|
216
|
-
var
|
|
309
|
+
}).pipe(Effect5.map((text) => text.includes('"id"') || text.includes('"member"')));
|
|
310
|
+
var assertNoLivePeers = (props, talosconfigPath) => Effect5.all((props.peers ?? []).map((peer) => isBootstrapped({ ...props, node: peer }, talosconfigPath).pipe(Effect5.matchEffect({
|
|
311
|
+
onFailure: (cause) => Effect5.fail(new TalosReBootstrapRefused({
|
|
312
|
+
detail: `peer ${peer}: live read failed before bootstrap: ${String(cause)}`,
|
|
313
|
+
node: props.node
|
|
314
|
+
})),
|
|
315
|
+
onSuccess: (hasMembers) => hasMembers ? Effect5.fail(new TalosReBootstrapRefused({
|
|
316
|
+
detail: `peer ${peer} already reports etcd members — a cluster may already exist; ` + "refusing to bootstrap a second one",
|
|
317
|
+
node: props.node
|
|
318
|
+
})) : Effect5.void
|
|
319
|
+
}))), { discard: true });
|
|
320
|
+
|
|
321
|
+
// src/talos/talos-bootstrap.ts
|
|
322
|
+
var TalosBootstrap = Resource2("Talos.Bootstrap", {
|
|
323
|
+
defaultRemovalPolicy: "retain"
|
|
324
|
+
});
|
|
325
|
+
var readBootstrap = (props) => Effect6.scoped(Effect6.gen(function* () {
|
|
217
326
|
const credential = yield* mintTalosconfig(props.target);
|
|
218
327
|
const bootstrapped = yield* isBootstrapped(props, credential.talosconfigPath);
|
|
219
|
-
return { bootstrapped, node: props.node };
|
|
220
|
-
});
|
|
221
|
-
var
|
|
328
|
+
return bootstrapped ? { bootstrapped: true, node: props.node } : undefined;
|
|
329
|
+
}));
|
|
330
|
+
var diffBootstrap = (news, output) => Effect6.gen(function* () {
|
|
222
331
|
if (output === undefined || !isResolved2(news))
|
|
223
332
|
return;
|
|
224
|
-
|
|
225
|
-
if (live.bootstrapped)
|
|
333
|
+
if (output.bootstrapped)
|
|
226
334
|
return { action: "noop" };
|
|
227
|
-
|
|
335
|
+
const live = yield* readBootstrap(news);
|
|
336
|
+
return live?.bootstrapped === true ? { action: "noop" } : { action: "update" };
|
|
228
337
|
});
|
|
229
|
-
var
|
|
338
|
+
var reconcileBootstrap = (props, output) => Effect6.scoped(Effect6.gen(function* () {
|
|
230
339
|
const credential = yield* mintTalosconfig(props.target);
|
|
340
|
+
if (output?.bootstrapped === true) {
|
|
341
|
+
const stillBootstrapped = yield* isBootstrapped(props, credential.talosconfigPath).pipe(Effect6.catchIf(() => true, (cause) => Effect6.fail(new TalosReBootstrapRefused({
|
|
342
|
+
detail: `live read failed: ${String(cause)}`,
|
|
343
|
+
node: props.node
|
|
344
|
+
}))));
|
|
345
|
+
if (!stillBootstrapped) {
|
|
346
|
+
return yield* Effect6.fail(new TalosReBootstrapRefused({
|
|
347
|
+
detail: "etcd members are absent on a live read against already-bootstrapped state",
|
|
348
|
+
node: props.node
|
|
349
|
+
}));
|
|
350
|
+
}
|
|
351
|
+
return { bootstrapped: true, node: props.node };
|
|
352
|
+
}
|
|
231
353
|
const before = yield* isBootstrapped(props, credential.talosconfigPath);
|
|
232
354
|
if (!before) {
|
|
355
|
+
yield* assertNoLivePeers(props, credential.talosconfigPath);
|
|
233
356
|
yield* talosctlOrAlready(["bootstrap"], {
|
|
234
357
|
nodes: [props.node],
|
|
235
358
|
talosconfigPath: credential.talosconfigPath
|
|
@@ -237,26 +360,29 @@ var reconcile2 = (props) => Effect4.gen(function* () {
|
|
|
237
360
|
}
|
|
238
361
|
const after = yield* isBootstrapped(props, credential.talosconfigPath);
|
|
239
362
|
if (!after) {
|
|
240
|
-
return yield*
|
|
363
|
+
return yield* Effect6.die(new Error(`${props.node}: bootstrap returned no error but etcd members are still absent. ` + "Read back rather than trusting the exit code alone."));
|
|
241
364
|
}
|
|
242
365
|
return { bootstrapped: true, node: props.node };
|
|
243
|
-
});
|
|
366
|
+
}));
|
|
244
367
|
var handlers2 = {
|
|
245
|
-
delete: () =>
|
|
368
|
+
delete: () => Effect6.void,
|
|
246
369
|
diff: ({
|
|
247
370
|
news,
|
|
248
371
|
output
|
|
249
|
-
}) =>
|
|
250
|
-
list: () =>
|
|
251
|
-
read: ({ olds }) =>
|
|
252
|
-
reconcile: ({
|
|
372
|
+
}) => diffBootstrap(news, output),
|
|
373
|
+
list: () => Effect6.succeed([]),
|
|
374
|
+
read: ({ olds }) => readBootstrap(olds),
|
|
375
|
+
reconcile: ({
|
|
376
|
+
news,
|
|
377
|
+
output
|
|
378
|
+
}) => reconcileBootstrap(news, output)
|
|
253
379
|
};
|
|
254
|
-
var TalosBootstrapProvider = () => Provider2.effect(TalosBootstrap,
|
|
380
|
+
var TalosBootstrapProvider = () => Provider2.effect(TalosBootstrap, Effect6.succeed(TalosBootstrap.Provider.of(handlers2)));
|
|
255
381
|
// src/talos/talos-cluster-health.ts
|
|
256
382
|
import { Resource as Resource3 } from "alchemy";
|
|
257
383
|
import { isResolved as isResolved3 } from "alchemy/Diff";
|
|
258
384
|
import * as Provider3 from "alchemy/Provider";
|
|
259
|
-
import * as
|
|
385
|
+
import * as Effect7 from "effect/Effect";
|
|
260
386
|
var TalosClusterHealth = Resource3("Talos.ClusterHealth");
|
|
261
387
|
var nodeCsv = (nodes) => nodes === undefined || nodes.length === 0 ? "" : nodes.join(",");
|
|
262
388
|
var healthArgs = (props, waitTimeout) => {
|
|
@@ -269,10 +395,15 @@ var healthArgs = (props, waitTimeout) => {
|
|
|
269
395
|
args.push("--worker-nodes", workers);
|
|
270
396
|
return args;
|
|
271
397
|
};
|
|
272
|
-
var check = (props, waitTimeout) =>
|
|
398
|
+
var check = (props, waitTimeout) => Effect7.scoped(Effect7.gen(function* () {
|
|
273
399
|
const credential = yield* mintTalosconfig(props.target);
|
|
400
|
+
const contact = props.controlPlaneNodes[0];
|
|
401
|
+
if (contact === undefined) {
|
|
402
|
+
return yield* Effect7.fail(new Error(`Talos.ClusterHealth ${props.target.cluster}: controlPlaneNodes is empty — need at ` + "least one node to contact."));
|
|
403
|
+
}
|
|
274
404
|
yield* talosctl(healthArgs(props, waitTimeout), {
|
|
275
|
-
|
|
405
|
+
endpoints: [contact],
|
|
406
|
+
nodes: [contact],
|
|
276
407
|
talosconfigPath: credential.talosconfigPath
|
|
277
408
|
});
|
|
278
409
|
return {
|
|
@@ -280,113 +411,166 @@ var check = (props, waitTimeout) => Effect5.gen(function* () {
|
|
|
280
411
|
healthy: true,
|
|
281
412
|
workerNodes: nodeCsv(props.workerNodes)
|
|
282
413
|
};
|
|
283
|
-
});
|
|
284
|
-
var
|
|
414
|
+
}));
|
|
415
|
+
var isTalosError = (cause) => cause instanceof TalosError;
|
|
416
|
+
var readClusterHealth = (props) => check(props, "5s").pipe(Effect7.catchIf(isTalosError, () => Effect7.succeed({
|
|
285
417
|
controlPlaneNodes: nodeCsv(props.controlPlaneNodes),
|
|
286
418
|
healthy: false,
|
|
287
419
|
workerNodes: nodeCsv(props.workerNodes)
|
|
288
420
|
})));
|
|
289
|
-
var
|
|
421
|
+
var diffClusterHealth = (news, output) => Effect7.gen(function* () {
|
|
290
422
|
if (output === undefined || !isResolved3(news))
|
|
291
423
|
return;
|
|
292
|
-
const live = yield*
|
|
424
|
+
const live = yield* readClusterHealth(news);
|
|
293
425
|
if (live.healthy)
|
|
294
426
|
return { action: "noop" };
|
|
295
427
|
return { action: "update" };
|
|
296
428
|
});
|
|
297
|
-
var
|
|
429
|
+
var reconcileClusterHealth = (props) => check(props, props.waitTimeout ?? "20m0s").pipe(Effect7.catchIf(isTalosError, (cause) => Effect7.fail(new Error(`Talos.ClusterHealth ${props.target.cluster}: cluster not healthy within ` + `${props.waitTimeout ?? "20m0s"} — ${String(cause)}`))));
|
|
298
430
|
var handlers3 = {
|
|
299
|
-
delete: () =>
|
|
431
|
+
delete: () => Effect7.void,
|
|
300
432
|
diff: ({
|
|
301
433
|
news,
|
|
302
434
|
output
|
|
303
|
-
}) =>
|
|
304
|
-
list: () =>
|
|
305
|
-
read: ({ olds }) =>
|
|
306
|
-
reconcile: ({ news }) =>
|
|
435
|
+
}) => diffClusterHealth(news, output),
|
|
436
|
+
list: () => Effect7.succeed([]),
|
|
437
|
+
read: ({ olds }) => readClusterHealth(olds),
|
|
438
|
+
reconcile: ({ news }) => reconcileClusterHealth(news)
|
|
307
439
|
};
|
|
308
|
-
var TalosClusterHealthProvider = () => Provider3.effect(TalosClusterHealth,
|
|
440
|
+
var TalosClusterHealthProvider = () => Provider3.effect(TalosClusterHealth, Effect7.succeed(TalosClusterHealth.Provider.of(handlers3)));
|
|
309
441
|
// src/talos/talos-machine-config.ts
|
|
310
442
|
import { Resource as Resource4 } from "alchemy";
|
|
311
|
-
import { isResolved as isResolved4 } from "alchemy/Diff";
|
|
312
443
|
import * as Provider4 from "alchemy/Provider";
|
|
313
|
-
import * as
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
return configDigest(text);
|
|
323
|
-
});
|
|
324
|
-
var liveDigest = (props, talosconfigPath) => talosctl(["get", "machineconfig", "-o", "yaml"], {
|
|
444
|
+
import * as Effect10 from "effect/Effect";
|
|
445
|
+
|
|
446
|
+
// src/talos/machine-config-read.ts
|
|
447
|
+
import { Unowned } from "alchemy/AdoptPolicy";
|
|
448
|
+
import { isResolved as isResolved4 } from "alchemy/Diff";
|
|
449
|
+
import * as Effect8 from "effect/Effect";
|
|
450
|
+
import * as Result from "effect/Result";
|
|
451
|
+
var liveSpecDigest = (props, talosconfigPath) => talosctl(["get", "machineconfig", "v1alpha1", "-o", "yaml"], {
|
|
452
|
+
endpoints: [props.node],
|
|
325
453
|
nodes: [props.node],
|
|
326
454
|
talosconfigPath
|
|
327
|
-
}).pipe(
|
|
328
|
-
|
|
455
|
+
}).pipe(Effect8.flatMap((wrapperYaml) => {
|
|
456
|
+
const spec = extractMachineConfigSpec(wrapperYaml);
|
|
457
|
+
return spec === undefined ? Effect8.fail(new Error(`${props.node}: get machineconfig v1alpha1 returned no string 'spec' field — see ` + "values.ts extractMachineConfigSpec for the expected shape.")) : Effect8.succeed(configDigest(spec));
|
|
329
458
|
}));
|
|
330
|
-
var
|
|
331
|
-
|
|
459
|
+
var probeMaintenanceMode = (props, talosconfigPath) => talosctl(["version"], {
|
|
460
|
+
endpoints: [props.node],
|
|
461
|
+
insecure: true,
|
|
462
|
+
nodes: [props.node],
|
|
463
|
+
talosconfigPath
|
|
464
|
+
}).pipe(Effect8.as(true), Effect8.orElseSucceed(() => false));
|
|
465
|
+
var readMachineConfig = (props) => Effect8.scoped(Effect8.gen(function* () {
|
|
332
466
|
const credential = yield* mintTalosconfig(props.target);
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
467
|
+
const outcome = yield* Effect8.result(liveSpecDigest(props, credential.talosconfigPath));
|
|
468
|
+
if (Result.isFailure(outcome)) {
|
|
469
|
+
const reachable = yield* probeMaintenanceMode(props, credential.talosconfigPath);
|
|
470
|
+
return reachable ? undefined : yield* Effect8.fail(outcome.failure);
|
|
471
|
+
}
|
|
472
|
+
const attrs = {
|
|
473
|
+
configDigest: props.configDigest,
|
|
474
|
+
converged: outcome.success === props.configDigest ? "read-back" : false,
|
|
337
475
|
mode: props.mode ?? "auto",
|
|
338
476
|
node: props.node
|
|
339
477
|
};
|
|
340
|
-
|
|
341
|
-
|
|
478
|
+
return attrs.converged === false ? Unowned(attrs) : attrs;
|
|
479
|
+
}));
|
|
480
|
+
var diffMachineConfig = (news, output) => Effect8.gen(function* () {
|
|
342
481
|
if (output === undefined || !isResolved4(news))
|
|
343
482
|
return;
|
|
344
|
-
const live = yield*
|
|
345
|
-
if (live.converged)
|
|
483
|
+
const live = yield* readMachineConfig(news);
|
|
484
|
+
if (live !== undefined && live.converged !== false)
|
|
346
485
|
return { action: "noop" };
|
|
347
486
|
return { action: "update" };
|
|
348
487
|
});
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
488
|
+
|
|
489
|
+
// src/talos/machine-config-poll.ts
|
|
490
|
+
import * as Effect9 from "effect/Effect";
|
|
491
|
+
var isRebootish = (mode) => mode === "reboot" || mode === "auto";
|
|
492
|
+
var POLICY = {
|
|
493
|
+
immediate: { intervalMs: 2000, maxAttempts: 5 },
|
|
494
|
+
rebootish: { intervalMs: 5000, maxAttempts: 24 }
|
|
495
|
+
};
|
|
496
|
+
var pollUntilMatch = (check2, policy) => Effect9.gen(function* () {
|
|
497
|
+
for (let attempt = 1;attempt <= policy.maxAttempts; attempt++) {
|
|
498
|
+
const matched = yield* check2.pipe(Effect9.orElseSucceed(() => false));
|
|
499
|
+
if (matched)
|
|
500
|
+
return true;
|
|
501
|
+
if (attempt < policy.maxAttempts)
|
|
502
|
+
yield* Effect9.sleep(policy.intervalMs);
|
|
503
|
+
}
|
|
504
|
+
return false;
|
|
505
|
+
});
|
|
506
|
+
var confirmConverged = (node, mode, check2, policies = POLICY) => Effect9.gen(function* () {
|
|
507
|
+
const rebootish = isRebootish(mode);
|
|
508
|
+
const policy = rebootish ? policies.rebootish : policies.immediate;
|
|
509
|
+
const matched = yield* pollUntilMatch(check2, policy);
|
|
510
|
+
if (!matched) {
|
|
511
|
+
return yield* Effect9.fail(new TalosConvergenceTimeout({ attempts: policy.maxAttempts, mode, node }));
|
|
512
|
+
}
|
|
513
|
+
return rebootish ? "accepted" : "read-back";
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// src/talos/talos-machine-config.ts
|
|
517
|
+
var TalosMachineConfig = Resource4("Talos.MachineConfig");
|
|
518
|
+
var reconcileMachineConfig = (props, output, pollPolicies) => Effect10.scoped(Effect10.gen(function* () {
|
|
519
|
+
const mode = props.mode ?? "auto";
|
|
520
|
+
const raw = yield* readKvValue(props.target.mount, props.configKey, ["config"]);
|
|
521
|
+
const gotDigest = configDigest(raw);
|
|
522
|
+
if (gotDigest !== props.configDigest) {
|
|
523
|
+
return yield* Effect10.fail(new TalosConfigDigestMismatch({
|
|
524
|
+
expected: props.configDigest,
|
|
525
|
+
got: gotDigest,
|
|
526
|
+
key: props.configKey,
|
|
527
|
+
mount: props.target.mount
|
|
528
|
+
}));
|
|
529
|
+
}
|
|
352
530
|
const credential = yield* mintTalosconfig(props.target);
|
|
531
|
+
const { path } = yield* mintKvTempFile(raw, `${props.target.cluster}-${props.node}-config`);
|
|
532
|
+
const isCreate = output === undefined;
|
|
353
533
|
const args = [
|
|
354
534
|
"apply-config",
|
|
355
535
|
"--file",
|
|
356
536
|
path,
|
|
357
537
|
"--mode",
|
|
358
|
-
|
|
359
|
-
...
|
|
538
|
+
mode,
|
|
539
|
+
...isCreate ? ["--insecure"] : []
|
|
360
540
|
];
|
|
361
541
|
yield* talosctl(args, { nodes: [props.node], talosconfigPath: credential.talosconfigPath });
|
|
362
|
-
const
|
|
363
|
-
if (live !== wanted) {
|
|
364
|
-
return yield* Effect6.die(new Error(`${props.node}: apply-config returned no error but machineconfig digest still differs. ` + "Read back rather than trusting the exit code alone."));
|
|
365
|
-
}
|
|
542
|
+
const converged = yield* confirmConverged(props.node, mode, liveSpecDigest(props, credential.talosconfigPath).pipe(Effect10.map((live) => live === props.configDigest)), pollPolicies);
|
|
366
543
|
return {
|
|
367
|
-
configDigest:
|
|
368
|
-
converged
|
|
369
|
-
mode
|
|
544
|
+
configDigest: props.configDigest,
|
|
545
|
+
converged,
|
|
546
|
+
mode,
|
|
370
547
|
node: props.node
|
|
371
548
|
};
|
|
372
|
-
});
|
|
549
|
+
}));
|
|
373
550
|
var handlers4 = {
|
|
374
|
-
delete: () =>
|
|
551
|
+
delete: () => Effect10.void,
|
|
375
552
|
diff: ({
|
|
376
553
|
news,
|
|
377
554
|
output
|
|
378
|
-
}) =>
|
|
379
|
-
list: () =>
|
|
380
|
-
read: ({ olds }) =>
|
|
381
|
-
reconcile: ({
|
|
555
|
+
}) => diffMachineConfig(news, output),
|
|
556
|
+
list: () => Effect10.succeed([]),
|
|
557
|
+
read: ({ olds }) => readMachineConfig(olds),
|
|
558
|
+
reconcile: ({
|
|
559
|
+
news,
|
|
560
|
+
output
|
|
561
|
+
}) => reconcileMachineConfig(news, output)
|
|
382
562
|
};
|
|
383
|
-
var TalosMachineConfigProvider = () => Provider4.effect(TalosMachineConfig,
|
|
563
|
+
var TalosMachineConfigProvider = () => Provider4.effect(TalosMachineConfig, Effect10.succeed(TalosMachineConfig.Provider.of(handlers4)));
|
|
384
564
|
export {
|
|
565
|
+
TalosBootstrap,
|
|
385
566
|
TalosBootstrapProvider,
|
|
567
|
+
TalosClusterHealth,
|
|
386
568
|
TalosClusterHealthProvider,
|
|
569
|
+
TalosKubeconfig,
|
|
387
570
|
TalosKubeconfigProvider,
|
|
571
|
+
TalosMachineConfig,
|
|
388
572
|
TalosMachineConfigProvider
|
|
389
573
|
};
|
|
390
574
|
|
|
391
|
-
//# debugId=
|
|
575
|
+
//# debugId=D7E7AB43E3608FE064756E2164756E21
|
|
392
576
|
//# sourceMappingURL=index.js.map
|