alchemy-docker-k3s 0.1.3 → 0.1.5
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/index.d.mts +26 -1
- package/dist/index.mjs +33 -5
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -42,6 +42,29 @@ interface K3sDefinition {
|
|
|
42
42
|
};
|
|
43
43
|
/** K3s' built-in Flannel data plane. @default "vxlan" */
|
|
44
44
|
flannelBackend?: "vxlan" | "wireguard-native";
|
|
45
|
+
/**
|
|
46
|
+
* Kubelet settings applied to every node. Changing them re-runs the K3s installer
|
|
47
|
+
* on each existing node in place, one node at a time; running containers keep running.
|
|
48
|
+
*/
|
|
49
|
+
kubelet?: KubeletSettings;
|
|
50
|
+
}
|
|
51
|
+
interface KubeletSettings {
|
|
52
|
+
/**
|
|
53
|
+
* Image garbage collection. The kubelet starts deleting unused images when the
|
|
54
|
+
* image filesystem is `highThresholdPercent` full and stops at `lowThresholdPercent`.
|
|
55
|
+
* Unset keeps the kubelet defaults (85 % and 80 %).
|
|
56
|
+
*/
|
|
57
|
+
imageGc?: {
|
|
58
|
+
/** Integer percentage, 1–100, above `lowThresholdPercent`. */
|
|
59
|
+
highThresholdPercent: number;
|
|
60
|
+
/** Integer percentage, 0–99, below `highThresholdPercent`. */
|
|
61
|
+
lowThresholdPercent: number;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Further kubelet flags as `name=value`, passed as `--kubelet-arg`. Flags the package
|
|
65
|
+
* manages itself (`cloud-provider`, `provider-id`, `image-gc-*`) are rejected.
|
|
66
|
+
*/
|
|
67
|
+
extraArgs?: string[];
|
|
45
68
|
}
|
|
46
69
|
interface NormalizedK3sDefinition {
|
|
47
70
|
channel: `v1.${number}`;
|
|
@@ -55,6 +78,8 @@ interface NormalizedK3sDefinition {
|
|
|
55
78
|
traefikValues: Record<string, unknown>;
|
|
56
79
|
};
|
|
57
80
|
flannelBackend: "vxlan" | "wireguard-native";
|
|
81
|
+
/** Validated kubelet flags as `name=value`, without the `--kubelet-arg` prefix. */
|
|
82
|
+
kubeletArgs: string[];
|
|
58
83
|
}
|
|
59
84
|
interface ClusterVersion {
|
|
60
85
|
node: string;
|
|
@@ -108,4 +133,4 @@ declare const Cluster: (id: string, props: ClusterProps) => Effect.Effect<Cluste
|
|
|
108
133
|
//#region src/cluster-state.d.ts
|
|
109
134
|
declare const ClusterState: import("alchemy").ResourceClass<ClusterResource>;
|
|
110
135
|
//#endregion
|
|
111
|
-
export { Cluster, type ClusterAttributes, type Cluster$1 as ClusterInstance, type ClusterProps, ClusterState, type K3sDefinition, type PortMapping, Providers, providers };
|
|
136
|
+
export { Cluster, type ClusterAttributes, type Cluster$1 as ClusterInstance, type ClusterProps, ClusterState, type K3sDefinition, type KubeletSettings, type PortMapping, Providers, providers };
|
package/dist/index.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import * as Provider from "alchemy/Provider";
|
|
|
5
5
|
import * as Redacted from "effect/Redacted";
|
|
6
6
|
import { parse } from "yaml";
|
|
7
7
|
import { chmod, mkdir, writeFile } from "node:fs/promises";
|
|
8
|
-
import { dirname, join
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
9
|
import { execFile } from "node:child_process";
|
|
10
10
|
import { promisify } from "node:util";
|
|
11
11
|
import * as Layer from "effect/Layer";
|
|
@@ -51,6 +51,24 @@ const validateUpdateWindow = (window) => {
|
|
|
51
51
|
}
|
|
52
52
|
return window;
|
|
53
53
|
};
|
|
54
|
+
const MANAGED_KUBELET_FLAGS = /^(cloud-provider|provider-id|image-gc-[a-z-]+)=/;
|
|
55
|
+
const KUBELET_FLAG = /^[a-z][a-z0-9-]*=\S*$/;
|
|
56
|
+
/** Validates kubelet settings and renders them as `name=value` kubelet flags. */
|
|
57
|
+
const kubeletArguments = (settings = {}) => {
|
|
58
|
+
const args = [];
|
|
59
|
+
if (settings.imageGc !== void 0) {
|
|
60
|
+
const { highThresholdPercent: high, lowThresholdPercent: low } = settings.imageGc;
|
|
61
|
+
const percent = (value) => Number.isInteger(value) && value >= 0 && value <= 100;
|
|
62
|
+
if (!percent(high) || !percent(low) || low >= high) throw new Error(`kubelet.imageGc thresholds must be integer percentages with lowThresholdPercent < highThresholdPercent; received ${JSON.stringify(settings.imageGc)}`);
|
|
63
|
+
args.push(`image-gc-high-threshold=${high}`, `image-gc-low-threshold=${low}`);
|
|
64
|
+
}
|
|
65
|
+
for (const arg of settings.extraArgs ?? []) {
|
|
66
|
+
if (!KUBELET_FLAG.test(arg)) throw new Error(`kubelet.extraArgs entries must be name=value without a leading --; received ${JSON.stringify(arg)}`);
|
|
67
|
+
if (MANAGED_KUBELET_FLAGS.test(arg)) throw new Error(`kubelet.extraArgs must not set ${JSON.stringify(arg.split("=")[0])}, which the package manages`);
|
|
68
|
+
args.push(arg);
|
|
69
|
+
}
|
|
70
|
+
return args;
|
|
71
|
+
};
|
|
54
72
|
const normalizeK3sDefinition = (definition) => {
|
|
55
73
|
const normalized = {
|
|
56
74
|
channel: validateChannel(definition.channel),
|
|
@@ -63,7 +81,8 @@ const normalizeK3sDefinition = (definition) => {
|
|
|
63
81
|
metricsServer: definition.addons?.metricsServer ?? true,
|
|
64
82
|
traefikValues: definition.addons?.traefikValues ?? {}
|
|
65
83
|
},
|
|
66
|
-
flannelBackend: definition.flannelBackend ?? "vxlan"
|
|
84
|
+
flannelBackend: definition.flannelBackend ?? "vxlan",
|
|
85
|
+
kubeletArgs: kubeletArguments(definition.kubelet)
|
|
67
86
|
};
|
|
68
87
|
if (normalized.flannelBackend !== "vxlan" && normalized.flannelBackend !== "wireguard-native") throw new Error(`flannelBackend must be "vxlan" or "wireguard-native"; received ${JSON.stringify(normalized.flannelBackend)}`);
|
|
69
88
|
const traefikValues = normalized.addons.traefikValues;
|
|
@@ -131,9 +150,16 @@ const resolveChannelVersion = async (channel, fetcher = fetch) => {
|
|
|
131
150
|
};
|
|
132
151
|
//#endregion
|
|
133
152
|
//#region ../shared/src/kubeconfig.ts
|
|
153
|
+
/**
|
|
154
|
+
* Relative to the directory Alchemy runs from. Alchemy identifies a cluster by
|
|
155
|
+
* its connection's auth, which contains this path, so an absolute path would
|
|
156
|
+
* make every checkout or workspace look like a different cluster and replace
|
|
157
|
+
* everything deployed to it. Reading the cluster rewrites the file wherever
|
|
158
|
+
* Alchemy runs.
|
|
159
|
+
*/
|
|
134
160
|
const kubeconfigPath = (provider, fqn) => {
|
|
135
161
|
const safe = fqn.replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-|-$/g, "");
|
|
136
|
-
return
|
|
162
|
+
return join(".alchemy", "kubeconfigs", provider, `${safe}.yaml`);
|
|
137
163
|
};
|
|
138
164
|
const writeKubeconfig = async (path, contents) => {
|
|
139
165
|
await mkdir(dirname(path), {
|
|
@@ -234,7 +260,8 @@ const buildCreateArgs = (props, version, token) => {
|
|
|
234
260
|
`--cluster-cidr=${props.k3s.clusterCidr}`,
|
|
235
261
|
`--service-cidr=${props.k3s.serviceCidr}`,
|
|
236
262
|
`--cluster-dns=${props.k3s.clusterDns}`,
|
|
237
|
-
`--flannel-backend=${props.k3s.flannelBackend}
|
|
263
|
+
`--flannel-backend=${props.k3s.flannelBackend}`,
|
|
264
|
+
...props.k3s.kubeletArgs.map((arg) => `--kubelet-arg=${arg}`)
|
|
238
265
|
]) args.push("--k3s-arg", `${value}@server:0`);
|
|
239
266
|
if (!props.k3s.addons.traefik) args.push("--k3s-arg", "--disable=traefik@server:0");
|
|
240
267
|
if (!props.k3s.addons.metricsServer) args.push("--k3s-arg", "--disable=metrics-server@server:0");
|
|
@@ -418,7 +445,8 @@ const Cluster = (id, props) => Effect.gen(function* () {
|
|
|
418
445
|
clusterCidr: k3s.clusterCidr,
|
|
419
446
|
serviceCidr: k3s.serviceCidr,
|
|
420
447
|
clusterDns: k3s.clusterDns,
|
|
421
|
-
addons: k3s.addons
|
|
448
|
+
addons: k3s.addons,
|
|
449
|
+
kubeletArgs: k3s.kubeletArgs
|
|
422
450
|
})
|
|
423
451
|
});
|
|
424
452
|
});
|