@homeflare/alchemy 0.8.0 → 0.10.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 +22 -11
- package/dist/caddy/config-lifecycle.d.ts +1 -1
- package/dist/caddy/config.d.ts.map +1 -1
- package/dist/caddy/index.js +221 -53
- package/dist/caddy/index.js.map +13 -9
- package/dist/launchd/fake-runner.d.ts +1 -0
- package/dist/launchd/fake-runner.d.ts.map +1 -1
- package/dist/launchd/fake-sudo.d.ts +1 -0
- package/dist/launchd/fake-sudo.d.ts.map +1 -1
- package/dist/launchd/host-effect.d.ts +9 -18
- package/dist/launchd/host-effect.d.ts.map +1 -1
- package/dist/launchd/host-file-lifecycle.d.ts +14 -1
- package/dist/launchd/host-file-lifecycle.d.ts.map +1 -1
- package/dist/launchd/host-file.d.ts +2 -1
- package/dist/launchd/host-file.d.ts.map +1 -1
- package/dist/launchd/index.js +345 -86
- package/dist/launchd/index.js.map +18 -12
- package/dist/launchd/job-lifecycle.d.ts +8 -1
- package/dist/launchd/job-lifecycle.d.ts.map +1 -1
- package/dist/launchd/job-preflight.d.ts +13 -1
- package/dist/launchd/job-preflight.d.ts.map +1 -1
- package/dist/launchd/job.d.ts +2 -1
- package/dist/launchd/job.d.ts.map +1 -1
- package/dist/launchd/runner.d.ts +7 -0
- package/dist/launchd/runner.d.ts.map +1 -1
- package/dist/launchd/sudo-acl.d.ts +25 -0
- package/dist/launchd/sudo-acl.d.ts.map +1 -0
- package/dist/launchd/sudo-guard.d.ts +31 -4
- package/dist/launchd/sudo-guard.d.ts.map +1 -1
- package/dist/launchd/sudo-runner.d.ts +3 -3
- package/dist/launchd/sudo-runner.d.ts.map +1 -1
- package/dist/launchd/sudo-said.d.ts +10 -0
- package/dist/launchd/sudo-said.d.ts.map +1 -0
- package/dist/openbao/auth-method-reconcile.d.ts +6 -2
- package/dist/openbao/auth-method-reconcile.d.ts.map +1 -1
- package/dist/openbao/auth-method.d.ts.map +1 -1
- package/dist/openbao/auth-role.d.ts.map +1 -1
- package/dist/openbao/cloudflare-role.d.ts.map +1 -1
- package/dist/openbao/fake-bao.d.ts +18 -0
- package/dist/openbao/fake-bao.d.ts.map +1 -1
- package/dist/openbao/fake-core.d.ts +39 -0
- package/dist/openbao/fake-core.d.ts.map +1 -0
- package/dist/openbao/fake-families.d.ts +6 -3
- package/dist/openbao/fake-families.d.ts.map +1 -1
- package/dist/openbao/fake-mounts.d.ts +16 -0
- package/dist/openbao/fake-mounts.d.ts.map +1 -0
- package/dist/openbao/fake-stack.d.ts +5 -1
- package/dist/openbao/fake-stack.d.ts.map +1 -1
- package/dist/openbao/index.js +541 -270
- package/dist/openbao/index.js.map +26 -21
- package/dist/openbao/jwt-config.d.ts.map +1 -1
- package/dist/openbao/jwt-role.d.ts.map +1 -1
- package/dist/openbao/kubernetes-role.d.ts.map +1 -1
- package/dist/openbao/mfa-enforcement.d.ts +6 -2
- package/dist/openbao/mfa-enforcement.d.ts.map +1 -1
- package/dist/openbao/mfa-totp.d.ts +6 -2
- package/dist/openbao/mfa-totp.d.ts.map +1 -1
- package/dist/openbao/mount-reconcile.d.ts +5 -1
- package/dist/openbao/mount-reconcile.d.ts.map +1 -1
- package/dist/openbao/mount.d.ts.map +1 -1
- package/dist/openbao/pki-role.d.ts.map +1 -1
- package/dist/openbao/plugin-reconcile.d.ts +6 -2
- package/dist/openbao/plugin-reconcile.d.ts.map +1 -1
- package/dist/openbao/plugin.d.ts.map +1 -1
- package/dist/openbao/policy.d.ts.map +1 -1
- package/dist/openbao/proxmox-role.d.ts.map +1 -1
- package/dist/openbao/role-reconcile.d.ts +11 -1
- package/dist/openbao/role-reconcile.d.ts.map +1 -1
- package/dist/openbao/ssh-role.d.ts.map +1 -1
- package/dist/ownership/adopt.d.ts +52 -0
- package/dist/ownership/adopt.d.ts.map +1 -0
- package/dist/ownership/probe.d.ts +13 -0
- package/dist/ownership/probe.d.ts.map +1 -0
- package/dist/ownership/resume.d.ts +40 -0
- package/dist/ownership/resume.d.ts.map +1 -0
- package/dist/ownership/rows.d.ts +33 -0
- package/dist/ownership/rows.d.ts.map +1 -0
- package/dist/ownership/whole.d.ts +14 -0
- package/dist/ownership/whole.d.ts.map +1 -0
- package/dist/proxmox/ceph-pool-form.d.ts +3 -3
- package/dist/proxmox/ceph-pool.d.ts.map +1 -1
- package/dist/proxmox/fake-bao-env.d.ts +3 -0
- package/dist/proxmox/fake-bao-env.d.ts.map +1 -0
- package/dist/proxmox/fake-pve-lxc.d.ts +42 -0
- package/dist/proxmox/fake-pve-lxc.d.ts.map +1 -0
- package/dist/proxmox/fake-pve.d.ts +38 -0
- package/dist/proxmox/fake-pve.d.ts.map +1 -0
- package/dist/proxmox/index.d.ts +6 -2
- package/dist/proxmox/index.d.ts.map +1 -1
- package/dist/proxmox/index.js +970 -186
- package/dist/proxmox/index.js.map +22 -9
- package/dist/proxmox/lxc-create-form.d.ts +6 -0
- package/dist/proxmox/lxc-create-form.d.ts.map +1 -0
- package/dist/proxmox/lxc-harness.d.ts +34 -0
- package/dist/proxmox/lxc-harness.d.ts.map +1 -0
- package/dist/proxmox/lxc-identity.d.ts +18 -0
- package/dist/proxmox/lxc-identity.d.ts.map +1 -0
- package/dist/proxmox/lxc-judge.d.ts +46 -0
- package/dist/proxmox/lxc-judge.d.ts.map +1 -0
- package/dist/proxmox/lxc-lifecycle.d.ts +68 -0
- package/dist/proxmox/lxc-lifecycle.d.ts.map +1 -0
- package/dist/proxmox/lxc-props.d.ts +139 -0
- package/dist/proxmox/lxc-props.d.ts.map +1 -0
- package/dist/proxmox/lxc-volume.d.ts +29 -0
- package/dist/proxmox/lxc-volume.d.ts.map +1 -0
- package/dist/proxmox/lxc-wire.d.ts +51 -0
- package/dist/proxmox/lxc-wire.d.ts.map +1 -0
- package/dist/proxmox/lxc.d.ts +27 -47
- package/dist/proxmox/lxc.d.ts.map +1 -1
- package/dist/proxmox/network-apply-read.d.ts +1 -1
- package/dist/proxmox/network-apply-read.d.ts.map +1 -1
- package/dist/proxmox/pbs-datastore.d.ts.map +1 -1
- package/dist/proxmox/resource.d.ts.map +1 -1
- package/dist/proxmox/update-guard.d.ts +27 -0
- package/dist/proxmox/update-guard.d.ts.map +1 -0
- package/dist/verify/args.d.ts +16 -0
- package/dist/verify/args.d.ts.map +1 -0
- package/dist/verify/cli.d.ts +3 -0
- package/dist/verify/cli.d.ts.map +1 -0
- package/dist/verify/cli.js +320 -0
- package/dist/verify/cli.js.map +15 -0
- package/dist/verify/fake-engine.d.ts +57 -0
- package/dist/verify/fake-engine.d.ts.map +1 -0
- package/dist/verify/index.d.ts +16 -0
- package/dist/verify/index.d.ts.map +1 -0
- package/dist/verify/index.js +297 -0
- package/dist/verify/index.js.map +14 -0
- package/dist/verify/report.d.ts +6 -0
- package/dist/verify/report.d.ts.map +1 -0
- package/dist/verify/rows.d.ts +57 -0
- package/dist/verify/rows.d.ts.map +1 -0
- package/dist/verify/spy.d.ts +31 -0
- package/dist/verify/spy.d.ts.map +1 -0
- package/dist/verify/verify.d.ts +43 -0
- package/dist/verify/verify.d.ts.map +1 -0
- package/docs/adopt-verify.md +108 -0
- package/docs/adopted-deploys.md +85 -0
- package/docs/credentials.md +15 -0
- package/docs/launchd-sudo.md +31 -6
- package/docs/launchd.md +7 -3
- package/docs/ownership.md +113 -0
- package/docs/proxmox-lxc.md +196 -0
- package/docs/proxmox.md +12 -0
- package/package.json +9 -2
- package/src/caddy/adopt-scope.test.ts +1 -1
- package/src/caddy/config-lifecycle.ts +1 -1
- package/src/caddy/config.ts +2 -1
- package/src/launchd/adopt-apply.test.ts +166 -0
- package/src/launchd/fake-runner.ts +26 -4
- package/src/launchd/host-effect.ts +1 -33
- package/src/launchd/host-file-lifecycle.ts +39 -14
- package/src/launchd/host-file.ts +13 -4
- package/src/launchd/job-lifecycle.ts +22 -14
- package/src/launchd/job-preflight.ts +18 -1
- package/src/launchd/job.ts +13 -4
- package/src/launchd/runner.ts +7 -0
- package/src/launchd/sudo-acl.ts +68 -0
- package/src/launchd/sudo-chain.test.ts +121 -0
- package/src/launchd/sudo-guard.ts +89 -10
- package/src/launchd/sudo-modes.test.ts +135 -0
- package/src/launchd/sudo-runner.ts +40 -59
- package/src/launchd/sudo-said.ts +58 -0
- package/src/openbao/README.md +5 -2
- package/src/openbao/REPLACE.md +13 -11
- package/src/openbao/adopt-core.test.ts +119 -0
- package/src/openbao/adopt-families.test.ts +97 -0
- package/src/openbao/adopt-holes.test.ts +168 -0
- package/src/openbao/adopt-recovery.test.ts +71 -0
- package/src/openbao/auth-method-reconcile.ts +15 -3
- package/src/openbao/auth-method.ts +15 -6
- package/src/openbao/auth-role.ts +14 -4
- package/src/openbao/cloudflare-role.ts +17 -4
- package/src/openbao/fake-bao.ts +52 -0
- package/src/openbao/fake-core.ts +141 -0
- package/src/openbao/fake-families.ts +11 -5
- package/src/openbao/fake-mounts.ts +74 -0
- package/src/openbao/fake-stack.ts +17 -4
- package/src/openbao/jwt-config.ts +10 -7
- package/src/openbao/jwt-role.ts +10 -7
- package/src/openbao/kubernetes-role.ts +13 -7
- package/src/openbao/mfa-enforcement.ts +20 -6
- package/src/openbao/mfa-totp.ts +22 -7
- package/src/openbao/mount-move.test.ts +2 -35
- package/src/openbao/mount-reconcile.ts +13 -2
- package/src/openbao/mount.ts +15 -6
- package/src/openbao/pki-role.ts +15 -4
- package/src/openbao/plugin-reconcile.ts +16 -5
- package/src/openbao/plugin.ts +17 -6
- package/src/openbao/policy.ts +17 -4
- package/src/openbao/proxmox-role.ts +13 -4
- package/src/openbao/rename-adoption.test.ts +32 -19
- package/src/openbao/role-reconcile.ts +23 -0
- package/src/openbao/ssh-role.ts +14 -4
- package/src/ownership/adopt.ts +122 -0
- package/src/ownership/ownership.test.ts +200 -0
- package/src/ownership/probe.ts +65 -0
- package/src/ownership/resume.ts +118 -0
- package/src/ownership/rows.ts +117 -0
- package/src/ownership/whole.test.ts +39 -0
- package/src/ownership/whole.ts +60 -0
- package/src/proxmox/adopt-noop.test.ts +112 -0
- package/src/proxmox/ceph-pool-adopt.test.ts +88 -0
- package/src/proxmox/ceph-pool-form.ts +3 -3
- package/src/proxmox/ceph-pool.ts +10 -3
- package/src/proxmox/fake-bao-env.ts +28 -0
- package/src/proxmox/fake-pve-lxc.ts +210 -0
- package/src/proxmox/fake-pve.ts +75 -0
- package/src/proxmox/index.test.ts +6 -6
- package/src/proxmox/index.ts +6 -2
- package/src/proxmox/lxc-adopt.test.ts +170 -0
- package/src/proxmox/lxc-create-form.ts +76 -0
- package/src/proxmox/lxc-create.test.ts +126 -0
- package/src/proxmox/lxc-guards.test.ts +158 -0
- package/src/proxmox/lxc-harness.ts +150 -0
- package/src/proxmox/lxc-identity.ts +43 -0
- package/src/proxmox/lxc-judge.test.ts +175 -0
- package/src/proxmox/lxc-judge.ts +196 -0
- package/src/proxmox/lxc-lifecycle.ts +200 -0
- package/src/proxmox/lxc-ownership.test.ts +159 -0
- package/src/proxmox/lxc-props.ts +215 -0
- package/src/proxmox/lxc-read.test.ts +60 -0
- package/src/proxmox/lxc-volume.test.ts +96 -0
- package/src/proxmox/lxc-volume.ts +153 -0
- package/src/proxmox/lxc-wire.ts +210 -0
- package/src/proxmox/lxc.ts +216 -93
- package/src/proxmox/network-apply-read.ts +3 -1
- package/src/proxmox/pbs-datastore.ts +4 -3
- package/src/proxmox/resource.ts +7 -7
- package/src/proxmox/update-guard.ts +31 -0
- package/src/verify/args.ts +82 -0
- package/src/verify/cli.test.ts +86 -0
- package/src/verify/cli.ts +49 -0
- package/src/verify/fake-engine.ts +158 -0
- package/src/verify/index.ts +36 -0
- package/src/verify/report.ts +52 -0
- package/src/verify/rows.ts +169 -0
- package/src/verify/spy.test.ts +141 -0
- package/src/verify/spy.ts +149 -0
- package/src/verify/verify.test.ts +124 -0
- package/src/verify/verify.ts +172 -0
package/dist/proxmox/index.js
CHANGED
|
@@ -224,6 +224,11 @@ var pve = (target, role, method, path, form) => Effect4.gen(function* () {
|
|
|
224
224
|
// src/proxmox/resource.ts
|
|
225
225
|
import { isResolved } from "alchemy/Diff";
|
|
226
226
|
import * as Effect5 from "effect/Effect";
|
|
227
|
+
|
|
228
|
+
// src/proxmox/update-guard.ts
|
|
229
|
+
var formToSend = (matches, live, props, form) => matches(live, props) || Object.keys(form).length === 0 ? undefined : form;
|
|
230
|
+
|
|
231
|
+
// src/proxmox/resource.ts
|
|
227
232
|
var pveOperations = (spec) => {
|
|
228
233
|
const read = (props) => pve(props.target, spec.readRole ?? "read", "GET", spec.path(props)).pipe(Effect5.map((data) => data === undefined ? undefined : spec.attributes(data, props)), Effect5.orElseSucceed(() => {
|
|
229
234
|
return;
|
|
@@ -244,9 +249,9 @@ var pveOperations = (spec) => {
|
|
|
244
249
|
const live = yield* read(news);
|
|
245
250
|
if (live === undefined) {
|
|
246
251
|
yield* pve(news.target, "provision", "POST", spec.collection(news), spec.createForm(news));
|
|
247
|
-
} else if (spec.updateForm !== undefined
|
|
248
|
-
const form = spec.updateForm(news);
|
|
249
|
-
if (
|
|
252
|
+
} else if (spec.updateForm !== undefined) {
|
|
253
|
+
const form = formToSend(spec.matches, live, news, spec.updateForm(news));
|
|
254
|
+
if (form !== undefined) {
|
|
250
255
|
yield* pve(news.target, "provision", "PUT", spec.path(news), form);
|
|
251
256
|
}
|
|
252
257
|
}
|
|
@@ -835,8 +840,8 @@ var ProxmoxCephPoolProvider = () => Provider8.effect(ProxmoxCephPool, Effect17.s
|
|
|
835
840
|
yield* confirmAbsent(news, spec.collection(news), object(news));
|
|
836
841
|
upid = yield* pve(news.target, "provision", "POST", collection(news), createBody(news));
|
|
837
842
|
} else {
|
|
838
|
-
const form = updateBody(news);
|
|
839
|
-
if (
|
|
843
|
+
const form = formToSend(spec.matches, live, news, updateBody(news));
|
|
844
|
+
if (form !== undefined) {
|
|
840
845
|
upid = yield* pve(news.target, "provision", "PUT", object(news), form);
|
|
841
846
|
}
|
|
842
847
|
}
|
|
@@ -1041,45 +1046,868 @@ var handlers9 = pveHandlers({
|
|
|
1041
1046
|
var ProxmoxHaRuleProvider = () => Provider12.effect(ProxmoxHaRule, Effect21.succeed(ProxmoxHaRule.Provider.of(handlers9)));
|
|
1042
1047
|
// src/proxmox/lxc.ts
|
|
1043
1048
|
import { Resource as Resource13 } from "alchemy";
|
|
1049
|
+
import { isResolved as isResolved6 } from "alchemy/Diff";
|
|
1044
1050
|
import * as Provider13 from "alchemy/Provider";
|
|
1051
|
+
import * as Effect29 from "effect/Effect";
|
|
1052
|
+
|
|
1053
|
+
// src/ownership/adopt.ts
|
|
1054
|
+
import { AdoptPolicy } from "alchemy/AdoptPolicy";
|
|
1055
|
+
import { AlchemyContext } from "alchemy/AlchemyContext";
|
|
1056
|
+
import { Stack as Stack3 } from "alchemy/Stack";
|
|
1057
|
+
import * as Effect25 from "effect/Effect";
|
|
1058
|
+
import * as Option5 from "effect/Option";
|
|
1059
|
+
|
|
1060
|
+
// src/ownership/resume.ts
|
|
1061
|
+
import { Unowned } from "alchemy/AdoptPolicy";
|
|
1062
|
+
import { Artifacts } from "alchemy/Artifacts";
|
|
1045
1063
|
import * as Effect22 from "effect/Effect";
|
|
1046
|
-
|
|
1047
|
-
var
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1064
|
+
import * as Option2 from "effect/Option";
|
|
1065
|
+
var PROVEN = "homeflare/ownership/resumes";
|
|
1066
|
+
var UNFINISHED = "homeflare/ownership/unfinished";
|
|
1067
|
+
var note = (key, instanceId) => Effect22.gen(function* () {
|
|
1068
|
+
const bag = yield* Effect22.serviceOption(Artifacts);
|
|
1069
|
+
if (Option2.isSome(bag))
|
|
1070
|
+
yield* bag.value.set(key, instanceId);
|
|
1071
|
+
});
|
|
1072
|
+
var noted = (key, instanceId) => Effect22.gen(function* () {
|
|
1073
|
+
const bag = yield* Effect22.serviceOption(Artifacts);
|
|
1074
|
+
if (Option2.isNone(bag))
|
|
1075
|
+
return false;
|
|
1076
|
+
return (yield* bag.value.get(key)) === instanceId;
|
|
1077
|
+
});
|
|
1078
|
+
var noteUnfinished = (instanceId) => Effect22.as(note(UNFINISHED, instanceId), undefined);
|
|
1079
|
+
var noteResume = (instanceId, recovered) => Effect22.gen(function* () {
|
|
1080
|
+
yield* noteUnfinished(instanceId);
|
|
1081
|
+
const unproven = () => Effect22.succeed(undefined);
|
|
1082
|
+
const found = yield* recovered.pipe(Effect22.catch(unproven), Effect22.catchDefect(unproven));
|
|
1083
|
+
if (found === undefined || found === null || Unowned.is(found))
|
|
1084
|
+
return;
|
|
1085
|
+
yield* note(PROVEN, instanceId);
|
|
1086
|
+
});
|
|
1087
|
+
var resumes = (instanceId) => noted(PROVEN, instanceId);
|
|
1088
|
+
var unfinished = (instanceId) => noted(UNFINISHED, instanceId);
|
|
1089
|
+
var provingResumes = (service) => {
|
|
1090
|
+
const { diff, read } = service;
|
|
1091
|
+
if (diff === undefined || read === undefined)
|
|
1092
|
+
return service;
|
|
1093
|
+
const proving = (input) => Effect22.gen(function* () {
|
|
1094
|
+
if (input.output === undefined) {
|
|
1095
|
+
yield* noteResume(input.instanceId, read({ ...input, output: undefined }));
|
|
1096
|
+
}
|
|
1097
|
+
return yield* diff(input);
|
|
1098
|
+
});
|
|
1099
|
+
return { ...service, diff: proving };
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
// src/ownership/rows.ts
|
|
1103
|
+
import { Stack as Stack2 } from "alchemy/Stack";
|
|
1104
|
+
import { State, isActionState } from "alchemy/State/State";
|
|
1105
|
+
import * as Effect24 from "effect/Effect";
|
|
1106
|
+
import * as Option4 from "effect/Option";
|
|
1107
|
+
|
|
1108
|
+
// src/ownership/whole.ts
|
|
1109
|
+
import { Stack } from "alchemy/Stack";
|
|
1110
|
+
import * as Effect23 from "effect/Effect";
|
|
1111
|
+
import * as Option3 from "effect/Option";
|
|
1112
|
+
var absent = (value) => value === undefined || value === null;
|
|
1113
|
+
var isPlainObject = (value) => {
|
|
1114
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
1115
|
+
return false;
|
|
1116
|
+
const proto = Object.getPrototypeOf(value);
|
|
1117
|
+
return proto === Object.prototype || proto === null;
|
|
1118
|
+
};
|
|
1119
|
+
var carries = (declared, recorded) => {
|
|
1120
|
+
if (absent(declared))
|
|
1121
|
+
return true;
|
|
1122
|
+
if (absent(recorded))
|
|
1123
|
+
return false;
|
|
1124
|
+
if (Array.isArray(declared)) {
|
|
1125
|
+
if (!Array.isArray(recorded))
|
|
1126
|
+
return false;
|
|
1127
|
+
return declared.every((each, index) => carries(each, recorded[index]));
|
|
1128
|
+
}
|
|
1129
|
+
if (isPlainObject(declared)) {
|
|
1130
|
+
if (!isPlainObject(recorded))
|
|
1131
|
+
return false;
|
|
1132
|
+
return Object.entries(declared).every(([key, each]) => carries(each, recorded[key]));
|
|
1133
|
+
}
|
|
1134
|
+
return true;
|
|
1135
|
+
};
|
|
1136
|
+
var wholeDeclaration = (fqn, props) => Effect23.map(Effect23.serviceOption(Stack), (stack) => {
|
|
1137
|
+
const declared = Option3.isSome(stack) ? stack.value.resources[fqn] : undefined;
|
|
1138
|
+
return declared !== undefined && carries(declared.Props, props);
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
// src/ownership/rows.ts
|
|
1142
|
+
var storeOf = Effect24.gen(function* () {
|
|
1143
|
+
const stack = yield* Effect24.serviceOption(Stack2);
|
|
1144
|
+
const state = yield* Effect24.serviceOption(State);
|
|
1145
|
+
if (Option4.isNone(stack) || Option4.isNone(state))
|
|
1146
|
+
return;
|
|
1147
|
+
const store = yield* state.value;
|
|
1148
|
+
const where = { stack: stack.value.name, stage: stack.value.stage };
|
|
1149
|
+
return { resources: stack.value.resources, store, where };
|
|
1150
|
+
});
|
|
1151
|
+
var rowAt = (store, where, fqn) => store.get({ ...where, fqn }).pipe(Effect24.orElseSucceed(() => {
|
|
1152
|
+
return;
|
|
1153
|
+
}));
|
|
1154
|
+
var generationOf = (row, instanceId) => {
|
|
1155
|
+
let at = row;
|
|
1156
|
+
while (typeof at === "object" && at !== null && !isActionState(at)) {
|
|
1157
|
+
const generation = at;
|
|
1158
|
+
if (generation.instanceId === instanceId)
|
|
1159
|
+
return generation;
|
|
1160
|
+
at = generation.old;
|
|
1161
|
+
}
|
|
1162
|
+
return;
|
|
1163
|
+
};
|
|
1164
|
+
var recordedGeneration = (fqn, instanceId) => Effect24.gen(function* () {
|
|
1165
|
+
const found = yield* storeOf;
|
|
1166
|
+
if (found === undefined)
|
|
1167
|
+
return;
|
|
1168
|
+
for (const at of [fqn, ...found.resources[fqn]?.FormerFqns ?? []]) {
|
|
1169
|
+
const generation = generationOf(yield* rowAt(found.store, found.where, at), instanceId);
|
|
1170
|
+
if (generation !== undefined)
|
|
1171
|
+
return generation;
|
|
1172
|
+
}
|
|
1173
|
+
return;
|
|
1174
|
+
});
|
|
1175
|
+
var recordedInstance = (fqn, instanceId) => Effect24.gen(function* () {
|
|
1176
|
+
const generation = yield* recordedGeneration(fqn, instanceId);
|
|
1177
|
+
if (generation === undefined)
|
|
1178
|
+
return "absent";
|
|
1179
|
+
return (yield* wholeDeclaration(fqn, generation.props)) ? "whole" : "partial";
|
|
1180
|
+
});
|
|
1181
|
+
var isCreate = (fqn, instanceId) => Effect24.map(recordedGeneration(fqn, instanceId), (generation) => generation?.status === "creating");
|
|
1182
|
+
var forgetRefusedCreate = (fqn, instanceId) => Effect24.gen(function* () {
|
|
1183
|
+
const found = yield* storeOf;
|
|
1184
|
+
if (found === undefined)
|
|
1185
|
+
return;
|
|
1186
|
+
const row = yield* rowAt(found.store, found.where, fqn);
|
|
1187
|
+
const mine = row;
|
|
1188
|
+
if (mine?.status !== "creating" || mine.instanceId !== instanceId || mine.attr !== undefined) {
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
yield* found.store.delete({ ...found.where, fqn }).pipe(Effect24.ignore);
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
// src/ownership/adopt.ts
|
|
1195
|
+
var adoptEnabled = (fqn) => Effect25.gen(function* () {
|
|
1196
|
+
const stack = yield* Effect25.serviceOption(Stack3);
|
|
1197
|
+
const scoped = Option5.isSome(stack) ? stack.value.resources[fqn]?.Adopt : undefined;
|
|
1198
|
+
if (scoped !== undefined)
|
|
1199
|
+
return scoped;
|
|
1200
|
+
const policy = yield* Effect25.serviceOption(AdoptPolicy);
|
|
1201
|
+
if (Option5.isSome(policy))
|
|
1202
|
+
return policy.value;
|
|
1203
|
+
const context = yield* Effect25.serviceOption(AlchemyContext);
|
|
1204
|
+
return Option5.isSome(context) ? context.value.adopt : false;
|
|
1205
|
+
});
|
|
1206
|
+
var adoptable = (owner) => Effect25.gen(function* () {
|
|
1207
|
+
if (yield* isCreate(owner.fqn, owner.instanceId))
|
|
1208
|
+
return true;
|
|
1209
|
+
return yield* unfinished(owner.instanceId);
|
|
1210
|
+
});
|
|
1211
|
+
var adoptsAtApply = (owner) => Effect25.gen(function* () {
|
|
1212
|
+
if (!(yield* adoptEnabled(owner.fqn)))
|
|
1213
|
+
return false;
|
|
1214
|
+
return yield* adoptable(owner);
|
|
1077
1215
|
});
|
|
1078
|
-
var
|
|
1216
|
+
var refuseTakeover = (owner, what) => Effect25.gen(function* () {
|
|
1217
|
+
if (owner.output !== undefined)
|
|
1218
|
+
return;
|
|
1219
|
+
if (yield* resumes(owner.instanceId))
|
|
1220
|
+
return;
|
|
1221
|
+
const may = yield* adoptable(owner);
|
|
1222
|
+
if (may && (yield* adoptEnabled(owner.fqn)))
|
|
1223
|
+
return;
|
|
1224
|
+
yield* forgetRefusedCreate(owner.fqn, owner.instanceId);
|
|
1225
|
+
const remedy = may ? "Deploy with --adopt (or wrap the resource in adopt(true)) to take it over, or remove it first." : "It is the new identity of a replace, which --adopt does not cover: remove it first.";
|
|
1226
|
+
return yield* Effect25.die(new Error(`${what}: already exists, and this stack holds no state for it — the plan could not ask ` + "first (a prop was still an Output, a replace moved onto it, or it appeared after the " + `plan). Nothing was written. ${remedy}`));
|
|
1227
|
+
});
|
|
1228
|
+
var claimFor = (owner) => (what) => refuseTakeover(owner, what);
|
|
1229
|
+
|
|
1230
|
+
// src/ownership/probe.ts
|
|
1231
|
+
import { Unowned as Unowned2 } from "alchemy/AdoptPolicy";
|
|
1232
|
+
import * as Effect26 from "effect/Effect";
|
|
1233
|
+
var ownedRead = (ask, found, settled2) => Effect26.gen(function* () {
|
|
1234
|
+
if (found === undefined || ask.output !== undefined)
|
|
1235
|
+
return found;
|
|
1236
|
+
const recorded = yield* recordedInstance(ask.fqn, ask.instanceId);
|
|
1237
|
+
if (recorded === "absent")
|
|
1238
|
+
return Unowned2(found);
|
|
1239
|
+
if (recorded === "partial") {
|
|
1240
|
+
yield* Effect26.logWarning(`${ask.fqn}: the interrupted create's state row lacks part of the declaration (a prop ` + "was still an Output when it was written), so the live object is not proven ours");
|
|
1241
|
+
return Unowned2(found);
|
|
1242
|
+
}
|
|
1243
|
+
const unproven = (reason) => Effect26.as(Effect26.logWarning(`${ask.fqn}: the interrupted create's object could not be compared with its props ` + `(${reason instanceof Error ? reason.message : String(reason)}), so it is not treated as ours`), false);
|
|
1244
|
+
const ours = yield* settled2.pipe(Effect26.catch(unproven), Effect26.catchDefect(unproven));
|
|
1245
|
+
return ours ? found : Unowned2(found);
|
|
1246
|
+
});
|
|
1247
|
+
|
|
1248
|
+
// src/proxmox/lxc-wire.ts
|
|
1249
|
+
var pairs = (value, defaultKey) => value.split(",").map((part) => part.trim()).filter((part) => part !== "").map((part) => {
|
|
1250
|
+
const at = part.indexOf("=");
|
|
1251
|
+
if (at === -1)
|
|
1252
|
+
return [defaultKey ?? part, defaultKey === undefined ? "" : part];
|
|
1253
|
+
return [part.slice(0, at).trim(), part.slice(at + 1).trim()];
|
|
1254
|
+
});
|
|
1255
|
+
var DEFAULTS = {
|
|
1256
|
+
dev: { "deny-write": "0" },
|
|
1257
|
+
features: { force_rw_sys: "0", fuse: "0", keyctl: "0", mknod: "0", nesting: "0" },
|
|
1258
|
+
net: { firewall: "0", "host-managed": "0", link_down: "0", type: "veth" },
|
|
1259
|
+
volume: { backup: "0", keepattrs: "0", quota: "0", replicate: "1", ro: "0", shared: "0" }
|
|
1260
|
+
};
|
|
1261
|
+
var FLAGS = new Set([
|
|
1262
|
+
"acl",
|
|
1263
|
+
"backup",
|
|
1264
|
+
"deny-write",
|
|
1265
|
+
"firewall",
|
|
1266
|
+
"force_rw_sys",
|
|
1267
|
+
"fuse",
|
|
1268
|
+
"host-managed",
|
|
1269
|
+
"keepattrs",
|
|
1270
|
+
"keyctl",
|
|
1271
|
+
"link_down",
|
|
1272
|
+
"mknod",
|
|
1273
|
+
"nesting",
|
|
1274
|
+
"quota",
|
|
1275
|
+
"replicate",
|
|
1276
|
+
"ro",
|
|
1277
|
+
"shared"
|
|
1278
|
+
]);
|
|
1279
|
+
var SETS = new Set(["mount", "mountoptions"]);
|
|
1280
|
+
var canonicalValue = (key, value) => {
|
|
1281
|
+
if (FLAGS.has(key))
|
|
1282
|
+
return canonicalToken(value);
|
|
1283
|
+
if (SETS.has(key))
|
|
1284
|
+
return [...new Set(value.split(";").map((part) => part.trim()))].sort().join(";");
|
|
1285
|
+
if (key === "hwaddr")
|
|
1286
|
+
return value.toUpperCase();
|
|
1287
|
+
return value;
|
|
1288
|
+
};
|
|
1289
|
+
var optionMap = (value, family, defaultKey) => {
|
|
1290
|
+
const defaults = DEFAULTS[family] ?? {};
|
|
1291
|
+
const out = new Map;
|
|
1292
|
+
for (const [key, raw] of pairs(value, defaultKey)) {
|
|
1293
|
+
const canonical = canonicalValue(key, raw);
|
|
1294
|
+
if (defaults[key] !== canonical)
|
|
1295
|
+
out.set(key, canonical);
|
|
1296
|
+
}
|
|
1297
|
+
return out;
|
|
1298
|
+
};
|
|
1299
|
+
var sameMap = (left, right) => left.size === right.size && [...left].every(([key, value]) => right.get(key) === value);
|
|
1300
|
+
var sortedForm = (map6) => [...map6].map(([key, value]) => `${key}=${value}`).sort().join(",");
|
|
1301
|
+
var printMap = (map6, lead = []) => [...lead.filter((key) => map6.has(key)), ...[...map6.keys()].filter((k) => !lead.includes(k))].map((key) => `${key}=${map6.get(key) ?? ""}`).join(",");
|
|
1302
|
+
var netMaps = (declared, live) => {
|
|
1303
|
+
const want = optionMap(declared, "net");
|
|
1304
|
+
const have = optionMap(live, "net");
|
|
1305
|
+
if (!want.has("hwaddr"))
|
|
1306
|
+
have.delete("hwaddr");
|
|
1307
|
+
return { have, want };
|
|
1308
|
+
};
|
|
1309
|
+
var netWrite = (declared, live) => {
|
|
1310
|
+
const want = new Map(pairs(declared));
|
|
1311
|
+
const mac = live === undefined ? undefined : new Map(pairs(live)).get("hwaddr");
|
|
1312
|
+
if (!want.has("hwaddr") && mac !== undefined)
|
|
1313
|
+
want.set("hwaddr", mac);
|
|
1314
|
+
return printMap(want, ["name"]);
|
|
1315
|
+
};
|
|
1316
|
+
var tagSet = (value) => [...new Set(value.split(/[;,\s]+/).filter((tag) => tag !== ""))].map((tag) => tag.toLowerCase()).sort().join(";");
|
|
1317
|
+
var orderedList = (value) => value.split(/[;,\s]+/).filter((part) => part !== "").join(" ");
|
|
1318
|
+
var descriptionText = (value) => value.replace(/\s+$/, "");
|
|
1319
|
+
var SCALAR_KINDS = {
|
|
1320
|
+
arch: "text",
|
|
1321
|
+
cmode: "text",
|
|
1322
|
+
console: "flag",
|
|
1323
|
+
cores: "number",
|
|
1324
|
+
cpulimit: "number",
|
|
1325
|
+
cpuunits: "number",
|
|
1326
|
+
description: "form",
|
|
1327
|
+
features: "form",
|
|
1328
|
+
hostname: "text",
|
|
1329
|
+
memory: "number",
|
|
1330
|
+
nameserver: "form",
|
|
1331
|
+
onboot: "flag",
|
|
1332
|
+
ostype: "text",
|
|
1333
|
+
protection: "flag",
|
|
1334
|
+
searchdomain: "form",
|
|
1335
|
+
startup: "form",
|
|
1336
|
+
swap: "number",
|
|
1337
|
+
tags: "form",
|
|
1338
|
+
timezone: "text",
|
|
1339
|
+
tty: "number"
|
|
1340
|
+
};
|
|
1341
|
+
var CLEARABLE = new Set([
|
|
1342
|
+
"description",
|
|
1343
|
+
"features",
|
|
1344
|
+
"nameserver",
|
|
1345
|
+
"searchdomain",
|
|
1346
|
+
"startup",
|
|
1347
|
+
"tags",
|
|
1348
|
+
"timezone"
|
|
1349
|
+
]);
|
|
1350
|
+
var SCALAR_DEFAULTS = {
|
|
1351
|
+
arch: "amd64",
|
|
1352
|
+
cmode: "tty",
|
|
1353
|
+
console: "1",
|
|
1354
|
+
cpulimit: "0",
|
|
1355
|
+
memory: "512",
|
|
1356
|
+
onboot: "0",
|
|
1357
|
+
protection: "0",
|
|
1358
|
+
swap: "512",
|
|
1359
|
+
tty: "2"
|
|
1360
|
+
};
|
|
1361
|
+
var scalarForm = (key, value) => {
|
|
1362
|
+
const kind2 = SCALAR_KINDS[key];
|
|
1363
|
+
if (kind2 === "number")
|
|
1364
|
+
return String(Number(value));
|
|
1365
|
+
if (kind2 === "flag")
|
|
1366
|
+
return canonicalToken(value);
|
|
1367
|
+
if (key === "description")
|
|
1368
|
+
return descriptionText(value);
|
|
1369
|
+
if (key === "features")
|
|
1370
|
+
return sortedForm(optionMap(value, "features"));
|
|
1371
|
+
if (key === "nameserver" || key === "searchdomain")
|
|
1372
|
+
return orderedList(value);
|
|
1373
|
+
if (key === "startup")
|
|
1374
|
+
return propertyString(value, "order");
|
|
1375
|
+
if (key === "tags")
|
|
1376
|
+
return tagSet(value);
|
|
1377
|
+
return value;
|
|
1378
|
+
};
|
|
1379
|
+
|
|
1380
|
+
// src/proxmox/lxc-props.ts
|
|
1381
|
+
var UNSTORED = new Set([
|
|
1382
|
+
"description",
|
|
1383
|
+
"digest",
|
|
1384
|
+
"env",
|
|
1385
|
+
"lock",
|
|
1386
|
+
"lxc",
|
|
1387
|
+
"pending"
|
|
1388
|
+
]);
|
|
1389
|
+
var INDEXED = {
|
|
1390
|
+
dev: 256,
|
|
1391
|
+
mp: 256,
|
|
1392
|
+
net: 32
|
|
1393
|
+
};
|
|
1394
|
+
var indexedKey = (key) => {
|
|
1395
|
+
const match = /^(mp|net|dev)(0|[1-9]\d*)$/.exec(key);
|
|
1396
|
+
if (match === null)
|
|
1397
|
+
return;
|
|
1398
|
+
const family = match[1];
|
|
1399
|
+
const index = Number(match[2]);
|
|
1400
|
+
return index < INDEXED[family] ? [family, index] : undefined;
|
|
1401
|
+
};
|
|
1402
|
+
var NOT_CONFIG = new Set([
|
|
1403
|
+
"node",
|
|
1404
|
+
"ostemplate",
|
|
1405
|
+
"start",
|
|
1406
|
+
"target",
|
|
1407
|
+
"vmid"
|
|
1408
|
+
]);
|
|
1409
|
+
var declaredValue = (props, key) => props[key];
|
|
1410
|
+
var declaredKeys = (props) => Object.keys(props).filter((key) => !NOT_CONFIG.has(key) && declaredValue(props, key) !== undefined).sort();
|
|
1411
|
+
var wireValue = (value) => value === true ? "1" : value === false ? "0" : String(value);
|
|
1412
|
+
var isManagedKey = (key) => SCALAR_KINDS[key] !== undefined || key === "unprivileged" || key === "rootfs" || indexedKey(key) !== undefined;
|
|
1413
|
+
var storedConfig = (live) => {
|
|
1414
|
+
const out = {};
|
|
1415
|
+
for (const [key, value] of Object.entries(live)) {
|
|
1416
|
+
if (!isManagedKey(key) || UNSTORED.has(key))
|
|
1417
|
+
continue;
|
|
1418
|
+
if (value === null || value === undefined || typeof value === "object")
|
|
1419
|
+
continue;
|
|
1420
|
+
out[key] = wireValue(value);
|
|
1421
|
+
}
|
|
1422
|
+
return out;
|
|
1423
|
+
};
|
|
1424
|
+
var rawKeysOf = (live) => {
|
|
1425
|
+
const raw = live["lxc"];
|
|
1426
|
+
if (!Array.isArray(raw))
|
|
1427
|
+
return [];
|
|
1428
|
+
return raw.map((pair) => Array.isArray(pair) ? String(pair[0]) : "").filter((key) => key !== "");
|
|
1429
|
+
};
|
|
1430
|
+
|
|
1431
|
+
// src/proxmox/lxc-volume.ts
|
|
1432
|
+
var NEW_DISK = /^([^:\s]+):(\d+(?:\.\d+)?)$/;
|
|
1433
|
+
var UNITS = {
|
|
1434
|
+
G: 1024 ** 3,
|
|
1435
|
+
K: 1024,
|
|
1436
|
+
M: 1024 ** 2,
|
|
1437
|
+
T: 1024 ** 4
|
|
1438
|
+
};
|
|
1439
|
+
var sizeBytes = (text2) => {
|
|
1440
|
+
const match = /^(\d+(?:\.\d+)?)([KMGT])?$/.exec(text2.trim());
|
|
1441
|
+
if (match === null)
|
|
1442
|
+
return;
|
|
1443
|
+
return Math.trunc(Number(match[1]) * (match[2] === undefined ? 1 : UNITS[match[2]] ?? 1));
|
|
1444
|
+
};
|
|
1445
|
+
var parseVolume = (text2) => {
|
|
1446
|
+
const [first] = pairs(text2, "volume");
|
|
1447
|
+
const spec2 = first?.[0] === "volume" ? first[1] : "";
|
|
1448
|
+
const options = optionMap(text2, "volume", "volume");
|
|
1449
|
+
options.delete("volume");
|
|
1450
|
+
const sizeText = options.get("size");
|
|
1451
|
+
options.delete("size");
|
|
1452
|
+
const fresh = NEW_DISK.exec(spec2);
|
|
1453
|
+
if (fresh !== null) {
|
|
1454
|
+
const gib = `${fresh[2] ?? "0"}G`;
|
|
1455
|
+
return {
|
|
1456
|
+
kind: "new",
|
|
1457
|
+
options,
|
|
1458
|
+
size: sizeBytes(gib),
|
|
1459
|
+
sizeText: gib,
|
|
1460
|
+
source: fresh[1] ?? "",
|
|
1461
|
+
volname: ""
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
const size = sizeText === undefined ? undefined : sizeBytes(sizeText);
|
|
1465
|
+
if (spec2.startsWith("/")) {
|
|
1466
|
+
const kind2 = spec2.startsWith("/dev/") ? "device" : "bind";
|
|
1467
|
+
return { kind: kind2, options, size, sizeText, source: spec2, volname: "" };
|
|
1468
|
+
}
|
|
1469
|
+
const at = spec2.indexOf(":");
|
|
1470
|
+
return {
|
|
1471
|
+
kind: "volume",
|
|
1472
|
+
options,
|
|
1473
|
+
size,
|
|
1474
|
+
sizeText,
|
|
1475
|
+
source: at === -1 ? spec2 : spec2.slice(0, at),
|
|
1476
|
+
volname: at === -1 ? "" : spec2.slice(at + 1)
|
|
1477
|
+
};
|
|
1478
|
+
};
|
|
1479
|
+
var isHostPath = (volume) => volume.kind === "bind" || volume.kind === "device";
|
|
1480
|
+
var rewrite = (live, declared) => {
|
|
1481
|
+
const parts = [`${live.source}:${live.volname}`];
|
|
1482
|
+
for (const [key, value] of declared.options)
|
|
1483
|
+
parts.push(`${key}=${value}`);
|
|
1484
|
+
if (live.sizeText !== undefined)
|
|
1485
|
+
parts.push(`size=${live.sizeText}`);
|
|
1486
|
+
return parts.join(",");
|
|
1487
|
+
};
|
|
1488
|
+
var judgeVolume = (key, declared, live) => {
|
|
1489
|
+
const want = parseVolume(declared);
|
|
1490
|
+
const have = parseVolume(live);
|
|
1491
|
+
if (isHostPath(want) || isHostPath(have)) {
|
|
1492
|
+
const same2 = want.kind === have.kind && want.source === have.source && sameMap(want.options, have.options);
|
|
1493
|
+
return same2 ? {} : { rootOnly: true };
|
|
1494
|
+
}
|
|
1495
|
+
if (want.source !== have.source) {
|
|
1496
|
+
return {
|
|
1497
|
+
refuse: `${key}: the volume is on ${have.source}, declared on ${want.source}. Moving it is ` + "`pct move-volume` (a copy), not a config update -- move it by hand, then declare it."
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
if (want.kind === "volume" && want.volname !== have.volname) {
|
|
1501
|
+
return {
|
|
1502
|
+
refuse: `${key}: declared volume ${want.volname} is not the live ${have.volname}. Writing it would ` + "detach the live volume to unusedN; attach or swap volumes by hand."
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
let resize;
|
|
1506
|
+
if (want.size !== undefined && have.size !== undefined && want.size !== have.size) {
|
|
1507
|
+
if (want.size < have.size) {
|
|
1508
|
+
return {
|
|
1509
|
+
refuse: `${key}: declared ${want.sizeText ?? "?"} is smaller than the live ` + `${have.sizeText ?? "?"}. PVE cannot shrink a container volume ("unable to shrink disk ` + 'size"), and replacing the guest to get there is not an update this resource makes.'
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
resize = want.sizeText;
|
|
1513
|
+
}
|
|
1514
|
+
const put = sameMap(want.options, have.options) ? undefined : rewrite(have, want);
|
|
1515
|
+
return {
|
|
1516
|
+
...put === undefined ? {} : { put },
|
|
1517
|
+
...resize === undefined ? {} : { resize }
|
|
1518
|
+
};
|
|
1519
|
+
};
|
|
1520
|
+
|
|
1521
|
+
// src/proxmox/lxc-judge.ts
|
|
1522
|
+
var text2 = (value) => value === undefined || value === null ? undefined : wireValue(value);
|
|
1523
|
+
var rootOnly = (vmid, key, value, why) => `${key}: ${why} PVE lets only root@pam write it, and an API token is never root@pam. Run ` + `\`pct set ${String(vmid)} ${value === "" ? `--delete ${key}` : `--${key} '${value}'`}\` ` + "on the node, then plan again.";
|
|
1524
|
+
var rawFeatures = (value) => new Map(pairs(value).map(([key, entry]) => [key, key === "mount" ? entry : canonicalToken(entry)]));
|
|
1525
|
+
var featuresNeedRoot = (live, sent, unprivileged) => {
|
|
1526
|
+
if (!unprivileged)
|
|
1527
|
+
return true;
|
|
1528
|
+
const have = rawFeatures(live);
|
|
1529
|
+
if (sent === undefined)
|
|
1530
|
+
return !(have.size === 0 || have.size === 1 && have.has("nesting"));
|
|
1531
|
+
const want = rawFeatures(sent);
|
|
1532
|
+
const keys = new Set([...have.keys(), ...want.keys()]);
|
|
1533
|
+
return [...keys].some((key) => key !== "nesting" && (have.get(key) ?? "") !== (want.get(key) ?? ""));
|
|
1534
|
+
};
|
|
1535
|
+
var featureWhy = (unprivileged) => unprivileged ? "A feature other than nesting." : "Any feature on a privileged guest.";
|
|
1536
|
+
var judge = (props, live) => {
|
|
1537
|
+
const put = {};
|
|
1538
|
+
const clear = [];
|
|
1539
|
+
const resize = [];
|
|
1540
|
+
const refuse = [];
|
|
1541
|
+
const drift = new Set;
|
|
1542
|
+
const unprivileged = bool(live["unprivileged"], false);
|
|
1543
|
+
for (const key of declaredKeys(props)) {
|
|
1544
|
+
const want = wireValue(declaredValue(props, key));
|
|
1545
|
+
const have = text2(live[key]);
|
|
1546
|
+
const indexed = indexedKey(key);
|
|
1547
|
+
if (key === "unprivileged") {
|
|
1548
|
+
if (canonicalToken(want) !== canonicalToken(have ?? "0")) {
|
|
1549
|
+
drift.add(key);
|
|
1550
|
+
refuse.push(`unprivileged: live ${have ?? "0"}, declared ${want}. PVE refuses to change it ("unable ` + 'to modify read-only option"); only a backup and restore can, and that is a new guest.');
|
|
1551
|
+
}
|
|
1552
|
+
continue;
|
|
1553
|
+
}
|
|
1554
|
+
if (key === "rootfs" || indexed?.[0] === "mp") {
|
|
1555
|
+
if (want === "") {
|
|
1556
|
+
if (have === undefined)
|
|
1557
|
+
continue;
|
|
1558
|
+
drift.add(key);
|
|
1559
|
+
refuse.push(`${key}: detaching a mount point moves its volume to unusedN -- do it by hand.`);
|
|
1560
|
+
} else if (have === undefined) {
|
|
1561
|
+
drift.add(key);
|
|
1562
|
+
if (isHostPath(parseVolume(want))) {
|
|
1563
|
+
refuse.push(rootOnly(props.vmid, key, want, "A bind or device mount point."));
|
|
1564
|
+
} else
|
|
1565
|
+
put[key] = want;
|
|
1566
|
+
} else {
|
|
1567
|
+
const verdict = judgeVolume(key, want, have);
|
|
1568
|
+
if (verdict.refuse !== undefined)
|
|
1569
|
+
refuse.push(verdict.refuse);
|
|
1570
|
+
if (verdict.rootOnly === true) {
|
|
1571
|
+
refuse.push(rootOnly(props.vmid, key, want, "A bind or device mount point."));
|
|
1572
|
+
}
|
|
1573
|
+
if (verdict.put !== undefined)
|
|
1574
|
+
put[key] = verdict.put;
|
|
1575
|
+
if (verdict.resize !== undefined)
|
|
1576
|
+
resize.push({ disk: key, size: verdict.resize });
|
|
1577
|
+
if (Object.keys(verdict).length > 0)
|
|
1578
|
+
drift.add(key);
|
|
1579
|
+
}
|
|
1580
|
+
continue;
|
|
1581
|
+
}
|
|
1582
|
+
if (indexed?.[0] === "dev") {
|
|
1583
|
+
const same2 = want === "" ? have === undefined : have !== undefined && devSame(want, have);
|
|
1584
|
+
if (!same2) {
|
|
1585
|
+
drift.add(key);
|
|
1586
|
+
refuse.push(rootOnly(props.vmid, key, want, "Device passthrough."));
|
|
1587
|
+
}
|
|
1588
|
+
continue;
|
|
1589
|
+
}
|
|
1590
|
+
if (indexed?.[0] === "net") {
|
|
1591
|
+
if (want === "") {
|
|
1592
|
+
if (have !== undefined) {
|
|
1593
|
+
drift.add(key);
|
|
1594
|
+
clear.push(key);
|
|
1595
|
+
}
|
|
1596
|
+
} else if (have === undefined) {
|
|
1597
|
+
drift.add(key);
|
|
1598
|
+
put[key] = want;
|
|
1599
|
+
} else {
|
|
1600
|
+
const maps = netMaps(want, have);
|
|
1601
|
+
if (!sameMap(maps.want, maps.have)) {
|
|
1602
|
+
drift.add(key);
|
|
1603
|
+
put[key] = netWrite(want, have);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
continue;
|
|
1607
|
+
}
|
|
1608
|
+
if (!isManagedKey(key)) {
|
|
1609
|
+
drift.add(key);
|
|
1610
|
+
refuse.push(`${key}: not a config key this resource manages.`);
|
|
1611
|
+
continue;
|
|
1612
|
+
}
|
|
1613
|
+
const wanted = want === "" ? "" : scalarForm(key, want);
|
|
1614
|
+
const current = have ?? SCALAR_DEFAULTS[key];
|
|
1615
|
+
const had = current === undefined ? "" : scalarForm(key, current);
|
|
1616
|
+
if (wanted === had)
|
|
1617
|
+
continue;
|
|
1618
|
+
drift.add(key);
|
|
1619
|
+
const next = key === "features" ? wanted : want;
|
|
1620
|
+
if (key === "features" && featuresNeedRoot(have ?? "", next || undefined, unprivileged)) {
|
|
1621
|
+
refuse.push(rootOnly(props.vmid, key, want, featureWhy(unprivileged)));
|
|
1622
|
+
} else if (next === "" && CLEARABLE.has(key)) {
|
|
1623
|
+
clear.push(key);
|
|
1624
|
+
} else if (next === "") {
|
|
1625
|
+
refuse.push(`${key}: cannot be removed; declare the value it should have.`);
|
|
1626
|
+
} else
|
|
1627
|
+
put[key] = next;
|
|
1628
|
+
}
|
|
1629
|
+
return { clear, drift: [...drift].sort(), put, refuse, resize };
|
|
1630
|
+
};
|
|
1631
|
+
var devSame = (want, have) => sortedForm(optionMap(want, "dev", "path")) === sortedForm(optionMap(have, "dev", "path"));
|
|
1632
|
+
|
|
1633
|
+
// src/proxmox/lxc-create-form.ts
|
|
1634
|
+
var existingVolume = (key, value) => `${key}: ${value} names an existing volume, and PVE would unpack the template onto it. A ` + "create allocates new volumes only: declare `storage:GiB`, and attach an existing volume by " + "hand after the create.";
|
|
1635
|
+
var createRefusals = (props) => {
|
|
1636
|
+
const refuse = [];
|
|
1637
|
+
if (props.ostemplate === undefined || props.ostemplate === "") {
|
|
1638
|
+
refuse.push(`CT ${String(props.vmid)} does not exist on ${props.node}, and without \`ostemplate\` ` + "there is nothing to create it from. To adopt a guest, declare the node it is on.");
|
|
1639
|
+
}
|
|
1640
|
+
const unprivileged = props.unprivileged === undefined ? true : bool(props.unprivileged);
|
|
1641
|
+
for (const key of declaredKeys(props)) {
|
|
1642
|
+
const value = wireValue(declaredValue(props, key));
|
|
1643
|
+
const family = indexedKey(key)?.[0];
|
|
1644
|
+
if (!isManagedKey(key))
|
|
1645
|
+
refuse.push(`${key}: not a config key this resource manages.`);
|
|
1646
|
+
if (value === "")
|
|
1647
|
+
continue;
|
|
1648
|
+
if (family === "dev")
|
|
1649
|
+
refuse.push(rootOnly(props.vmid, key, value, "Device passthrough."));
|
|
1650
|
+
const volume = family === "mp" || key === "rootfs" ? parseVolume(value) : undefined;
|
|
1651
|
+
if (volume !== undefined && isHostPath(volume)) {
|
|
1652
|
+
refuse.push(rootOnly(props.vmid, key, value, "A bind or device mount point."));
|
|
1653
|
+
}
|
|
1654
|
+
if (volume?.kind === "volume")
|
|
1655
|
+
refuse.push(existingVolume(key, value));
|
|
1656
|
+
if (key === "features" && featuresNeedRoot("", value, unprivileged)) {
|
|
1657
|
+
refuse.push(rootOnly(props.vmid, key, value, featureWhy(unprivileged)));
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
return refuse;
|
|
1661
|
+
};
|
|
1662
|
+
var createForm3 = (props) => {
|
|
1663
|
+
const form = { vmid: String(props.vmid) };
|
|
1664
|
+
if (props.ostemplate !== undefined)
|
|
1665
|
+
form["ostemplate"] = props.ostemplate;
|
|
1666
|
+
if (props.start !== undefined)
|
|
1667
|
+
form["start"] = wireValue(props.start);
|
|
1668
|
+
for (const key of declaredKeys(props)) {
|
|
1669
|
+
const value = wireValue(declaredValue(props, key));
|
|
1670
|
+
if (value === "")
|
|
1671
|
+
continue;
|
|
1672
|
+
form[key] = key === "features" ? scalarForm(key, value) : value;
|
|
1673
|
+
}
|
|
1674
|
+
return form;
|
|
1675
|
+
};
|
|
1676
|
+
|
|
1677
|
+
// src/proxmox/lxc-identity.ts
|
|
1678
|
+
var plain = (news, key) => {
|
|
1679
|
+
const value = news[key];
|
|
1680
|
+
return typeof value === "string" || typeof value === "number" ? value : undefined;
|
|
1681
|
+
};
|
|
1682
|
+
var identityRefusals = (news, olds, output) => {
|
|
1683
|
+
const was = output?.vmid ?? olds.vmid;
|
|
1684
|
+
const vmid = plain(news, "vmid");
|
|
1685
|
+
const template = plain(news, "ostemplate");
|
|
1686
|
+
const reasons = [];
|
|
1687
|
+
if (vmid !== undefined && vmid !== was) {
|
|
1688
|
+
reasons.push(`vmid: ${String(was)} -> ${String(vmid)} is a different guest. Declare it as a new ` + "resource; this one is retained when its declaration goes.");
|
|
1689
|
+
}
|
|
1690
|
+
if (template !== undefined && olds.ostemplate !== undefined && template !== olds.ostemplate) {
|
|
1691
|
+
reasons.push("ostemplate: a template is consumed at create; another one is a new guest.");
|
|
1692
|
+
}
|
|
1693
|
+
return reasons;
|
|
1694
|
+
};
|
|
1695
|
+
|
|
1696
|
+
// src/proxmox/lxc-lifecycle.ts
|
|
1697
|
+
import * as Effect28 from "effect/Effect";
|
|
1698
|
+
|
|
1699
|
+
// src/proxmox/network-apply-read.ts
|
|
1700
|
+
import * as Effect27 from "effect/Effect";
|
|
1701
|
+
var stagedDiff = (target, node) => Effect27.gen(function* () {
|
|
1702
|
+
const path = `nodes/${node}/network`;
|
|
1703
|
+
const credential = yield* leased(target, "read");
|
|
1704
|
+
const envelope = yield* pveEnvelopeWith(target, credential, "GET", path);
|
|
1705
|
+
return text(envelope.changes);
|
|
1706
|
+
});
|
|
1707
|
+
var pendingLines = (diff) => {
|
|
1708
|
+
if (diff === "")
|
|
1709
|
+
return 0;
|
|
1710
|
+
const changed = diff.split(`
|
|
1711
|
+
`).filter((line) => (line.startsWith("+") || line.startsWith("-")) && !line.startsWith("+++") && !line.startsWith("---")).length;
|
|
1712
|
+
return Math.max(changed, 1);
|
|
1713
|
+
};
|
|
1714
|
+
var pendingCount = (target, node) => stagedDiff(target, node).pipe(Effect27.map(pendingLines));
|
|
1715
|
+
var clusterHealth = (target) => pve(target, "read", "GET", "cluster/status").pipe(Effect27.map((rows) => {
|
|
1716
|
+
const all = rows ?? [];
|
|
1717
|
+
const cluster = all.find((row) => row.type === "cluster");
|
|
1718
|
+
return {
|
|
1719
|
+
offline: all.filter((row) => row.type === "node" && !bool(row.online)).map((row) => text(row.name, "<unnamed>")),
|
|
1720
|
+
quorate: cluster === undefined ? true : bool(cluster.quorate)
|
|
1721
|
+
};
|
|
1722
|
+
}));
|
|
1723
|
+
var degradedReason = (target) => clusterHealth(target).pipe(Effect27.map((health) => health.quorate && health.offline.length === 0 ? undefined : `quorate=${String(health.quorate)} offline=[${health.offline.join(", ")}]`));
|
|
1724
|
+
var UNREACHABLE = "the task could not be read -- the node stopped answering, or this lease cannot see it";
|
|
1725
|
+
var STILL_RUNNING = "still running when this provider stopped waiting";
|
|
1726
|
+
var ATTEMPTS = 30;
|
|
1727
|
+
var awaitTask = (target, credential, node, upid, attempts = ATTEMPTS) => Effect27.gen(function* () {
|
|
1728
|
+
const path = `nodes/${node}/tasks/${upid}/status`;
|
|
1729
|
+
for (let attempt = 0;attempt < attempts; attempt += 1) {
|
|
1730
|
+
const status = yield* pveWith(target, credential, "GET", path).pipe(Effect27.orElseSucceed(() => {
|
|
1731
|
+
return;
|
|
1732
|
+
}));
|
|
1733
|
+
if (status === undefined)
|
|
1734
|
+
return UNREACHABLE;
|
|
1735
|
+
if (status.status === "stopped")
|
|
1736
|
+
return text(status.exitstatus, "stopped with no exitstatus");
|
|
1737
|
+
yield* Effect27.sleep("1 second");
|
|
1738
|
+
}
|
|
1739
|
+
return STILL_RUNNING;
|
|
1740
|
+
});
|
|
1741
|
+
|
|
1742
|
+
// src/proxmox/lxc-lifecycle.ts
|
|
1743
|
+
class LxcRefusedError extends Error {
|
|
1744
|
+
name = "LxcRefusedError";
|
|
1745
|
+
}
|
|
1746
|
+
var guestPath = (where) => `nodes/${where.node}/lxc/${String(where.vmid)}`;
|
|
1747
|
+
var CREATE_POLLS = 180;
|
|
1748
|
+
var TASK_POLLS = 60;
|
|
1749
|
+
var isServerError = (error) => error instanceof PveError && error.status === 500;
|
|
1750
|
+
var readLive = (where) => Effect28.gen(function* () {
|
|
1751
|
+
const answer = yield* pve(where.target, "read", "GET", `${guestPath(where)}/config`).pipe(Effect28.map((data) => ({ data, error: undefined })), Effect28.catchIf(isServerError, (error) => Effect28.succeed({ data: undefined, error })));
|
|
1752
|
+
if (answer.data !== undefined && answer.data !== null)
|
|
1753
|
+
return answer.data;
|
|
1754
|
+
const rows = yield* pve(where.target, "read", "GET", "cluster/resources?type=vm");
|
|
1755
|
+
const found = (rows ?? []).find((row) => Number(row.vmid) === where.vmid);
|
|
1756
|
+
if (found !== undefined && found.node === where.node && found.type === "lxc") {
|
|
1757
|
+
return yield* Effect28.fail(answer.error ?? new LxcRefusedError(`CT ${String(where.vmid)} is listed but its config read empty.`));
|
|
1758
|
+
}
|
|
1759
|
+
if (found !== undefined) {
|
|
1760
|
+
return yield* Effect28.fail(new LxcRefusedError(`vmid ${String(where.vmid)} is a ${text(found.type, "guest")} on ` + `${text(found.node, "?")}, not a container on ${where.node}. A migration is not an ` + "update and vmids are cluster-wide: declare the node it is on, or pick a free vmid."));
|
|
1761
|
+
}
|
|
1762
|
+
return;
|
|
1763
|
+
});
|
|
1764
|
+
var attributesOf = (where, live) => ({
|
|
1765
|
+
config: storedConfig(live),
|
|
1766
|
+
node: where.node,
|
|
1767
|
+
rawKeys: rawKeysOf(live),
|
|
1768
|
+
vmid: where.vmid
|
|
1769
|
+
});
|
|
1770
|
+
var task = (where, method, path, what, polls, form) => Effect28.gen(function* () {
|
|
1771
|
+
const credential = yield* mint(where.target, "provision");
|
|
1772
|
+
const upid = text(yield* pveWith(where.target, credential, method, path, form));
|
|
1773
|
+
if (upid === "") {
|
|
1774
|
+
return yield* Effect28.fail(new LxcRefusedError(`${what}: PVE returned no task id, so the write cannot be confirmed and is not claimed.`));
|
|
1775
|
+
}
|
|
1776
|
+
const exit = yield* awaitTask(where.target, credential, where.node, upid, polls);
|
|
1777
|
+
if (exit !== "OK") {
|
|
1778
|
+
return yield* Effect28.fail(new LxcRefusedError(`${what}: task ${upid} ended "${exit}". Read \`pvesh get /nodes/${where.node}/tasks/` + `${upid}/log\` before running the deploy again.`));
|
|
1779
|
+
}
|
|
1780
|
+
});
|
|
1781
|
+
var createGuest = (props) => task(props, "POST", `nodes/${props.node}/lxc`, `create CT ${String(props.vmid)}`, CREATE_POLLS, createForm3(props));
|
|
1782
|
+
var updateGuest = (props, change, digest) => Effect28.gen(function* () {
|
|
1783
|
+
if (Object.keys(change.put).length > 0 || change.clear.length > 0) {
|
|
1784
|
+
const form = withClears({ ...change.put, ...digest === "" ? {} : { digest } }, change.clear);
|
|
1785
|
+
yield* pve(props.target, "provision", "PUT", `${guestPath(props)}/config`, form);
|
|
1786
|
+
}
|
|
1787
|
+
for (const grow of change.resize) {
|
|
1788
|
+
yield* task(props, "PUT", `${guestPath(props)}/resize`, `grow ${grow.disk} of CT ${String(props.vmid)} to ${grow.size}`, TASK_POLLS, { disk: grow.disk, size: grow.size });
|
|
1789
|
+
}
|
|
1790
|
+
});
|
|
1791
|
+
var destroyGuest = (where, declared) => Effect28.gen(function* () {
|
|
1792
|
+
const live = yield* readLive(where);
|
|
1793
|
+
if (live === undefined)
|
|
1794
|
+
return;
|
|
1795
|
+
const drift = judge(declared, live).drift;
|
|
1796
|
+
if (drift.length > 0) {
|
|
1797
|
+
return yield* Effect28.fail(new LxcRefusedError(`destroy CT ${String(where.vmid)}: the guest on ${where.node} no longer matches its last ` + `declaration (${drift.join(", ")}), so it may not be the guest this stack made. ` + "Nothing was deleted. Delete it by hand if it should go, or declare it again."));
|
|
1798
|
+
}
|
|
1799
|
+
yield* task(where, "DELETE", guestPath(where), `destroy CT ${String(where.vmid)}`, TASK_POLLS);
|
|
1800
|
+
});
|
|
1801
|
+
|
|
1802
|
+
// src/proxmox/lxc.ts
|
|
1803
|
+
var ProxmoxLxc = Resource13("Proxmox.Lxc", { defaultRemovalPolicy: "retain" });
|
|
1804
|
+
var refused = (reasons) => Effect29.fail(new LxcRefusedError(reasons.join(`
|
|
1805
|
+
`)));
|
|
1806
|
+
var diff = (news, olds, output) => Effect29.gen(function* () {
|
|
1807
|
+
const identity2 = identityRefusals(news, olds, output);
|
|
1808
|
+
if (identity2.length > 0)
|
|
1809
|
+
return yield* refused(identity2);
|
|
1810
|
+
if (output === undefined)
|
|
1811
|
+
return;
|
|
1812
|
+
if (!isResolved6(news))
|
|
1813
|
+
return;
|
|
1814
|
+
const live = yield* readLive(news);
|
|
1815
|
+
if (live === undefined) {
|
|
1816
|
+
const refusals = createRefusals(news);
|
|
1817
|
+
if (refusals.length > 0)
|
|
1818
|
+
return yield* refused(refusals);
|
|
1819
|
+
yield* Effect29.logWarning(`Proxmox.Lxc ${news.node}/${String(news.vmid)}: the cluster lists no guest with this ` + "vmid -- a deploy CREATES it again from ostemplate, with new, empty volumes.");
|
|
1820
|
+
return { action: "update" };
|
|
1821
|
+
}
|
|
1822
|
+
const change = judge(news, live);
|
|
1823
|
+
if (change.refuse.length > 0)
|
|
1824
|
+
return yield* refused(change.refuse);
|
|
1825
|
+
const moved = news.node !== output.node;
|
|
1826
|
+
if (change.drift.length === 0 && !moved)
|
|
1827
|
+
return { action: "noop" };
|
|
1828
|
+
const what = change.drift.length === 0 ? `the guest is on ${news.node} now, not ${output.node} -- a deploy re-records it, ` + "writing nothing." : `live config differs from the declaration in ${change.drift.join(", ")} -- a ` + "deploy writes these.";
|
|
1829
|
+
yield* Effect29.logWarning(`Proxmox.Lxc ${news.node}/${String(news.vmid)}: ${what}`);
|
|
1830
|
+
return { action: "update" };
|
|
1831
|
+
});
|
|
1832
|
+
var reconcile = (owner, news) => Effect29.gen(function* () {
|
|
1833
|
+
const { output } = owner;
|
|
1834
|
+
const identity2 = identityRefusals(news, news, output);
|
|
1835
|
+
if (identity2.length > 0)
|
|
1836
|
+
return yield* refused(identity2);
|
|
1837
|
+
const live = yield* readLive(news);
|
|
1838
|
+
if (live === undefined) {
|
|
1839
|
+
const refusals = createRefusals(news);
|
|
1840
|
+
if (refusals.length > 0)
|
|
1841
|
+
return yield* refused(refusals);
|
|
1842
|
+
yield* createGuest(news);
|
|
1843
|
+
} else {
|
|
1844
|
+
if (output === undefined) {
|
|
1845
|
+
yield* refuseTakeover(owner, `Proxmox.Lxc CT ${String(news.vmid)} on ${news.node}`);
|
|
1846
|
+
}
|
|
1847
|
+
const change = judge(news, live);
|
|
1848
|
+
if (output === undefined && change.drift.length > 0) {
|
|
1849
|
+
return yield* refused([
|
|
1850
|
+
`CT ${String(news.vmid)} already exists on ${news.node} and differs in ` + `${change.drift.join(", ")}, but this deploy planned a create. A create never writes ` + "onto an existing guest: plan again, so the plan can show it and adopt it."
|
|
1851
|
+
]);
|
|
1852
|
+
}
|
|
1853
|
+
if (change.refuse.length > 0)
|
|
1854
|
+
return yield* refused(change.refuse);
|
|
1855
|
+
if (change.drift.length > 0)
|
|
1856
|
+
yield* updateGuest(news, change, text(live["digest"]));
|
|
1857
|
+
}
|
|
1858
|
+
const after = yield* readLive(news);
|
|
1859
|
+
if (after === undefined) {
|
|
1860
|
+
return yield* refused([
|
|
1861
|
+
`CT ${String(news.vmid)}: the write returned no error but the guest is still absent.`
|
|
1862
|
+
]);
|
|
1863
|
+
}
|
|
1864
|
+
const left = judge(news, after).drift;
|
|
1865
|
+
if (left.length > 0) {
|
|
1866
|
+
return yield* refused([
|
|
1867
|
+
`CT ${String(news.vmid)}: after the write, ${left.join(", ")} still differ from the ` + "declaration. PVE may have normalised the value (tags are lower-cased by default) -- " + "declare what it stores."
|
|
1868
|
+
]);
|
|
1869
|
+
}
|
|
1870
|
+
return attributesOf(news, after);
|
|
1871
|
+
});
|
|
1872
|
+
var whereOf = (olds, output) => ({
|
|
1873
|
+
node: output?.node ?? olds.node,
|
|
1874
|
+
target: olds.target,
|
|
1875
|
+
vmid: output?.vmid ?? olds.vmid
|
|
1876
|
+
});
|
|
1877
|
+
var read = (owner, olds) => Effect29.gen(function* () {
|
|
1878
|
+
const where = whereOf(olds, owner.output);
|
|
1879
|
+
const live = yield* readLive(where);
|
|
1880
|
+
if (live === undefined)
|
|
1881
|
+
return;
|
|
1882
|
+
const settled2 = Effect29.sync(() => judge(olds, live).drift.length === 0);
|
|
1883
|
+
return yield* ownedRead(owner, attributesOf(where, live), settled2);
|
|
1884
|
+
});
|
|
1885
|
+
var handlers10 = {
|
|
1886
|
+
delete: ({ olds, output }) => destroyGuest(whereOf(olds, output), olds),
|
|
1887
|
+
diff: ({
|
|
1888
|
+
news,
|
|
1889
|
+
olds,
|
|
1890
|
+
output
|
|
1891
|
+
}) => diff(news, olds, output),
|
|
1892
|
+
list: () => Effect29.succeed([]),
|
|
1893
|
+
read: ({
|
|
1894
|
+
fqn,
|
|
1895
|
+
instanceId,
|
|
1896
|
+
olds,
|
|
1897
|
+
output
|
|
1898
|
+
}) => read({ fqn, instanceId, output }, olds),
|
|
1899
|
+
reconcile: ({
|
|
1900
|
+
fqn,
|
|
1901
|
+
instanceId,
|
|
1902
|
+
news,
|
|
1903
|
+
output
|
|
1904
|
+
}) => reconcile({ fqn, instanceId, output }, news)
|
|
1905
|
+
};
|
|
1906
|
+
var ProxmoxLxcProvider = () => Provider13.effect(ProxmoxLxc, Effect29.succeed(ProxmoxLxc.Provider.of(handlers10)).pipe(Effect29.map(provingResumes)));
|
|
1079
1907
|
// src/proxmox/metric-server.ts
|
|
1080
1908
|
import { Resource as Resource14 } from "alchemy";
|
|
1081
1909
|
import * as Provider14 from "alchemy/Provider";
|
|
1082
|
-
import * as
|
|
1910
|
+
import * as Effect30 from "effect/Effect";
|
|
1083
1911
|
|
|
1084
1912
|
// src/proxmox/metric-server-form.ts
|
|
1085
1913
|
var UNSET2 = -1;
|
|
@@ -1187,67 +2015,22 @@ var handlers11 = pveHandlers({
|
|
|
1187
2015
|
path: (props) => `cluster/metrics/server/${props.id}`,
|
|
1188
2016
|
updateForm: update
|
|
1189
2017
|
});
|
|
1190
|
-
var ProxmoxMetricServerProvider = () => Provider14.effect(ProxmoxMetricServer,
|
|
2018
|
+
var ProxmoxMetricServerProvider = () => Provider14.effect(ProxmoxMetricServer, Effect30.succeed(ProxmoxMetricServer.Provider.of(handlers11)));
|
|
1191
2019
|
// src/proxmox/network-apply.ts
|
|
1192
2020
|
import { Resource as Resource15 } from "alchemy";
|
|
1193
|
-
import { isResolved as
|
|
2021
|
+
import { isResolved as isResolved7 } from "alchemy/Diff";
|
|
1194
2022
|
import * as Provider15 from "alchemy/Provider";
|
|
1195
|
-
import * as
|
|
1196
|
-
|
|
1197
|
-
// src/proxmox/network-apply-read.ts
|
|
1198
|
-
import * as Effect24 from "effect/Effect";
|
|
1199
|
-
var stagedDiff = (target, node) => Effect24.gen(function* () {
|
|
1200
|
-
const path = `nodes/${node}/network`;
|
|
1201
|
-
const credential = yield* leased(target, "read");
|
|
1202
|
-
const envelope = yield* pveEnvelopeWith(target, credential, "GET", path);
|
|
1203
|
-
return text(envelope.changes);
|
|
1204
|
-
});
|
|
1205
|
-
var pendingLines = (diff) => {
|
|
1206
|
-
if (diff === "")
|
|
1207
|
-
return 0;
|
|
1208
|
-
const changed = diff.split(`
|
|
1209
|
-
`).filter((line) => (line.startsWith("+") || line.startsWith("-")) && !line.startsWith("+++") && !line.startsWith("---")).length;
|
|
1210
|
-
return Math.max(changed, 1);
|
|
1211
|
-
};
|
|
1212
|
-
var pendingCount = (target, node) => stagedDiff(target, node).pipe(Effect24.map(pendingLines));
|
|
1213
|
-
var clusterHealth = (target) => pve(target, "read", "GET", "cluster/status").pipe(Effect24.map((rows) => {
|
|
1214
|
-
const all = rows ?? [];
|
|
1215
|
-
const cluster = all.find((row) => row.type === "cluster");
|
|
1216
|
-
return {
|
|
1217
|
-
offline: all.filter((row) => row.type === "node" && !bool(row.online)).map((row) => text(row.name, "<unnamed>")),
|
|
1218
|
-
quorate: cluster === undefined ? true : bool(cluster.quorate)
|
|
1219
|
-
};
|
|
1220
|
-
}));
|
|
1221
|
-
var degradedReason = (target) => clusterHealth(target).pipe(Effect24.map((health) => health.quorate && health.offline.length === 0 ? undefined : `quorate=${String(health.quorate)} offline=[${health.offline.join(", ")}]`));
|
|
1222
|
-
var UNREACHABLE = "the task could not be read -- the node stopped answering, or this lease cannot see it";
|
|
1223
|
-
var STILL_RUNNING = "still running when this provider stopped waiting";
|
|
1224
|
-
var ATTEMPTS = 30;
|
|
1225
|
-
var awaitTask = (target, credential, node, upid) => Effect24.gen(function* () {
|
|
1226
|
-
const path = `nodes/${node}/tasks/${upid}/status`;
|
|
1227
|
-
for (let attempt = 0;attempt < ATTEMPTS; attempt += 1) {
|
|
1228
|
-
const status = yield* pveWith(target, credential, "GET", path).pipe(Effect24.orElseSucceed(() => {
|
|
1229
|
-
return;
|
|
1230
|
-
}));
|
|
1231
|
-
if (status === undefined)
|
|
1232
|
-
return UNREACHABLE;
|
|
1233
|
-
if (status.status === "stopped")
|
|
1234
|
-
return text(status.exitstatus, "stopped with no exitstatus");
|
|
1235
|
-
yield* Effect24.sleep("1 second");
|
|
1236
|
-
}
|
|
1237
|
-
return STILL_RUNNING;
|
|
1238
|
-
});
|
|
1239
|
-
|
|
1240
|
-
// src/proxmox/network-apply.ts
|
|
2023
|
+
import * as Effect31 from "effect/Effect";
|
|
1241
2024
|
var ProxmoxNetworkApply = Resource15("Proxmox.NetworkApply");
|
|
1242
|
-
var
|
|
1243
|
-
var
|
|
1244
|
-
if (!
|
|
2025
|
+
var read2 = (props) => pendingCount(props.target, props.node).pipe(Effect31.map((pending) => ({ node: props.node, pending })));
|
|
2026
|
+
var diff2 = (news) => Effect31.gen(function* () {
|
|
2027
|
+
if (!isResolved7(news))
|
|
1245
2028
|
return;
|
|
1246
|
-
const { pending } = yield*
|
|
2029
|
+
const { pending } = yield* read2(news);
|
|
1247
2030
|
return pending === 0 ? { action: "noop" } : { action: "update" };
|
|
1248
2031
|
});
|
|
1249
|
-
var refuse = (node, why) =>
|
|
1250
|
-
var apply = (props) =>
|
|
2032
|
+
var refuse = (node, why) => Effect31.die(new Error(`Proxmox.NetworkApply ${node}: ${why}`));
|
|
2033
|
+
var apply = (props) => Effect31.gen(function* () {
|
|
1251
2034
|
const node = props.node;
|
|
1252
2035
|
const staged = yield* pendingCount(props.target, node);
|
|
1253
2036
|
if (staged === 0)
|
|
@@ -1276,17 +2059,17 @@ var apply = (props) => Effect25.gen(function* () {
|
|
|
1276
2059
|
return { node, pending: left };
|
|
1277
2060
|
});
|
|
1278
2061
|
var handlers12 = {
|
|
1279
|
-
delete: () =>
|
|
1280
|
-
diff: ({ news }) =>
|
|
1281
|
-
list: () =>
|
|
1282
|
-
read: ({ olds }) =>
|
|
2062
|
+
delete: () => Effect31.void,
|
|
2063
|
+
diff: ({ news }) => diff2(news),
|
|
2064
|
+
list: () => Effect31.succeed([]),
|
|
2065
|
+
read: ({ olds }) => read2(olds),
|
|
1283
2066
|
reconcile: ({ news }) => apply(news)
|
|
1284
2067
|
};
|
|
1285
|
-
var ProxmoxNetworkApplyProvider = () => Provider15.effect(ProxmoxNetworkApply,
|
|
2068
|
+
var ProxmoxNetworkApplyProvider = () => Provider15.effect(ProxmoxNetworkApply, Effect31.succeed(ProxmoxNetworkApply.Provider.of(handlers12)));
|
|
1286
2069
|
// src/proxmox/node-network.ts
|
|
1287
2070
|
import { Resource as Resource16 } from "alchemy";
|
|
1288
2071
|
import * as Provider16 from "alchemy/Provider";
|
|
1289
|
-
import * as
|
|
2072
|
+
import * as Effect32 from "effect/Effect";
|
|
1290
2073
|
|
|
1291
2074
|
// src/proxmox/node-network-form.ts
|
|
1292
2075
|
var UNSET3 = -1;
|
|
@@ -1366,11 +2149,11 @@ var handlers13 = pveHandlers({
|
|
|
1366
2149
|
path: (props) => `nodes/${props.node}/network/${props.iface}`,
|
|
1367
2150
|
updateForm: updateBody2
|
|
1368
2151
|
});
|
|
1369
|
-
var ProxmoxNodeNetworkProvider = () => Provider16.effect(ProxmoxNodeNetwork,
|
|
2152
|
+
var ProxmoxNodeNetworkProvider = () => Provider16.effect(ProxmoxNodeNetwork, Effect32.succeed(ProxmoxNodeNetwork.Provider.of(handlers13)));
|
|
1370
2153
|
// src/proxmox/notification-target.ts
|
|
1371
2154
|
import { Resource as Resource17 } from "alchemy";
|
|
1372
2155
|
import * as Provider17 from "alchemy/Provider";
|
|
1373
|
-
import * as
|
|
2156
|
+
import * as Effect33 from "effect/Effect";
|
|
1374
2157
|
|
|
1375
2158
|
// src/proxmox/notification-target-form.ts
|
|
1376
2159
|
var REPEATED_KEY = "\x00";
|
|
@@ -1426,15 +2209,15 @@ var handlers14 = pveHandlers({
|
|
|
1426
2209
|
path: (props) => `cluster/notifications/endpoints/${props.type}/${props.name}`,
|
|
1427
2210
|
updateForm: shape4
|
|
1428
2211
|
});
|
|
1429
|
-
var ProxmoxNotificationTargetProvider = () => Provider17.effect(ProxmoxNotificationTarget,
|
|
2212
|
+
var ProxmoxNotificationTargetProvider = () => Provider17.effect(ProxmoxNotificationTarget, Effect33.succeed(ProxmoxNotificationTarget.Provider.of(handlers14)));
|
|
1430
2213
|
// src/proxmox/pbs-datastore.ts
|
|
1431
2214
|
import { Resource as Resource18 } from "alchemy";
|
|
1432
|
-
import { isResolved as
|
|
2215
|
+
import { isResolved as isResolved8 } from "alchemy/Diff";
|
|
1433
2216
|
import * as Provider18 from "alchemy/Provider";
|
|
1434
|
-
import * as
|
|
2217
|
+
import * as Effect36 from "effect/Effect";
|
|
1435
2218
|
|
|
1436
2219
|
// src/proxmox/pbs-datastore-form.ts
|
|
1437
|
-
import * as
|
|
2220
|
+
import * as Effect34 from "effect/Effect";
|
|
1438
2221
|
var attributes = (live, props) => ({
|
|
1439
2222
|
backend: propertyString(live["backend"]),
|
|
1440
2223
|
comment: text(live["comment"]),
|
|
@@ -1478,7 +2261,7 @@ var mutable = (props) => ({
|
|
|
1478
2261
|
...set2("tuning", props.tuning),
|
|
1479
2262
|
...set2("verify-new", flag(props["verify-new"]))
|
|
1480
2263
|
});
|
|
1481
|
-
var
|
|
2264
|
+
var createForm4 = (props) => ({
|
|
1482
2265
|
...mutable(props),
|
|
1483
2266
|
name: props.name,
|
|
1484
2267
|
path: props.path,
|
|
@@ -1486,37 +2269,37 @@ var createForm3 = (props) => ({
|
|
|
1486
2269
|
});
|
|
1487
2270
|
var updateForm = (props) => mutable(props);
|
|
1488
2271
|
var object2 = (props) => `config/datastore/${props.name}`;
|
|
1489
|
-
var readOne = (props) => pve(props.target, "read", "GET", object2(props)).pipe(
|
|
2272
|
+
var readOne = (props) => pve(props.target, "read", "GET", object2(props)).pipe(Effect34.map((live) => live === undefined || typeof live["name"] !== "string" ? undefined : attributes(live, props)), Effect34.orElseSucceed(() => {
|
|
1490
2273
|
return;
|
|
1491
2274
|
}));
|
|
1492
2275
|
|
|
1493
2276
|
// src/proxmox/pbs-datastore-guard.ts
|
|
1494
|
-
import * as
|
|
2277
|
+
import * as Effect35 from "effect/Effect";
|
|
1495
2278
|
var samePath = (left, right) => {
|
|
1496
2279
|
const trim = (value) => value.length > 1 ? value.replace(/\/+$/, "") : value;
|
|
1497
2280
|
return trim(left) === trim(right);
|
|
1498
2281
|
};
|
|
1499
|
-
var guardPath = (live, props) => live.path === "" || samePath(live.path, props.path) ?
|
|
1500
|
-
var guardBackend = (live, props) => props.backend === undefined || propertyString(props.backend) === live.backend ?
|
|
1501
|
-
var settle3 = (
|
|
2282
|
+
var guardPath = (live, props) => live.path === "" || samePath(live.path, props.path) ? Effect35.void : Effect35.die(new Error(`${object2(props)}: declared path ${props.path} but PBS holds ${live.path}. \`path\` ` + "is create-only, so this cannot be reconciled and will NOT be planned as a silent " + "noop: backups would keep landing in the old directory behind a green plan. Correct " + "the declaration, or remove the datastore and declare a new one -- the contents live, " + "because `destroy-data` is never sent."));
|
|
2283
|
+
var guardBackend = (live, props) => props.backend === undefined || propertyString(props.backend) === live.backend ? Effect35.void : Effect35.die(new Error(`${object2(props)}: declared backend ${props.backend} but PBS holds ` + `${live.backend === "" ? "(none — a local datastore)" : live.backend}. \`backend\` ` + "is create-only: PBS has no update for it, so this cannot be reconciled and will NOT " + "be planned as a silent noop. Correct the declaration to match what is really there, " + "or remove the datastore and declare a new one -- the contents live, because " + "`destroy-data` is never sent."));
|
|
2284
|
+
var settle3 = (read3, done) => Effect35.gen(function* () {
|
|
1502
2285
|
for (let attempt = 0;attempt < 60; attempt += 1) {
|
|
1503
|
-
const live = yield*
|
|
2286
|
+
const live = yield* read3;
|
|
1504
2287
|
if (done(live))
|
|
1505
2288
|
return live;
|
|
1506
|
-
yield*
|
|
2289
|
+
yield* Effect35.sleep("2 seconds");
|
|
1507
2290
|
}
|
|
1508
|
-
return yield*
|
|
2291
|
+
return yield* read3;
|
|
1509
2292
|
});
|
|
1510
2293
|
|
|
1511
2294
|
// src/proxmox/pbs-datastore.ts
|
|
1512
2295
|
var PbsDatastore = Resource18("Pbs.Datastore", {
|
|
1513
2296
|
defaultRemovalPolicy: "retain"
|
|
1514
2297
|
});
|
|
1515
|
-
var PbsDatastoreProvider = () => Provider18.effect(PbsDatastore,
|
|
1516
|
-
list: () =>
|
|
2298
|
+
var PbsDatastoreProvider = () => Provider18.effect(PbsDatastore, Effect36.succeed(PbsDatastore.Provider.of({
|
|
2299
|
+
list: () => Effect36.succeed([]),
|
|
1517
2300
|
read: ({ olds }) => readOne(olds),
|
|
1518
|
-
diff: ({ news, output }) =>
|
|
1519
|
-
if (output === undefined || !
|
|
2301
|
+
diff: ({ news, output }) => Effect36.gen(function* () {
|
|
2302
|
+
if (output === undefined || !isResolved8(news))
|
|
1520
2303
|
return;
|
|
1521
2304
|
const live = yield* readOne(news);
|
|
1522
2305
|
if (live === undefined)
|
|
@@ -1525,37 +2308,37 @@ var PbsDatastoreProvider = () => Provider18.effect(PbsDatastore, Effect30.succee
|
|
|
1525
2308
|
yield* guardBackend(live, news);
|
|
1526
2309
|
return matches(live, news) ? { action: "noop" } : { action: "update" };
|
|
1527
2310
|
}),
|
|
1528
|
-
reconcile:
|
|
2311
|
+
reconcile: Effect36.fn(function* ({ news }) {
|
|
1529
2312
|
const live = yield* readOne(news);
|
|
1530
2313
|
let upid;
|
|
1531
2314
|
if (live === undefined) {
|
|
1532
|
-
upid = yield* pve(news.target, "provision", "POST", "config/datastore",
|
|
2315
|
+
upid = yield* pve(news.target, "provision", "POST", "config/datastore", createForm4(news));
|
|
1533
2316
|
} else {
|
|
1534
2317
|
yield* guardPath(live, news);
|
|
1535
2318
|
yield* guardBackend(live, news);
|
|
1536
|
-
const form = updateForm(news);
|
|
1537
|
-
if (
|
|
2319
|
+
const form = formToSend(matches, live, news, updateForm(news));
|
|
2320
|
+
if (form !== undefined) {
|
|
1538
2321
|
yield* pve(news.target, "provision", "PUT", object2(news), form);
|
|
1539
2322
|
}
|
|
1540
2323
|
}
|
|
1541
2324
|
const after = yield* settle3(readOne(news), (row) => row !== undefined && matches(row, news));
|
|
1542
2325
|
if (after === undefined || !matches(after, news)) {
|
|
1543
|
-
return yield*
|
|
2326
|
+
return yield* Effect36.die(new Error(`${object2(news)}: the write returned no error but the datastore still does not ` + `match the declaration after 120s. PBS answered with task ${upid ?? "(none)"} ` + "-- read it with `proxmox-backup-manager task log <upid>`. A slow disk is a " + "plausible cause (see `settle`); a create over an existing chunk store is not, " + "because PBS refuses that outright rather than timing out."));
|
|
1544
2327
|
}
|
|
1545
2328
|
return after;
|
|
1546
2329
|
}),
|
|
1547
|
-
delete:
|
|
2330
|
+
delete: Effect36.fn(function* ({ olds }) {
|
|
1548
2331
|
yield* pve(olds.target, "provision", "DELETE", object2(olds));
|
|
1549
2332
|
const left = yield* settle3(readOne(olds), (row) => row === undefined);
|
|
1550
2333
|
if (left !== undefined) {
|
|
1551
|
-
return yield*
|
|
2334
|
+
return yield* Effect36.die(new Error(`${object2(olds)}: the DELETE returned no error but the datastore is still in ` + "datastore.cfg after 120s. Either a backup, verify or GC task still holds it, " + "or the provision role lacks Datastore.Allocate on /datastore/" + `${olds.name}. Read the task log with \`proxmox-backup-manager task log <upid>\`.`));
|
|
1552
2335
|
}
|
|
1553
2336
|
})
|
|
1554
2337
|
})));
|
|
1555
2338
|
// src/proxmox/pbs-prune-job.ts
|
|
1556
2339
|
import { Resource as Resource19 } from "alchemy";
|
|
1557
2340
|
import * as Provider19 from "alchemy/Provider";
|
|
1558
|
-
import * as
|
|
2341
|
+
import * as Effect37 from "effect/Effect";
|
|
1559
2342
|
|
|
1560
2343
|
// src/proxmox/pbs-prune-job-form.ts
|
|
1561
2344
|
var KEEP_KEYS = [
|
|
@@ -1630,15 +2413,15 @@ var handlers15 = pveHandlers({
|
|
|
1630
2413
|
path: (props) => `config/prune/${props.id}`,
|
|
1631
2414
|
updateForm: updateBody3
|
|
1632
2415
|
});
|
|
1633
|
-
var PbsPruneJobProvider = () => Provider19.effect(PbsPruneJob,
|
|
2416
|
+
var PbsPruneJobProvider = () => Provider19.effect(PbsPruneJob, Effect37.succeed(PbsPruneJob.Provider.of(handlers15)));
|
|
1634
2417
|
// src/proxmox/pbs-sync-job.ts
|
|
1635
2418
|
import { Resource as Resource20 } from "alchemy";
|
|
1636
2419
|
import * as Provider20 from "alchemy/Provider";
|
|
1637
|
-
import * as
|
|
2420
|
+
import * as Effect38 from "effect/Effect";
|
|
1638
2421
|
|
|
1639
2422
|
// src/proxmox/pbs-sync-job-form.ts
|
|
1640
2423
|
var UNSET4 = -1;
|
|
1641
|
-
var
|
|
2424
|
+
var UNITS2 = {
|
|
1642
2425
|
"": 1,
|
|
1643
2426
|
B: 1,
|
|
1644
2427
|
GB: 1e9,
|
|
@@ -1657,7 +2440,7 @@ var bytes = (value) => {
|
|
|
1657
2440
|
const match = /^([0-9]+(?:\.[0-9]+)?)\s*([A-Za-z]*)$/.exec(raw);
|
|
1658
2441
|
if (match === null)
|
|
1659
2442
|
return UNSET4;
|
|
1660
|
-
const unit =
|
|
2443
|
+
const unit = UNITS2[match[2] ?? ""];
|
|
1661
2444
|
return unit === undefined ? UNSET4 : Math.round(Number(match[1] ?? "0") * unit);
|
|
1662
2445
|
};
|
|
1663
2446
|
var filters = (value) => Array.isArray(value) ? value.map((entry) => String(entry)).join(" | ") : text(value);
|
|
@@ -1710,11 +2493,11 @@ var handlers16 = pveHandlers({
|
|
|
1710
2493
|
|
|
1711
2494
|
// src/proxmox/pbs-sync-job.ts
|
|
1712
2495
|
var PbsSyncJob = Resource20("Pbs.SyncJob");
|
|
1713
|
-
var PbsSyncJobProvider = () => Provider20.effect(PbsSyncJob,
|
|
2496
|
+
var PbsSyncJobProvider = () => Provider20.effect(PbsSyncJob, Effect38.succeed(PbsSyncJob.Provider.of(handlers16)));
|
|
1714
2497
|
// src/proxmox/pbs-verify-job.ts
|
|
1715
2498
|
import { Resource as Resource21 } from "alchemy";
|
|
1716
2499
|
import * as Provider21 from "alchemy/Provider";
|
|
1717
|
-
import * as
|
|
2500
|
+
import * as Effect39 from "effect/Effect";
|
|
1718
2501
|
|
|
1719
2502
|
// src/proxmox/pbs-verify-job-form.ts
|
|
1720
2503
|
var rechecks = (ignoreVerified, outdatedAfter) => {
|
|
@@ -1758,11 +2541,11 @@ var handlers17 = pveHandlers({
|
|
|
1758
2541
|
path: (props) => `config/verify/${props.id}`,
|
|
1759
2542
|
updateForm: shape7
|
|
1760
2543
|
});
|
|
1761
|
-
var PbsVerifyJobProvider = () => Provider21.effect(PbsVerifyJob,
|
|
2544
|
+
var PbsVerifyJobProvider = () => Provider21.effect(PbsVerifyJob, Effect39.succeed(PbsVerifyJob.Provider.of(handlers17)));
|
|
1762
2545
|
// src/proxmox/pool.ts
|
|
1763
2546
|
import { Resource as Resource22 } from "alchemy";
|
|
1764
2547
|
import * as Provider22 from "alchemy/Provider";
|
|
1765
|
-
import * as
|
|
2548
|
+
import * as Effect40 from "effect/Effect";
|
|
1766
2549
|
var ProxmoxPool = Resource22("Proxmox.Pool");
|
|
1767
2550
|
var poolCreateForm = (props) => ({
|
|
1768
2551
|
comment: props.comment ?? "",
|
|
@@ -1786,11 +2569,11 @@ var handlers18 = pveHandlers({
|
|
|
1786
2569
|
path: (props) => `pools/${props.poolid}`,
|
|
1787
2570
|
updateForm: poolUpdateForm
|
|
1788
2571
|
});
|
|
1789
|
-
var ProxmoxPoolProvider = () => Provider22.effect(ProxmoxPool,
|
|
2572
|
+
var ProxmoxPoolProvider = () => Provider22.effect(ProxmoxPool, Effect40.succeed(ProxmoxPool.Provider.of(handlers18)));
|
|
1790
2573
|
// src/proxmox/qemu.ts
|
|
1791
2574
|
import { Resource as Resource23 } from "alchemy";
|
|
1792
2575
|
import * as Provider23 from "alchemy/Provider";
|
|
1793
|
-
import * as
|
|
2576
|
+
import * as Effect41 from "effect/Effect";
|
|
1794
2577
|
var ProxmoxVm = Resource23("Proxmox.Vm");
|
|
1795
2578
|
var shape8 = (props) => ({
|
|
1796
2579
|
cores: String(props.cores ?? 1),
|
|
@@ -1816,11 +2599,11 @@ var handlers19 = pveHandlers({
|
|
|
1816
2599
|
path: (props) => `nodes/${props.node}/qemu/${String(props.vmid)}/config`,
|
|
1817
2600
|
updateForm: shape8
|
|
1818
2601
|
});
|
|
1819
|
-
var ProxmoxVmProvider = () => Provider23.effect(ProxmoxVm,
|
|
2602
|
+
var ProxmoxVmProvider = () => Provider23.effect(ProxmoxVm, Effect41.succeed(ProxmoxVm.Provider.of(handlers19)));
|
|
1820
2603
|
// src/proxmox/replication-job.ts
|
|
1821
2604
|
import { Resource as Resource24 } from "alchemy";
|
|
1822
2605
|
import * as Provider24 from "alchemy/Provider";
|
|
1823
|
-
import * as
|
|
2606
|
+
import * as Effect42 from "effect/Effect";
|
|
1824
2607
|
|
|
1825
2608
|
// src/proxmox/replication-job-form.ts
|
|
1826
2609
|
var DEFAULT_SCHEDULE = "*/15";
|
|
@@ -1870,11 +2653,11 @@ var handlers20 = pveHandlers({
|
|
|
1870
2653
|
path: (props) => `cluster/replication/${jobId(props)}`,
|
|
1871
2654
|
updateForm: updateBody5
|
|
1872
2655
|
});
|
|
1873
|
-
var ProxmoxReplicationJobProvider = () => Provider24.effect(ProxmoxReplicationJob,
|
|
2656
|
+
var ProxmoxReplicationJobProvider = () => Provider24.effect(ProxmoxReplicationJob, Effect42.succeed(ProxmoxReplicationJob.Provider.of(handlers20)));
|
|
1874
2657
|
// src/proxmox/role.ts
|
|
1875
2658
|
import { Resource as Resource25 } from "alchemy";
|
|
1876
2659
|
import * as Provider25 from "alchemy/Provider";
|
|
1877
|
-
import * as
|
|
2660
|
+
import * as Effect43 from "effect/Effect";
|
|
1878
2661
|
var ProxmoxRole = Resource25("Proxmox.Role");
|
|
1879
2662
|
var canonical = (privs) => [...new Set(privs.map((priv) => priv.trim()).filter((priv) => priv.length > 0))].sort();
|
|
1880
2663
|
var livePrivs = (live) => {
|
|
@@ -1891,15 +2674,15 @@ var handlers21 = pveHandlers({
|
|
|
1891
2674
|
path: (props) => `access/roles/${props.roleid}`,
|
|
1892
2675
|
updateForm: (props) => ({ privs: canonical(props.privs).join(",") })
|
|
1893
2676
|
});
|
|
1894
|
-
var ProxmoxRoleProvider = () => Provider25.effect(ProxmoxRole,
|
|
2677
|
+
var ProxmoxRoleProvider = () => Provider25.effect(ProxmoxRole, Effect43.succeed(ProxmoxRole.Provider.of(handlers21)));
|
|
1895
2678
|
// src/proxmox/sdn-apply.ts
|
|
1896
2679
|
import { Resource as Resource26 } from "alchemy";
|
|
1897
|
-
import { isResolved as
|
|
2680
|
+
import { isResolved as isResolved9 } from "alchemy/Diff";
|
|
1898
2681
|
import * as Provider26 from "alchemy/Provider";
|
|
1899
|
-
import * as
|
|
2682
|
+
import * as Effect45 from "effect/Effect";
|
|
1900
2683
|
|
|
1901
2684
|
// src/proxmox/sdn-apply-read.ts
|
|
1902
|
-
import * as
|
|
2685
|
+
import * as Effect44 from "effect/Effect";
|
|
1903
2686
|
var MARKED = [
|
|
1904
2687
|
"cluster/sdn/zones",
|
|
1905
2688
|
"cluster/sdn/vnets",
|
|
@@ -1908,7 +2691,7 @@ var MARKED = [
|
|
|
1908
2691
|
"cluster/sdn/route-maps"
|
|
1909
2692
|
];
|
|
1910
2693
|
var subsystemAbsent = (error) => error instanceof PveError && error.status === 501;
|
|
1911
|
-
var markedPending = (target) =>
|
|
2694
|
+
var markedPending = (target) => Effect44.all(MARKED.map((collection2) => pve(target, "read", "GET", `${collection2}?pending=1`).pipe(Effect44.map((rows) => (rows ?? []).filter((row) => row.state !== undefined).length), Effect44.catchIf(subsystemAbsent, () => Effect44.succeed(0)))), { concurrency: "unbounded" }).pipe(Effect44.map((counts) => counts.reduce((total, count2) => total + count2, 0)));
|
|
1912
2695
|
var fabricStaged = (staged, running) => {
|
|
1913
2696
|
if (staged === undefined && running === undefined)
|
|
1914
2697
|
return 0;
|
|
@@ -1916,9 +2699,9 @@ var fabricStaged = (staged, running) => {
|
|
|
1916
2699
|
return "inconsistent";
|
|
1917
2700
|
return staged === running ? 0 : 1;
|
|
1918
2701
|
};
|
|
1919
|
-
var fabricPending = (target) =>
|
|
2702
|
+
var fabricPending = (target) => Effect44.all(["pending", "running"].map((view) => pve(target, "read", "GET", `cluster/sdn/fabrics/all?${view}=1`).pipe(Effect44.map(canonical2), Effect44.catchIf(subsystemAbsent, () => Effect44.succeed(undefined)))), { concurrency: "unbounded" }).pipe(Effect44.flatMap(([staged, running]) => {
|
|
1920
2703
|
const verdict = fabricStaged(staged, running);
|
|
1921
|
-
return verdict === "inconsistent" ?
|
|
2704
|
+
return verdict === "inconsistent" ? Effect44.die(new Error("cluster/sdn/fabrics/all answered 501 for one of ?pending=1 and ?running=1 and not " + "the other. Refusing to read that as a staged fabric change.")) : Effect44.succeed(verdict);
|
|
1922
2705
|
}));
|
|
1923
2706
|
var canonical2 = (value) => JSON.stringify(sortDeep(value));
|
|
1924
2707
|
var sortDeep = (value) => {
|
|
@@ -1931,39 +2714,39 @@ var sortDeep = (value) => {
|
|
|
1931
2714
|
}
|
|
1932
2715
|
return value;
|
|
1933
2716
|
};
|
|
1934
|
-
var sdnPendingCount = (target) =>
|
|
2717
|
+
var sdnPendingCount = (target) => Effect44.all([markedPending(target), fabricPending(target)], { concurrency: "unbounded" }).pipe(Effect44.map(([marked, fabric]) => marked + fabric));
|
|
1935
2718
|
|
|
1936
2719
|
// src/proxmox/sdn-apply.ts
|
|
1937
2720
|
var ProxmoxSdnApply = Resource26("Proxmox.SdnApply");
|
|
1938
|
-
var
|
|
1939
|
-
var ProxmoxSdnApplyProvider = () => Provider26.effect(ProxmoxSdnApply,
|
|
1940
|
-
list: () =>
|
|
1941
|
-
read:
|
|
1942
|
-
return yield*
|
|
2721
|
+
var read3 = (props) => sdnPendingCount(props.target).pipe(Effect45.map((pending) => ({ pending })));
|
|
2722
|
+
var ProxmoxSdnApplyProvider = () => Provider26.effect(ProxmoxSdnApply, Effect45.succeed(ProxmoxSdnApply.Provider.of({
|
|
2723
|
+
list: () => Effect45.succeed([]),
|
|
2724
|
+
read: Effect45.fn(function* ({ olds }) {
|
|
2725
|
+
return yield* read3(olds);
|
|
1943
2726
|
}),
|
|
1944
|
-
diff:
|
|
1945
|
-
if (!
|
|
2727
|
+
diff: Effect45.fn(function* ({ news }) {
|
|
2728
|
+
if (!isResolved9(news))
|
|
1946
2729
|
return;
|
|
1947
|
-
const { pending } = yield*
|
|
2730
|
+
const { pending } = yield* read3(news);
|
|
1948
2731
|
return pending === 0 ? { action: "noop" } : { action: "update" };
|
|
1949
2732
|
}),
|
|
1950
|
-
reconcile:
|
|
1951
|
-
const staged = yield*
|
|
2733
|
+
reconcile: Effect45.fn(function* ({ news }) {
|
|
2734
|
+
const staged = yield* read3(news);
|
|
1952
2735
|
if (staged.pending === 0)
|
|
1953
2736
|
return staged;
|
|
1954
2737
|
yield* pve(news.target, "provision", "PUT", "cluster/sdn");
|
|
1955
|
-
const after = yield*
|
|
2738
|
+
const after = yield* read3(news);
|
|
1956
2739
|
if (after.pending !== 0) {
|
|
1957
|
-
return yield*
|
|
2740
|
+
return yield* Effect45.die(new Error(`PUT /cluster/sdn returned no error but ${String(after.pending)} SDN object(s) ` + "are still staged. The running config does not match the declared one -- check " + "`pvesh get /cluster/sdn/zones --pending 1` for the objects PVE refused."));
|
|
1958
2741
|
}
|
|
1959
2742
|
return after;
|
|
1960
2743
|
}),
|
|
1961
|
-
delete: () =>
|
|
2744
|
+
delete: () => Effect45.void
|
|
1962
2745
|
})));
|
|
1963
2746
|
// src/proxmox/sdn-subnet.ts
|
|
1964
2747
|
import { Resource as Resource27 } from "alchemy";
|
|
1965
2748
|
import * as Provider27 from "alchemy/Provider";
|
|
1966
|
-
import * as
|
|
2749
|
+
import * as Effect46 from "effect/Effect";
|
|
1967
2750
|
|
|
1968
2751
|
// src/proxmox/sdn-subnet-form.ts
|
|
1969
2752
|
var subnetId = (props) => `${props.zone}-${props.cidr.replace("/", "-")}`;
|
|
@@ -1982,7 +2765,7 @@ var body5 = (props) => ({
|
|
|
1982
2765
|
vnet: props.vnet,
|
|
1983
2766
|
...Object.fromEntries(Object.entries(optional3(props)).filter((entry) => entry[1] !== undefined))
|
|
1984
2767
|
});
|
|
1985
|
-
var
|
|
2768
|
+
var createForm5 = (props) => ({
|
|
1986
2769
|
...body5(props),
|
|
1987
2770
|
subnet: props.cidr,
|
|
1988
2771
|
type: "subnet"
|
|
@@ -2014,16 +2797,16 @@ var handlers22 = pveHandlers({
|
|
|
2014
2797
|
};
|
|
2015
2798
|
},
|
|
2016
2799
|
collection: (props) => `cluster/sdn/vnets/${props.vnet}/subnets`,
|
|
2017
|
-
createForm:
|
|
2800
|
+
createForm: createForm5,
|
|
2018
2801
|
matches: (attributes2, props) => attributes2.gateway === (props.gateway ?? "") && attributes2.snat === (props.snat === true) && attributes2.dnszoneprefix === (props.dnszoneprefix ?? "") && attributes2.dhcpDnsServer === (props.dhcpDnsServer ?? "") && attributes2.dhcpRange === declaredRanges(props.dhcpRange),
|
|
2019
2802
|
path: (props) => `cluster/sdn/vnets/${props.vnet}/subnets/${subnetId(props)}`,
|
|
2020
2803
|
updateForm: updateForm2
|
|
2021
2804
|
});
|
|
2022
|
-
var ProxmoxSdnSubnetProvider = () => Provider27.effect(ProxmoxSdnSubnet,
|
|
2805
|
+
var ProxmoxSdnSubnetProvider = () => Provider27.effect(ProxmoxSdnSubnet, Effect46.succeed(ProxmoxSdnSubnet.Provider.of(handlers22)));
|
|
2023
2806
|
// src/proxmox/sdn-vnet.ts
|
|
2024
2807
|
import { Resource as Resource28 } from "alchemy";
|
|
2025
2808
|
import * as Provider28 from "alchemy/Provider";
|
|
2026
|
-
import * as
|
|
2809
|
+
import * as Effect47 from "effect/Effect";
|
|
2027
2810
|
var ProxmoxSdnVnet = Resource28("Proxmox.SdnVnet");
|
|
2028
2811
|
var tagOf = (value) => typeof value === "number" ? value : undefined;
|
|
2029
2812
|
var shape10 = (props) => {
|
|
@@ -2060,11 +2843,11 @@ var handlers23 = pveHandlers({
|
|
|
2060
2843
|
path: (props) => `cluster/sdn/vnets/${props.vnet}`,
|
|
2061
2844
|
updateForm: updateForm3
|
|
2062
2845
|
});
|
|
2063
|
-
var ProxmoxSdnVnetProvider = () => Provider28.effect(ProxmoxSdnVnet,
|
|
2846
|
+
var ProxmoxSdnVnetProvider = () => Provider28.effect(ProxmoxSdnVnet, Effect47.succeed(ProxmoxSdnVnet.Provider.of(handlers23)));
|
|
2064
2847
|
// src/proxmox/sdn-zone.ts
|
|
2065
2848
|
import { Resource as Resource29 } from "alchemy";
|
|
2066
2849
|
import * as Provider29 from "alchemy/Provider";
|
|
2067
|
-
import * as
|
|
2850
|
+
import * as Effect48 from "effect/Effect";
|
|
2068
2851
|
var ProxmoxSdnZone = Resource29("Proxmox.SdnZone");
|
|
2069
2852
|
var sameIpam = (live, declared) => (live === "" ? "pve" : live) === (declared === undefined || declared === "" ? "pve" : declared);
|
|
2070
2853
|
var body6 = (props) => ({
|
|
@@ -2116,11 +2899,11 @@ var handlers24 = pveHandlers({
|
|
|
2116
2899
|
return withClears(fields, clear);
|
|
2117
2900
|
}
|
|
2118
2901
|
});
|
|
2119
|
-
var ProxmoxSdnZoneProvider = () => Provider29.effect(ProxmoxSdnZone,
|
|
2902
|
+
var ProxmoxSdnZoneProvider = () => Provider29.effect(ProxmoxSdnZone, Effect48.succeed(ProxmoxSdnZone.Provider.of(handlers24)));
|
|
2120
2903
|
// src/proxmox/storage.ts
|
|
2121
2904
|
import { Resource as Resource30 } from "alchemy";
|
|
2122
2905
|
import * as Provider30 from "alchemy/Provider";
|
|
2123
|
-
import * as
|
|
2906
|
+
import * as Effect49 from "effect/Effect";
|
|
2124
2907
|
var ProxmoxStorage = Resource30("Proxmox.Storage", {
|
|
2125
2908
|
defaultRemovalPolicy: "retain"
|
|
2126
2909
|
});
|
|
@@ -2165,11 +2948,11 @@ var handlers25 = pveHandlers({
|
|
|
2165
2948
|
path: (props) => `storage/${props.storage}`,
|
|
2166
2949
|
updateForm: mutable2
|
|
2167
2950
|
});
|
|
2168
|
-
var ProxmoxStorageProvider = () => Provider30.effect(ProxmoxStorage,
|
|
2951
|
+
var ProxmoxStorageProvider = () => Provider30.effect(ProxmoxStorage, Effect49.succeed(ProxmoxStorage.Provider.of(handlers25)));
|
|
2169
2952
|
// src/proxmox/user.ts
|
|
2170
2953
|
import { Resource as Resource31 } from "alchemy";
|
|
2171
2954
|
import * as Provider31 from "alchemy/Provider";
|
|
2172
|
-
import * as
|
|
2955
|
+
import * as Effect50 from "effect/Effect";
|
|
2173
2956
|
var ProxmoxUser = Resource31("Proxmox.User");
|
|
2174
2957
|
var str3 = (value) => typeof value === "string" ? value : "";
|
|
2175
2958
|
var int2 = (value) => {
|
|
@@ -2209,15 +2992,15 @@ var handlers26 = pveHandlers({
|
|
|
2209
2992
|
path: (props) => `access/users/${props.userid}`,
|
|
2210
2993
|
updateForm: shape11
|
|
2211
2994
|
});
|
|
2212
|
-
var ProxmoxUserProvider = () => Provider31.effect(ProxmoxUser,
|
|
2995
|
+
var ProxmoxUserProvider = () => Provider31.effect(ProxmoxUser, Effect50.succeed(ProxmoxUser.Provider.of(handlers26)));
|
|
2213
2996
|
// src/proxmox/zfs-pool.ts
|
|
2214
2997
|
import { Resource as Resource32 } from "alchemy";
|
|
2215
2998
|
import * as Provider32 from "alchemy/Provider";
|
|
2216
|
-
import * as
|
|
2999
|
+
import * as Effect52 from "effect/Effect";
|
|
2217
3000
|
|
|
2218
3001
|
// src/proxmox/zfs-pool-write.ts
|
|
2219
|
-
import * as
|
|
2220
|
-
var
|
|
3002
|
+
import * as Effect51 from "effect/Effect";
|
|
3003
|
+
var createForm6 = (props) => ({
|
|
2221
3004
|
devices: props.devices.join(","),
|
|
2222
3005
|
name: props.name,
|
|
2223
3006
|
raidlevel: props.raidlevel,
|
|
@@ -2226,21 +3009,21 @@ var createForm5 = (props) => ({
|
|
|
2226
3009
|
...props["draid-config"] === undefined ? {} : { "draid-config": props["draid-config"] }
|
|
2227
3010
|
});
|
|
2228
3011
|
var SETTLE_ATTEMPTS = 30;
|
|
2229
|
-
var createPool = (props, collection2, path,
|
|
2230
|
-
const existing = yield*
|
|
3012
|
+
var createPool = (props, collection2, path, read4) => Effect51.gen(function* () {
|
|
3013
|
+
const existing = yield* read4(props);
|
|
2231
3014
|
if (existing !== undefined)
|
|
2232
3015
|
return existing;
|
|
2233
|
-
const upid = yield* pve(props.target, "provision", "POST", collection2,
|
|
2234
|
-
const settle4 = (attempts) =>
|
|
2235
|
-
const live = yield*
|
|
3016
|
+
const upid = yield* pve(props.target, "provision", "POST", collection2, createForm6(props));
|
|
3017
|
+
const settle4 = (attempts) => Effect51.gen(function* () {
|
|
3018
|
+
const live = yield* read4(props);
|
|
2236
3019
|
if (live !== undefined || attempts <= 0)
|
|
2237
3020
|
return live;
|
|
2238
|
-
yield*
|
|
3021
|
+
yield* Effect51.sleep("2 seconds");
|
|
2239
3022
|
return yield* settle4(attempts - 1);
|
|
2240
3023
|
});
|
|
2241
3024
|
const after = yield* settle4(SETTLE_ATTEMPTS);
|
|
2242
3025
|
if (after === undefined) {
|
|
2243
|
-
return yield*
|
|
3026
|
+
return yield* Effect51.die(new Error(`${path}: POST returned ${text(upid, "(no UPID)")} but the pool has not appeared. That ` + "POST only forks a `zfscreate` worker, so the task holds the real error -- read it " + `with \`pvesh get /nodes/${props.node}/tasks/<upid>/log\`. NOTHING WAS RETRIED AND ` + "NOTHING WAS CLEANED UP: if the worker reached `zpool create`, the declared devices " + "have already been written to."));
|
|
2244
3027
|
}
|
|
2245
3028
|
return after;
|
|
2246
3029
|
});
|
|
@@ -2268,19 +3051,19 @@ var spec2 = {
|
|
|
2268
3051
|
};
|
|
2269
3052
|
},
|
|
2270
3053
|
collection: (props) => `nodes/${props.node}/disks/zfs`,
|
|
2271
|
-
createForm:
|
|
3054
|
+
createForm: createForm6,
|
|
2272
3055
|
matches: () => true,
|
|
2273
3056
|
path: (props) => `nodes/${props.node}/disks/zfs/${props.name}`
|
|
2274
3057
|
};
|
|
2275
3058
|
var ops5 = pveOperations(spec2);
|
|
2276
3059
|
var handlers27 = {
|
|
2277
3060
|
...pveHandlers(spec2),
|
|
2278
|
-
delete:
|
|
3061
|
+
delete: Effect52.fn(function* ({ olds }) {
|
|
2279
3062
|
yield* destroyPool(olds, spec2.path(olds));
|
|
2280
3063
|
}),
|
|
2281
3064
|
reconcile: ({ news }) => createPool(news, spec2.collection(news), spec2.path(news), ops5.read)
|
|
2282
3065
|
};
|
|
2283
|
-
var ProxmoxZfsPoolProvider = () => Provider32.effect(ProxmoxZfsPool,
|
|
3066
|
+
var ProxmoxZfsPoolProvider = () => Provider32.effect(ProxmoxZfsPool, Effect52.succeed(ProxmoxZfsPool.Provider.of(handlers27)));
|
|
2284
3067
|
export {
|
|
2285
3068
|
PbsDatastore,
|
|
2286
3069
|
PbsDatastoreProvider,
|
|
@@ -2308,6 +3091,7 @@ export {
|
|
|
2308
3091
|
ProxmoxHaResourceProvider,
|
|
2309
3092
|
ProxmoxHaRule,
|
|
2310
3093
|
ProxmoxHaRuleProvider,
|
|
3094
|
+
ProxmoxLxc,
|
|
2311
3095
|
ProxmoxLxcProvider,
|
|
2312
3096
|
ProxmoxMetricServer,
|
|
2313
3097
|
ProxmoxMetricServerProvider,
|
|
@@ -2336,5 +3120,5 @@ export {
|
|
|
2336
3120
|
ProxmoxZfsPoolProvider
|
|
2337
3121
|
};
|
|
2338
3122
|
|
|
2339
|
-
//# debugId=
|
|
3123
|
+
//# debugId=2E96DC8F7406C81F64756E2164756E21
|
|
2340
3124
|
//# sourceMappingURL=index.js.map
|