@homeflare/alchemy 0.49.0 → 0.51.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/dist/kubernetes/helm-chart-bounded.d.ts +44 -0
- package/dist/kubernetes/helm-chart-bounded.d.ts.map +1 -0
- package/dist/kubernetes/index.d.ts +12 -0
- package/dist/kubernetes/index.d.ts.map +1 -0
- package/dist/kubernetes/index.js +585 -0
- package/dist/kubernetes/index.js.map +19 -0
- package/dist/kubernetes/ready-checks.d.ts +45 -0
- package/dist/kubernetes/ready-checks.d.ts.map +1 -0
- package/dist/kubernetes/ready-classify.d.ts +23 -0
- package/dist/kubernetes/ready-classify.d.ts.map +1 -0
- package/dist/kubernetes/ready-connect.d.ts +29 -0
- package/dist/kubernetes/ready-connect.d.ts.map +1 -0
- package/dist/kubernetes/ready-declare.d.ts +5 -0
- package/dist/kubernetes/ready-declare.d.ts.map +1 -0
- package/dist/kubernetes/ready-errors.d.ts +80 -0
- package/dist/kubernetes/ready-errors.d.ts.map +1 -0
- package/dist/kubernetes/ready-poll.d.ts +61 -0
- package/dist/kubernetes/ready-poll.d.ts.map +1 -0
- package/dist/kubernetes/ready.d.ts +74 -0
- package/dist/kubernetes/ready.d.ts.map +1 -0
- package/dist/talos/fake-apiserver.d.ts +11 -2
- package/dist/talos/fake-apiserver.d.ts.map +1 -1
- package/dist/unifi/acl-rule-ordering.d.ts +6 -1
- package/dist/unifi/acl-rule-ordering.d.ts.map +1 -1
- package/dist/unifi/acl-rule.d.ts +6 -1
- package/dist/unifi/acl-rule.d.ts.map +1 -1
- package/dist/unifi/dns-policy.d.ts +6 -1
- package/dist/unifi/dns-policy.d.ts.map +1 -1
- package/dist/unifi/fake-unifi.d.ts +4 -0
- package/dist/unifi/fake-unifi.d.ts.map +1 -1
- package/dist/unifi/firewall-policy-ordering.d.ts +6 -1
- package/dist/unifi/firewall-policy-ordering.d.ts.map +1 -1
- package/dist/unifi/firewall-policy.d.ts +6 -1
- package/dist/unifi/firewall-policy.d.ts.map +1 -1
- package/dist/unifi/firewall-zone.d.ts +6 -1
- package/dist/unifi/firewall-zone.d.ts.map +1 -1
- package/dist/unifi/index.d.ts +5 -3
- package/dist/unifi/index.d.ts.map +1 -1
- package/dist/unifi/index.js +339 -86
- package/dist/unifi/index.js.map +10 -6
- package/dist/unifi/network-scope.d.ts +31 -0
- package/dist/unifi/network-scope.d.ts.map +1 -0
- package/dist/unifi/network-update.d.ts +44 -0
- package/dist/unifi/network-update.d.ts.map +1 -0
- package/dist/unifi/network.d.ts +32 -12
- package/dist/unifi/network.d.ts.map +1 -1
- package/dist/unifi/policy.d.ts +89 -1
- package/dist/unifi/policy.d.ts.map +1 -1
- package/dist/unifi/resource.d.ts +17 -43
- package/dist/unifi/resource.d.ts.map +1 -1
- package/dist/unifi/traffic-matching-list.d.ts +6 -1
- package/dist/unifi/traffic-matching-list.d.ts.map +1 -1
- package/dist/unifi/update-reconcile.d.ts +31 -0
- package/dist/unifi/update-reconcile.d.ts.map +1 -0
- package/dist/unifi/wifi-broadcast.d.ts +6 -1
- package/dist/unifi/wifi-broadcast.d.ts.map +1 -1
- package/dist/unifi/wire-guard.d.ts +33 -0
- package/dist/unifi/wire-guard.d.ts.map +1 -0
- package/docs/kubernetes-ready.md +146 -0
- package/docs/peers.md +13 -0
- package/docs/provider-standard.md +7 -0
- package/docs/unifi-network-write.md +90 -0
- package/docs/unifi.md +36 -36
- package/package.json +7 -1
- package/src/kubernetes/helm-chart-bounded.test.ts +242 -0
- package/src/kubernetes/helm-chart-bounded.ts +110 -0
- package/src/kubernetes/index.ts +28 -0
- package/src/kubernetes/ready-backoff.test.ts +124 -0
- package/src/kubernetes/ready-checks.test.ts +106 -0
- package/src/kubernetes/ready-checks.ts +165 -0
- package/src/kubernetes/ready-classify.ts +76 -0
- package/src/kubernetes/ready-connect.ts +52 -0
- package/src/kubernetes/ready-declare.test.ts +85 -0
- package/src/kubernetes/ready-declare.ts +50 -0
- package/src/kubernetes/ready-errors.ts +102 -0
- package/src/kubernetes/ready-first-install.test.ts +126 -0
- package/src/kubernetes/ready-poll-states.test.ts +92 -0
- package/src/kubernetes/ready-poll.test.ts +194 -0
- package/src/kubernetes/ready-poll.ts +215 -0
- package/src/kubernetes/ready-rollout.test.ts +58 -0
- package/src/kubernetes/ready-secrecy.test.ts +131 -0
- package/src/kubernetes/ready.fixtures.ts +103 -0
- package/src/kubernetes/ready.test.ts +205 -0
- package/src/kubernetes/ready.ts +188 -0
- package/src/talos/fake-apiserver.ts +21 -3
- package/src/unifi/fake-unifi.ts +15 -2
- package/src/unifi/handler-allow-news.test.ts +80 -0
- package/src/unifi/index.ts +21 -5
- package/src/unifi/network-scope.test.ts +203 -0
- package/src/unifi/network-scope.ts +75 -0
- package/src/unifi/network-update.test.ts +250 -0
- package/src/unifi/network-update.ts +95 -0
- package/src/unifi/network.test.ts +27 -1
- package/src/unifi/network.ts +56 -12
- package/src/unifi/policy.ts +128 -10
- package/src/unifi/resource.test.ts +15 -3
- package/src/unifi/resource.ts +64 -105
- package/src/unifi/update-reconcile.ts +126 -0
- package/src/unifi/wire-guard-anchor.test.ts +198 -0
- package/src/unifi/wire-guard-round2.test.ts +120 -0
- package/src/unifi/wire-guard.test.ts +218 -0
- package/src/unifi/wire-guard.ts +192 -0
- package/src/unifi/write-op-reference.test.ts +38 -2
- package/src/unifi/get-only-guard.test.ts +0 -165
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `boundedHelmChartProvider`: upstream's `Kubernetes.HelmChart` provider with a wall-clock deadline
|
|
3
|
+
* on `reconcile` and `read`. Wrapped, never re-implemented (alchemy-provider-standard: use what
|
|
4
|
+
* Alchemy ships).
|
|
5
|
+
*
|
|
6
|
+
* ⛔ WHY (measured 2026-10-09, alchemy 2.0.0-beta.81): `requestJson` in `Kubernetes/internal/client.ts`
|
|
7
|
+
* sets no timeout and no signal on any request, and retries transport errors only. A hung apiserver
|
|
8
|
+
* therefore blocks a HelmChart reconcile or read forever, holding the 1h platform token across a
|
|
9
|
+
* stuck deploy. The deadline turns that into a typed `KubernetesReconcileTimeout`.
|
|
10
|
+
* ★ A DIRECT `Provider(HelmChart)` layer, not a collection member: the engine checks the direct
|
|
11
|
+
* service before any collection (`Provider.ts` `tryFindProviderRegistrationByType`), so merging
|
|
12
|
+
* this beside `Kubernetes.providers()` makes the wrapper win with no change to upstream's layer.
|
|
13
|
+
* ⚠️ The interrupted fiber ABANDONS its socket (upstream takes no signal). The run then fails typed
|
|
14
|
+
* and the process exits once main completes, so a dangling socket cannot hold anything.
|
|
15
|
+
* `diff`, `delete`, `stables` and `aliases` come from upstream by spread, so state identity and
|
|
16
|
+
* replace semantics are exactly upstream's.
|
|
17
|
+
*/
|
|
18
|
+
import { HelmChart } from 'alchemy/Kubernetes/HelmChart';
|
|
19
|
+
import * as Provider from 'alchemy/Provider';
|
|
20
|
+
import * as Layer from 'effect/Layer';
|
|
21
|
+
/** A render + discovery + ~60 server-side-apply PATCHes fit well inside this. */
|
|
22
|
+
export declare const HELM_RECONCILE_TIMEOUT = "5m0s";
|
|
23
|
+
/** `read` is one connect (identity GET): a minute is generous. */
|
|
24
|
+
export declare const HELM_READ_TIMEOUT = "1m0s";
|
|
25
|
+
declare const KubernetesReconcileTimeout_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
26
|
+
readonly _tag: "KubernetesReconcileTimeout";
|
|
27
|
+
} & Readonly<A>;
|
|
28
|
+
/** The deadline passed with no answer from the apiserver. Names the row and seconds only. */
|
|
29
|
+
export declare class KubernetesReconcileTimeout extends KubernetesReconcileTimeout_base<{
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly operation: 'reconcile' | 'read';
|
|
32
|
+
readonly seconds: number;
|
|
33
|
+
}> {
|
|
34
|
+
get message(): string;
|
|
35
|
+
}
|
|
36
|
+
/** True for a service built by `boundedHelmChartProvider`: lets a stack test its real layer. */
|
|
37
|
+
export declare const isBoundedHelmChartProvider: (service: unknown) => boolean;
|
|
38
|
+
/**
|
|
39
|
+
* ⛔ A malformed duration dies at layer build (`Effect.orDie`), never becomes a silent unbounded
|
|
40
|
+
* call: a bad override cannot quietly drop the deadline.
|
|
41
|
+
*/
|
|
42
|
+
export declare const boundedHelmChartProvider: (reconcileTimeout?: string, readTimeout?: string) => Layer.Layer<Provider.Provider<HelmChart>, never, import("effect/process/ChildProcessSpawner").ChildProcessSpawner | import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("alchemy").Stack | import("alchemy").Stage>;
|
|
43
|
+
export {};
|
|
44
|
+
//# sourceMappingURL=helm-chart-bounded.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helm-chart-bounded.d.ts","sourceRoot":"","sources":["../../src/kubernetes/helm-chart-bounded.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,SAAS,EAAqB,MAAM,8BAA8B,CAAC;AAC5E,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAK7C,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAItC,iFAAiF;AACjF,eAAO,MAAM,sBAAsB,SAAS,CAAC;AAC7C,kEAAkE;AAClE,eAAO,MAAM,iBAAiB,SAAS,CAAC;;;;AAExC,6FAA6F;AAC7F,qBAAa,0BAA2B,SAAQ,gCAA+C;IAC7F,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,SAAS,EAAE,WAAW,GAAG,MAAM,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;IACA,IAAa,OAAO,IAAI,MAAM,CAE7B;CACF;AA6BD,gGAAgG;AAChG,eAAO,MAAM,0BAA0B,YAAa,OAAO,KAAG,OAGN,CAAC;AAEzD;;;GAGG;AACH,eAAO,MAAM,wBAAwB,sBACjB,MAAM,gBACX,MAAM,iPA2BuB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kubernetes helpers for Alchemy that upstream does not ship.
|
|
3
|
+
*
|
|
4
|
+
* ⛔ A DELIBERATELY SMALL BARREL (the `talos/index.ts` rule): the resource, its provider, its
|
|
5
|
+
* props and the typed errors a stack can `catchTag`. The evaluators and the poll stay internal.
|
|
6
|
+
*/
|
|
7
|
+
export { HELM_READ_TIMEOUT, HELM_RECONCILE_TIMEOUT, KubernetesReconcileTimeout, boundedHelmChartProvider, isBoundedHelmChartProvider, } from './helm-chart-bounded.ts';
|
|
8
|
+
export { KubernetesReady, KubernetesReadyProvider, type ReadyAttributes, type ReadyProps, } from './ready.ts';
|
|
9
|
+
export type { ReadyCheck } from './ready-checks.ts';
|
|
10
|
+
export { KubernetesReadyBadAfter, KubernetesReadyBadCheck, KubernetesReadyBadDuration, KubernetesReadyTimeout, KubernetesRolloutFailed, } from './ready-errors.ts';
|
|
11
|
+
export { KubernetesReadyApiError, KubernetesReadyGetTimeout } from './ready-poll.ts';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/kubernetes/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,0BAA0B,EAC1B,wBAAwB,EACxB,0BAA0B,GAC3B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,KAAK,eAAe,EACpB,KAAK,UAAU,GAChB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,0BAA0B,EAC1B,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
// src/kubernetes/helm-chart-bounded.ts
|
|
2
|
+
import { HelmChart, HelmChartProvider } from "alchemy/Kubernetes/HelmChart";
|
|
3
|
+
import * as Provider2 from "alchemy/Provider";
|
|
4
|
+
import * as Data5 from "effect/Data";
|
|
5
|
+
import * as Duration3 from "effect/Duration";
|
|
6
|
+
import * as Effect6 from "effect/Effect";
|
|
7
|
+
import * as Layer from "effect/Layer";
|
|
8
|
+
|
|
9
|
+
// src/kubernetes/ready-classify.ts
|
|
10
|
+
import * as Data from "effect/Data";
|
|
11
|
+
var isNotFound = (error) => {
|
|
12
|
+
const e = error;
|
|
13
|
+
return e?._tag === "KubernetesNotFound" || e?._tag === "KubernetesApiError" && e.statusCode === 404;
|
|
14
|
+
};
|
|
15
|
+
var UPSTREAM_TRANSPORT = /^Failed Kubernetes /;
|
|
16
|
+
var transientOf = (error) => {
|
|
17
|
+
const e = error;
|
|
18
|
+
if (e?._tag === "KubernetesReadyGetTimeout")
|
|
19
|
+
return { tag: "KubernetesReadyGetTimeout" };
|
|
20
|
+
if (e?._tag === "TalosClusterIdentityTimeout")
|
|
21
|
+
return { tag: "TalosClusterIdentityTimeout" };
|
|
22
|
+
if (e?._tag === "KubernetesApiError") {
|
|
23
|
+
const status = typeof e.statusCode === "number" ? e.statusCode : 0;
|
|
24
|
+
return status >= 500 || status === 429 ? { status, tag: "KubernetesApiError" } : undefined;
|
|
25
|
+
}
|
|
26
|
+
return error instanceof Error && e?._tag === undefined && UPSTREAM_TRANSPORT.test(error.message) ? { tag: "TransportError" } : undefined;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
class KubernetesReadyApiError extends Data.TaggedError("KubernetesReadyApiError") {
|
|
30
|
+
get message() {
|
|
31
|
+
return `Kubernetes.Ready: ${this.method} ${this.path} responded ${this.statusCode}`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
var scrubbed = (error) => {
|
|
35
|
+
const e = error;
|
|
36
|
+
return e?._tag === "KubernetesApiError" ? new KubernetesReadyApiError({
|
|
37
|
+
method: String(e.method),
|
|
38
|
+
path: String(e.path),
|
|
39
|
+
statusCode: Number(e.statusCode)
|
|
40
|
+
}) : error;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// src/kubernetes/ready.ts
|
|
44
|
+
import { Resource } from "alchemy";
|
|
45
|
+
import { isResolved } from "alchemy/Diff";
|
|
46
|
+
import * as Provider from "alchemy/Provider";
|
|
47
|
+
import * as Duration2 from "effect/Duration";
|
|
48
|
+
import * as Effect5 from "effect/Effect";
|
|
49
|
+
|
|
50
|
+
// src/talos/literal-connection.ts
|
|
51
|
+
import { hasUnresolvedInputs } from "alchemy/Diff";
|
|
52
|
+
import * as Effect from "effect/Effect";
|
|
53
|
+
|
|
54
|
+
// src/talos/cluster-adapter-errors.ts
|
|
55
|
+
import * as Data2 from "effect/Data";
|
|
56
|
+
|
|
57
|
+
class TalosOpenBaoUnknownCluster extends Data2.TaggedError("TalosOpenBaoUnknownCluster") {
|
|
58
|
+
get message() {
|
|
59
|
+
return `talos-openbao adapter has no entry pinned to cluster uid ${this.uid} ` + `(configured uids: ${this.known.join(", ") || "none"}). Refused rather than reading ` + "another cluster's kubeconfig. If the cluster was retired, keep its entry with " + "`retired: true` and its uid until its rows are destroyed.";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
class TalosOpenBaoAmbiguousUid extends Data2.TaggedError("TalosOpenBaoAmbiguousUid") {
|
|
64
|
+
get message() {
|
|
65
|
+
return `talos-openbao adapter entries ${this.aliases.join(", ")} are all pinned to uid ` + `${this.uid}. Refused instead of picking one: a uid names exactly one entry.`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
class TalosOpenBaoLegacyAuth extends Data2.TaggedError("TalosOpenBaoLegacyAuth") {
|
|
70
|
+
get message() {
|
|
71
|
+
return "talos-openbao connection carries a legacy `cluster` alias in its auth block. Refused " + "before any vault read or request, with no silent migration (old and new auth differ, so " + "upstream would PATCH then DELETE the same object). Remedy: edit the saved state ONCE so " + "each such row's `connection.auth` is `{ kind: 'talos-openbao', uid }` (drop `cluster`), " + "then redeploy.";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
class TalosClusterMoved extends Data2.TaggedError("TalosClusterMoved") {
|
|
76
|
+
get message() {
|
|
77
|
+
return `Talos.ClusterIdentity '${this.cluster}' published uid ${this.saved} but the vault key now ` + `answers as ${this.live}. Refused, not updated: an update cannot become a replace for the ` + "workloads downstream, so the new cluster would be force-applied and the old one orphaned. " + "A move is explicit: declare a NEW Talos.ClusterIdentity for the new cluster and leave this " + "one pointing at the old cluster until its workloads are destroyed.";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
class TalosVaultKeyMissing extends Data2.TaggedError("TalosVaultKeyMissing") {
|
|
82
|
+
get message() {
|
|
83
|
+
return `${this.mount}/${this.key}: OpenBao has no value at this key. Talos.Kubeconfig writes it ` + "once at bring-up. Connect refused instead of reading a kubeconfig from disk.";
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
class TalosKubeconfigUnreadable extends Data2.TaggedError("TalosKubeconfigUnreadable") {
|
|
88
|
+
get message() {
|
|
89
|
+
return `${this.mount}/${this.key}: OpenBao kubeconfig does not parse for context ${this.context}. ` + "The document stayed in the vault. Connect wrote nothing to disk and nothing to state.";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
class TalosOpenBaoAuthKind extends Data2.TaggedError("TalosOpenBaoAuthKind") {
|
|
94
|
+
get message() {
|
|
95
|
+
return `talos-openbao adapter received auth kind '${this.kind}'. This adapter only connects ` + "`talos-openbao`.";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
class TalosClusterIdentityMissing extends Data2.TaggedError("TalosClusterIdentityMissing") {
|
|
100
|
+
get message() {
|
|
101
|
+
return "talos-openbao connection carries no uid, so it cannot say which physical cluster it " + "targets. Refused before any vault read or request: pin the uid " + "printed by `Talos.ClusterIdentity` as a literal in `TalosOpenBaoAdapter` and build " + "workloads from `talosOpenBaoConnection(<that literal>)`.";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
class TalosClusterIdentityMismatch extends Data2.TaggedError("TalosClusterIdentityMismatch") {
|
|
106
|
+
get message() {
|
|
107
|
+
return `talos-openbao cluster '${this.cluster}' answered as kube-system uid ${this.got}, but the ` + `saved connection expects ${this.expected}. Refused before any apply or delete: the name ` + "now resolves to a different physical cluster. If the original cluster is gone for good, " + "remove its rows from state deliberately.";
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
class TalosClusterIdentityUnreadable extends Data2.TaggedError("TalosClusterIdentityUnreadable") {
|
|
112
|
+
get message() {
|
|
113
|
+
return `talos-openbao cluster '${this.cluster}': kube-system has no readable metadata.uid. ` + "Refused rather than assuming the cluster is the expected one.";
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class TalosClusterIdentityTimeout extends Data2.TaggedError("TalosClusterIdentityTimeout") {
|
|
118
|
+
get message() {
|
|
119
|
+
return `talos-openbao cluster '${this.cluster}': no answer to the kube-system uid read within ` + `${this.seconds}s (the identity read's own deadline, inside the 10 s connect deadline). ` + "Refused (failed closed) rather than waiting on a silent apiserver.";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
class TalosOpenBaoConnectTimeout extends Data2.TaggedError("TalosOpenBaoConnectTimeout") {
|
|
124
|
+
get message() {
|
|
125
|
+
return `talos-openbao cluster '${this.cluster}': vault read and identity check did not finish ` + `within ${this.seconds}s. Refused (failed closed); the bao child was killed.`;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
class TalosUidNotLiteral extends Data2.TaggedError("TalosUidNotLiteral") {
|
|
130
|
+
get message() {
|
|
131
|
+
return "talos-openbao connection uid must be a literal string taken from the adapter config, not " + "an Output (for example a Talos.ClusterIdentity attribute). An unresolved uid hides a " + "cluster change from the plan: upstream would plan an UPDATE and reconcile the old cluster's " + "objects on the new one. Pin the uid in `TalosOpenBaoAdapter` and take it from there.";
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/talos/literal-connection.ts
|
|
136
|
+
var literalConnectionRefusal = (connection) => {
|
|
137
|
+
const auth = connection?.auth;
|
|
138
|
+
return hasUnresolvedInputs(connection) || auth?.kind !== "talos-openbao" || typeof auth?.uid !== "string" || auth.uid === "" ? new TalosUidNotLiteral({}) : undefined;
|
|
139
|
+
};
|
|
140
|
+
var withLiteralConnection = (resource) => new Proxy(resource, {
|
|
141
|
+
apply: (target, thisArg, args) => {
|
|
142
|
+
const refusal = (given) => literalConnectionRefusal(given?.connection);
|
|
143
|
+
const props = args[1];
|
|
144
|
+
if (Effect.isEffect(props)) {
|
|
145
|
+
const checked = props.pipe(Effect.flatMap((resolved) => {
|
|
146
|
+
const refused2 = refusal(resolved);
|
|
147
|
+
return refused2 === undefined ? Effect.succeed(resolved) : Effect.fail(refused2);
|
|
148
|
+
}));
|
|
149
|
+
return Reflect.apply(target, thisArg, [args[0], checked, ...args.slice(2)]);
|
|
150
|
+
}
|
|
151
|
+
const refused = refusal(props);
|
|
152
|
+
return refused === undefined ? Reflect.apply(target, thisArg, args) : Effect.fail(refused);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// src/kubernetes/ready-errors.ts
|
|
157
|
+
import * as Data3 from "effect/Data";
|
|
158
|
+
var describeState = (state) => {
|
|
159
|
+
if (state === undefined)
|
|
160
|
+
return "";
|
|
161
|
+
if ("notFound" in state)
|
|
162
|
+
return " (not found)";
|
|
163
|
+
const counts = Object.entries(state.counts).map(([name, value]) => `${name}=${value}`);
|
|
164
|
+
return counts.length === 0 ? "" : ` (${counts.join(" ")})`;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
class KubernetesReadyTimeout extends Data3.TaggedError("KubernetesReadyTimeout") {
|
|
168
|
+
get message() {
|
|
169
|
+
const failing = this.failing.map((key) => `${key}${describeState(this.states?.[key])}`);
|
|
170
|
+
const last = this.lastTransient === undefined ? "" : `; last transient error ${this.lastTransient.tag}${this.lastTransient.status === undefined ? "" : ` ${this.lastTransient.status}`}`;
|
|
171
|
+
return `Kubernetes.Ready: not ready within ${this.seconds}s: ${failing.join(", ")}${last}`;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
class KubernetesRolloutFailed extends Data3.TaggedError("KubernetesRolloutFailed") {
|
|
176
|
+
get message() {
|
|
177
|
+
return this.reason === "UpdateStrategyNotRollingUpdate" ? `Kubernetes.Ready: ${this.check} does not use the RollingUpdate update strategy` : `Kubernetes.Ready: ${this.check} exceeded its progress deadline (ProgressDeadlineExceeded)`;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
class KubernetesReadyBadDuration extends Data3.TaggedError("KubernetesReadyBadDuration") {
|
|
182
|
+
get message() {
|
|
183
|
+
const shown = this.value.length > 40 ? `${this.value.slice(0, 40)}...` : this.value;
|
|
184
|
+
return `${this.resource ?? "Kubernetes.Ready"}: ${this.field} '${shown}' is not a duration like 10m0s`;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
class KubernetesReadyBadCheck extends Data3.TaggedError("KubernetesReadyBadCheck") {
|
|
189
|
+
get message() {
|
|
190
|
+
return `Kubernetes.Ready: checks[${this.index}] ${this.problem}`;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
class KubernetesReadyBadAfter extends Data3.TaggedError("KubernetesReadyBadAfter") {
|
|
195
|
+
get message() {
|
|
196
|
+
return `Kubernetes.Ready: after[${this.index}] is not chart.objects (the only shape that waits for the chart's update)`;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/kubernetes/ready-checks.ts
|
|
201
|
+
var checkKey = (check) => {
|
|
202
|
+
switch (check.kind) {
|
|
203
|
+
case "DaemonSet":
|
|
204
|
+
return `DaemonSet/${check.namespace}/${check.name}${check.minReady === undefined ? "" : `:min=${check.minReady}`}`;
|
|
205
|
+
case "Deployment":
|
|
206
|
+
return `Deployment/${check.namespace}/${check.name}`;
|
|
207
|
+
case "CustomResourceDefinition":
|
|
208
|
+
return `CustomResourceDefinition/${check.name}`;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
var checksCsv = (checks) => checks.map(checkKey).join(",");
|
|
212
|
+
var validateChecks = (checks) => {
|
|
213
|
+
if (!Array.isArray(checks) || checks.length === 0) {
|
|
214
|
+
return new KubernetesReadyBadCheck({ index: 0, problem: "must list at least one check" });
|
|
215
|
+
}
|
|
216
|
+
for (const [index, raw] of checks.entries()) {
|
|
217
|
+
const check = obj(raw);
|
|
218
|
+
const bad = (problem) => new KubernetesReadyBadCheck({ index, problem });
|
|
219
|
+
const kind = check["kind"];
|
|
220
|
+
if (kind !== "DaemonSet" && kind !== "Deployment" && kind !== "CustomResourceDefinition") {
|
|
221
|
+
return bad("has an unknown kind");
|
|
222
|
+
}
|
|
223
|
+
if (typeof check["name"] !== "string" || check["name"] === "") {
|
|
224
|
+
return bad("needs a non-empty name");
|
|
225
|
+
}
|
|
226
|
+
if (kind !== "CustomResourceDefinition") {
|
|
227
|
+
if (typeof check["namespace"] !== "string" || check["namespace"] === "") {
|
|
228
|
+
return bad(`needs a non-empty namespace (${kind} is namespaced)`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const min = check["minReady"];
|
|
232
|
+
if (min !== undefined && (kind !== "DaemonSet" || !Number.isInteger(min) || Number(min) < 1)) {
|
|
233
|
+
return bad("minReady is an integer >= 1 and only applies to a DaemonSet");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return;
|
|
237
|
+
};
|
|
238
|
+
var obj = (value) => typeof value === "object" && value !== null ? value : {};
|
|
239
|
+
var num = (value) => typeof value === "number" ? value : 0;
|
|
240
|
+
var countsOf = (check, body) => {
|
|
241
|
+
const status = obj(obj(body)["status"]);
|
|
242
|
+
const names = check.kind === "DaemonSet" ? [
|
|
243
|
+
"observedGeneration",
|
|
244
|
+
"desiredNumberScheduled",
|
|
245
|
+
"updatedNumberScheduled",
|
|
246
|
+
"numberAvailable"
|
|
247
|
+
] : check.kind === "Deployment" ? ["observedGeneration", "replicas", "updatedReplicas", "availableReplicas"] : [];
|
|
248
|
+
const counts = Object.fromEntries(names.map((name) => [name, num(status[name])]));
|
|
249
|
+
return { counts: { generation: num(obj(obj(body)["metadata"])["generation"]), ...counts } };
|
|
250
|
+
};
|
|
251
|
+
var conditions = (status) => Array.isArray(status["conditions"]) ? status["conditions"].map(obj) : [];
|
|
252
|
+
var daemonSet = (body, minReady) => {
|
|
253
|
+
const status = obj(body["status"]);
|
|
254
|
+
const strategy = obj(obj(body["spec"])["updateStrategy"])["type"];
|
|
255
|
+
if (strategy !== undefined && strategy !== "RollingUpdate")
|
|
256
|
+
return "failed";
|
|
257
|
+
const generation = num(obj(body["metadata"])["generation"]);
|
|
258
|
+
if (num(status["observedGeneration"]) < generation)
|
|
259
|
+
return "pending";
|
|
260
|
+
const desired = num(status["desiredNumberScheduled"]);
|
|
261
|
+
const need = minReady ?? desired;
|
|
262
|
+
if (need < 1)
|
|
263
|
+
return "pending";
|
|
264
|
+
const updated = num(status["updatedNumberScheduled"]);
|
|
265
|
+
const oldAtMost = Math.max(desired - updated, 0);
|
|
266
|
+
return updated >= need && num(status["numberAvailable"]) - oldAtMost >= need ? "ready" : "pending";
|
|
267
|
+
};
|
|
268
|
+
var deployment = (body) => {
|
|
269
|
+
const status = obj(body["status"]);
|
|
270
|
+
const generation = num(obj(body["metadata"])["generation"]);
|
|
271
|
+
if (num(status["observedGeneration"]) < generation)
|
|
272
|
+
return "pending";
|
|
273
|
+
const exceeded = conditions(status).some((c) => c["type"] === "Progressing" && c["reason"] === "ProgressDeadlineExceeded");
|
|
274
|
+
if (exceeded)
|
|
275
|
+
return "failed";
|
|
276
|
+
const desired = obj(body["spec"])["replicas"] === undefined ? 1 : num(obj(body["spec"])["replicas"]);
|
|
277
|
+
const updated = num(status["updatedReplicas"]);
|
|
278
|
+
return updated >= desired && num(status["replicas"]) === updated && num(status["availableReplicas"]) >= updated ? "ready" : "pending";
|
|
279
|
+
};
|
|
280
|
+
var established = (body) => conditions(obj(body["status"])).some((c) => c["type"] === "Established" && c["status"] === "True") ? "ready" : "pending";
|
|
281
|
+
var evaluate = (check, body) => {
|
|
282
|
+
if (typeof body !== "object" || body === null)
|
|
283
|
+
return "pending";
|
|
284
|
+
switch (check.kind) {
|
|
285
|
+
case "DaemonSet":
|
|
286
|
+
return daemonSet(body, check.minReady);
|
|
287
|
+
case "Deployment":
|
|
288
|
+
return deployment(body);
|
|
289
|
+
case "CustomResourceDefinition":
|
|
290
|
+
return established(body);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// src/kubernetes/ready-connect.ts
|
|
295
|
+
import { connectCluster } from "alchemy/Kubernetes/internal/client";
|
|
296
|
+
import * as Effect2 from "effect/Effect";
|
|
297
|
+
var connectOnce = (connection) => connectCluster(connection).pipe(Effect2.mapError(scrubbed));
|
|
298
|
+
var connectForDiff = (connection) => Effect2.gen(function* () {
|
|
299
|
+
const attempt = yield* Effect2.result(connectCluster(connection));
|
|
300
|
+
if (attempt._tag === "Success")
|
|
301
|
+
return attempt.success;
|
|
302
|
+
return transientOf(attempt.failure) === undefined ? yield* Effect2.fail(scrubbed(attempt.failure)) : undefined;
|
|
303
|
+
});
|
|
304
|
+
var connectTolerant = (connection, pollInterval) => (note) => Effect2.gen(function* () {
|
|
305
|
+
while (true) {
|
|
306
|
+
const attempt = yield* Effect2.result(connectCluster(connection));
|
|
307
|
+
if (attempt._tag === "Success")
|
|
308
|
+
return attempt.success;
|
|
309
|
+
const transient = transientOf(attempt.failure);
|
|
310
|
+
if (transient === undefined)
|
|
311
|
+
return yield* Effect2.fail(scrubbed(attempt.failure));
|
|
312
|
+
yield* note(transient);
|
|
313
|
+
yield* Effect2.sleep(pollInterval);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// src/kubernetes/ready-declare.ts
|
|
318
|
+
import { isPropExpr } from "alchemy/Output";
|
|
319
|
+
import * as Effect3 from "effect/Effect";
|
|
320
|
+
var declarationRefusal = (props) => {
|
|
321
|
+
const { after, checks } = props ?? {};
|
|
322
|
+
const badCheck = validateChecks(checks);
|
|
323
|
+
if (badCheck !== undefined)
|
|
324
|
+
return badCheck;
|
|
325
|
+
if (after === undefined)
|
|
326
|
+
return;
|
|
327
|
+
const list = Array.isArray(after) ? after : [after];
|
|
328
|
+
const index = list.findIndex((item) => !(isPropExpr(item) && item.identifier === "objects"));
|
|
329
|
+
return index === -1 ? undefined : new KubernetesReadyBadAfter({ index });
|
|
330
|
+
};
|
|
331
|
+
var declaredReady = (resource) => new Proxy(resource, {
|
|
332
|
+
apply: (target, thisArg, args) => {
|
|
333
|
+
const props = args[1];
|
|
334
|
+
if (Effect3.isEffect(props)) {
|
|
335
|
+
const checked = props.pipe(Effect3.flatMap((resolved) => {
|
|
336
|
+
const refused2 = declarationRefusal(resolved);
|
|
337
|
+
return refused2 === undefined ? Effect3.succeed(resolved) : Effect3.fail(refused2);
|
|
338
|
+
}));
|
|
339
|
+
return Reflect.apply(target, thisArg, [args[0], checked, ...args.slice(2)]);
|
|
340
|
+
}
|
|
341
|
+
const refused = declarationRefusal(props);
|
|
342
|
+
return refused === undefined ? Reflect.apply(target, thisArg, args) : Effect3.fail(refused);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// src/kubernetes/ready-poll.ts
|
|
347
|
+
import { readObject } from "alchemy/Kubernetes/internal/client";
|
|
348
|
+
import * as Data4 from "effect/Data";
|
|
349
|
+
import * as Duration from "effect/Duration";
|
|
350
|
+
import * as Effect4 from "effect/Effect";
|
|
351
|
+
import * as Ref from "effect/Ref";
|
|
352
|
+
var GET_TIMEOUT = Duration.seconds(5);
|
|
353
|
+
|
|
354
|
+
class KubernetesReadyGetTimeout extends Data4.TaggedError("KubernetesReadyGetTimeout") {
|
|
355
|
+
get message() {
|
|
356
|
+
return `Kubernetes.Ready: GET for ${this.check} timed out after ${this.seconds}s`;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
var REFS = {
|
|
360
|
+
CustomResourceDefinition: {
|
|
361
|
+
apiVersion: "apiextensions.k8s.io/v1",
|
|
362
|
+
kind: "CustomResourceDefinition"
|
|
363
|
+
},
|
|
364
|
+
DaemonSet: { apiVersion: "apps/v1", kind: "DaemonSet" },
|
|
365
|
+
Deployment: { apiVersion: "apps/v1", kind: "Deployment" }
|
|
366
|
+
};
|
|
367
|
+
var refOf = (check) => ({
|
|
368
|
+
...REFS[check.kind],
|
|
369
|
+
name: check.name,
|
|
370
|
+
..."namespace" in check ? { namespace: check.namespace } : {}
|
|
371
|
+
});
|
|
372
|
+
var BACKOFF_CAP = Duration.seconds(30);
|
|
373
|
+
var getOne = (transport, check) => readObject({ object: refOf(check), transport }).pipe(Effect4.timeoutOrElse({
|
|
374
|
+
duration: GET_TIMEOUT,
|
|
375
|
+
orElse: () => Effect4.fail(new KubernetesReadyGetTimeout({
|
|
376
|
+
check: checkKey(check),
|
|
377
|
+
seconds: Duration.toSeconds(GET_TIMEOUT)
|
|
378
|
+
}))
|
|
379
|
+
}));
|
|
380
|
+
var pass = (transport, checks, tolerant) => Effect4.gen(function* () {
|
|
381
|
+
const pending = [];
|
|
382
|
+
const failed = [];
|
|
383
|
+
const states = {};
|
|
384
|
+
let lastTransient;
|
|
385
|
+
let timedOut = false;
|
|
386
|
+
for (const check of checks) {
|
|
387
|
+
const key = checkKey(check);
|
|
388
|
+
if (timedOut) {
|
|
389
|
+
pending.push(key);
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
const outcome = yield* getOne(transport, check).pipe(Effect4.map((body) => {
|
|
393
|
+
states[key] = countsOf(check, body);
|
|
394
|
+
return evaluate(check, body);
|
|
395
|
+
}), Effect4.catch((error) => {
|
|
396
|
+
if (isNotFound(error)) {
|
|
397
|
+
states[key] = { notFound: true };
|
|
398
|
+
return Effect4.succeed("pending");
|
|
399
|
+
}
|
|
400
|
+
const transient = tolerant ? transientOf(error) : undefined;
|
|
401
|
+
if (transient === undefined)
|
|
402
|
+
return Effect4.fail(scrubbed(error));
|
|
403
|
+
lastTransient = transient;
|
|
404
|
+
timedOut = timedOut || transient.tag === "KubernetesReadyGetTimeout";
|
|
405
|
+
return Effect4.succeed("pending");
|
|
406
|
+
}));
|
|
407
|
+
if (outcome === "pending")
|
|
408
|
+
pending.push(key);
|
|
409
|
+
if (outcome === "failed")
|
|
410
|
+
failed.push(key);
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
failed,
|
|
414
|
+
pending,
|
|
415
|
+
states,
|
|
416
|
+
timedOut,
|
|
417
|
+
...lastTransient === undefined ? {} : { lastTransient }
|
|
418
|
+
};
|
|
419
|
+
});
|
|
420
|
+
var poll = (connect, checks, waitTimeout, pollInterval) => Effect4.gen(function* () {
|
|
421
|
+
const pendingNow = checks.map(checkKey);
|
|
422
|
+
const last = yield* Ref.make({
|
|
423
|
+
pending: pendingNow,
|
|
424
|
+
states: {}
|
|
425
|
+
});
|
|
426
|
+
const transient = yield* Ref.make(undefined);
|
|
427
|
+
const loop = Effect4.gen(function* () {
|
|
428
|
+
const transport = yield* connect((t) => Ref.set(transient, t));
|
|
429
|
+
let pause = pollInterval;
|
|
430
|
+
while (true) {
|
|
431
|
+
const result2 = yield* pass(transport, checks, true);
|
|
432
|
+
yield* Ref.update(last, (prior) => ({
|
|
433
|
+
pending: result2.pending,
|
|
434
|
+
states: { ...prior.states, ...result2.states }
|
|
435
|
+
}));
|
|
436
|
+
yield* Ref.set(transient, result2.lastTransient);
|
|
437
|
+
const [firstFailed] = result2.failed;
|
|
438
|
+
if (firstFailed !== undefined) {
|
|
439
|
+
return yield* Effect4.fail(new KubernetesRolloutFailed({
|
|
440
|
+
check: firstFailed,
|
|
441
|
+
reason: firstFailed.startsWith("DaemonSet/") ? "UpdateStrategyNotRollingUpdate" : "ProgressDeadlineExceeded"
|
|
442
|
+
}));
|
|
443
|
+
}
|
|
444
|
+
if (result2.pending.length === 0)
|
|
445
|
+
return;
|
|
446
|
+
pause = result2.timedOut ? Duration.min(Duration.times(pause, 2), Duration.max(BACKOFF_CAP, pollInterval)) : pollInterval;
|
|
447
|
+
yield* Effect4.sleep(pause);
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
return yield* loop.pipe(Effect4.timeoutOrElse({
|
|
451
|
+
duration: waitTimeout,
|
|
452
|
+
orElse: () => Effect4.gen(function* () {
|
|
453
|
+
const { pending, states } = yield* Ref.get(last);
|
|
454
|
+
const lastTransient = yield* Ref.get(transient);
|
|
455
|
+
const seen = Object.fromEntries(pending.flatMap((k) => states[k] ? [[k, states[k]]] : []));
|
|
456
|
+
return yield* Effect4.fail(new KubernetesReadyTimeout({
|
|
457
|
+
failing: pending,
|
|
458
|
+
seconds: Duration.toSeconds(waitTimeout),
|
|
459
|
+
states: seen,
|
|
460
|
+
...lastTransient === undefined ? {} : { lastTransient }
|
|
461
|
+
}));
|
|
462
|
+
})
|
|
463
|
+
}));
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// src/kubernetes/ready.ts
|
|
467
|
+
var KubernetesReady = declaredReady(withLiteralConnection(Resource("HomeFlare.Kubernetes.Ready")));
|
|
468
|
+
var HOUR_MS = 3600000;
|
|
469
|
+
var MINUTE_MS = 60000;
|
|
470
|
+
var SECOND_MS = 1000;
|
|
471
|
+
var GO_DURATION = /^(?:(\d{1,4})h)?(?:(\d{1,4})m)?(?:(\d{1,6})s)?$/;
|
|
472
|
+
var MAX_DURATION_LENGTH = 20;
|
|
473
|
+
var parseGoDuration = (field, value, resource) => Effect5.gen(function* () {
|
|
474
|
+
const refuse = () => Effect5.fail(new KubernetesReadyBadDuration({
|
|
475
|
+
field,
|
|
476
|
+
value,
|
|
477
|
+
...resource === undefined ? {} : { resource }
|
|
478
|
+
}));
|
|
479
|
+
const match = value.length > MAX_DURATION_LENGTH ? null : GO_DURATION.exec(value);
|
|
480
|
+
if (match === null || value === "")
|
|
481
|
+
return yield* refuse();
|
|
482
|
+
const [, h, m, s] = match;
|
|
483
|
+
const ms = Number(h ?? 0) * HOUR_MS + Number(m ?? 0) * MINUTE_MS + Number(s ?? 0) * SECOND_MS;
|
|
484
|
+
if (ms <= 0)
|
|
485
|
+
return yield* refuse();
|
|
486
|
+
return Duration2.millis(ms);
|
|
487
|
+
});
|
|
488
|
+
var attributes = (props, ready) => ({
|
|
489
|
+
checks: checksCsv(props.checks),
|
|
490
|
+
connection: props.connection,
|
|
491
|
+
ready
|
|
492
|
+
});
|
|
493
|
+
var sameConnection = (left, right) => right?.auth?.kind === left.auth.kind && right.auth.uid === left.auth.uid && !Object.hasOwn(right.auth, "cluster");
|
|
494
|
+
var readReady = (props) => Effect5.gen(function* () {
|
|
495
|
+
const transport = yield* connectOnce(props.connection);
|
|
496
|
+
const result2 = yield* pass(transport, props.checks, false);
|
|
497
|
+
return attributes(props, result2.pending.length === 0 && result2.failed.length === 0);
|
|
498
|
+
});
|
|
499
|
+
var readReadyForDiff = (props) => Effect5.gen(function* () {
|
|
500
|
+
const transport = yield* connectForDiff(props.connection);
|
|
501
|
+
if (transport === undefined)
|
|
502
|
+
return;
|
|
503
|
+
const result2 = yield* pass(transport, props.checks, true);
|
|
504
|
+
const settled = result2.pending.length === 0 && result2.failed.length === 0;
|
|
505
|
+
return settled ? attributes(props, true) : undefined;
|
|
506
|
+
});
|
|
507
|
+
var reconcileReady = (props) => Effect5.gen(function* () {
|
|
508
|
+
const waitTimeout = yield* parseGoDuration("waitTimeout", props.waitTimeout ?? "10m0s");
|
|
509
|
+
const pollInterval = yield* parseGoDuration("pollInterval", props.pollInterval ?? "10s");
|
|
510
|
+
const connect = connectTolerant(props.connection, pollInterval);
|
|
511
|
+
yield* poll(connect, props.checks, waitTimeout, pollInterval);
|
|
512
|
+
return attributes(props, true);
|
|
513
|
+
});
|
|
514
|
+
var diffReady = (news, output) => Effect5.gen(function* () {
|
|
515
|
+
const refused = literalConnectionRefusal(news.connection);
|
|
516
|
+
if (refused !== undefined)
|
|
517
|
+
return yield* Effect5.fail(refused);
|
|
518
|
+
if (output === undefined || !isResolved(news))
|
|
519
|
+
return;
|
|
520
|
+
if (!sameConnection(news.connection, output.connection))
|
|
521
|
+
return { action: "replace" };
|
|
522
|
+
const live = yield* readReadyForDiff(news);
|
|
523
|
+
return live !== undefined && live.checks === output.checks ? { action: "noop" } : { action: "update" };
|
|
524
|
+
});
|
|
525
|
+
var readHandler = ({
|
|
526
|
+
olds,
|
|
527
|
+
output
|
|
528
|
+
}) => output === undefined ? Effect5.succeed(undefined) : readReady(olds);
|
|
529
|
+
var handlers = {
|
|
530
|
+
delete: () => Effect5.void,
|
|
531
|
+
diff: ({ news, output }) => diffReady(news, output),
|
|
532
|
+
list: () => Effect5.succeed([]),
|
|
533
|
+
read: readHandler,
|
|
534
|
+
reconcile: ({ news }) => reconcileReady(news),
|
|
535
|
+
stables: ["connection"]
|
|
536
|
+
};
|
|
537
|
+
var KubernetesReadyProvider = () => Provider.effect(KubernetesReady, Effect5.succeed(KubernetesReady.Provider.of(handlers)));
|
|
538
|
+
|
|
539
|
+
// src/kubernetes/helm-chart-bounded.ts
|
|
540
|
+
var HELM_RECONCILE_TIMEOUT = "5m0s";
|
|
541
|
+
var HELM_READ_TIMEOUT = "1m0s";
|
|
542
|
+
|
|
543
|
+
class KubernetesReconcileTimeout extends Data5.TaggedError("KubernetesReconcileTimeout") {
|
|
544
|
+
get message() {
|
|
545
|
+
return `Kubernetes.HelmChart ${this.id}: ${this.operation} did not finish within ${this.seconds}s (the apiserver stopped answering)`;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
var bound = (effect3, deadline, id, operation) => effect3.pipe(Effect6.mapError((error) => scrubbed(error)), Effect6.timeoutOrElse({
|
|
549
|
+
duration: deadline,
|
|
550
|
+
orElse: () => Effect6.fail(new KubernetesReconcileTimeout({ id, operation, seconds: Duration3.toSeconds(deadline) }))
|
|
551
|
+
}));
|
|
552
|
+
var HelmChartKey = Provider2.Provider(HelmChart.Type);
|
|
553
|
+
var BOUNDED = Symbol.for("@homeflare/alchemy/kubernetes/boundedHelmChartProvider");
|
|
554
|
+
var isBoundedHelmChartProvider = (service) => typeof service === "object" && service !== null && service[BOUNDED] === true;
|
|
555
|
+
var boundedHelmChartProvider = (reconcileTimeout = HELM_RECONCILE_TIMEOUT, readTimeout = HELM_READ_TIMEOUT) => Layer.effect(HelmChartKey, Effect6.gen(function* () {
|
|
556
|
+
const inner = yield* HelmChartKey;
|
|
557
|
+
const reconcileDeadline = yield* parseGoDuration("reconcileTimeout", reconcileTimeout, "Kubernetes.HelmChart");
|
|
558
|
+
const innerRead = inner.read?.bind(inner);
|
|
559
|
+
const readDeadline = yield* parseGoDuration("readTimeout", readTimeout, "Kubernetes.HelmChart");
|
|
560
|
+
const service = {
|
|
561
|
+
...inner,
|
|
562
|
+
reconcile: (input) => bound(inner.reconcile(input), reconcileDeadline, input.id, "reconcile"),
|
|
563
|
+
...innerRead === undefined ? {} : { read: (input) => bound(innerRead(input), readDeadline, input.id, "read") }
|
|
564
|
+
};
|
|
565
|
+
return Object.defineProperty(service, BOUNDED, { value: true });
|
|
566
|
+
}).pipe(Effect6.orDie)).pipe(Layer.provide(HelmChartProvider()));
|
|
567
|
+
export {
|
|
568
|
+
HELM_READ_TIMEOUT,
|
|
569
|
+
HELM_RECONCILE_TIMEOUT,
|
|
570
|
+
KubernetesReady,
|
|
571
|
+
KubernetesReadyApiError,
|
|
572
|
+
KubernetesReadyBadAfter,
|
|
573
|
+
KubernetesReadyBadCheck,
|
|
574
|
+
KubernetesReadyBadDuration,
|
|
575
|
+
KubernetesReadyGetTimeout,
|
|
576
|
+
KubernetesReadyProvider,
|
|
577
|
+
KubernetesReadyTimeout,
|
|
578
|
+
KubernetesReconcileTimeout,
|
|
579
|
+
KubernetesRolloutFailed,
|
|
580
|
+
boundedHelmChartProvider,
|
|
581
|
+
isBoundedHelmChartProvider
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
//# debugId=2779EA87BD42448264756E2164756E21
|
|
585
|
+
//# sourceMappingURL=index.js.map
|