@jr2/cli 0.2.1 → 0.4.1
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 +1 -1
- package/bin/jr2.js +3 -3
- package/manifests/operator.yaml +266 -24
- package/package.json +6 -4
- package/src/assembly.ts +432 -0
- package/src/build.ts +137 -300
- package/src/cli.ts +6 -2
- package/src/client.ts +126 -33
- package/src/commands/down.ts +2 -1
- package/src/commands/init.ts +9 -2
- package/src/commands/kit.ts +24 -12
- package/src/commands/logs.ts +9 -0
- package/src/commands/run.ts +58 -4
- package/src/commands/runs.ts +96 -4
- package/src/commands/send.ts +20 -7
- package/src/commands/status.ts +90 -14
- package/src/commands/up.ts +1174 -183
- package/src/commands/version.ts +18 -13
- package/src/deploy.ts +782 -111
- package/src/effector-image.ts +133 -0
- package/src/held-pki.ts +306 -0
- package/src/index.ts +4 -0
- package/src/kube.ts +115 -9
- package/src/nodes.ts +27 -2
- package/src/sweep.ts +27 -13
package/src/nodes.ts
CHANGED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
// at the moment of the read. The set moves (a pool autoscales, a node is cordoned), which is why an
|
|
8
8
|
// empty set is reported and never refused.
|
|
9
9
|
|
|
10
|
-
import type
|
|
10
|
+
import { cpuMillis, memoryBytes, type ResolvedSize, type SandboxPlacement, type Toleration } from "@jr2/orchestrator";
|
|
11
11
|
|
|
12
12
|
/** A cluster node, read for the facts that decide placement and platform (ADR-0045/0052). */
|
|
13
13
|
export type NodeObject = {
|
|
14
14
|
metadata: { name: string; labels?: Record<string, string> };
|
|
15
15
|
spec?: { unschedulable?: boolean; taints?: readonly Taint[] };
|
|
16
|
-
status?: { nodeInfo?: { architecture?: string } };
|
|
16
|
+
status?: { nodeInfo?: { architecture?: string }; allocatable?: Record<string, string> };
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
export type Taint = { key: string; value?: string; effect: string };
|
|
@@ -72,3 +72,28 @@ export function tolerates(t: Toleration, taint: Taint): boolean {
|
|
|
72
72
|
if (t.operator === "Exists") return true;
|
|
73
73
|
return (t.value ?? "") === (taint.value ?? "");
|
|
74
74
|
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Whether any of `nodes` could hold `size` (ADR-0060): its allocatable memory and cpu both at least
|
|
78
|
+
* the whole Size — what the scheduler compares a Guaranteed pod against on an empty node. `undefined`
|
|
79
|
+
* when no node reports allocatable, which is an unknown, never a no. A WARNING at `jr2 up`, never a
|
|
80
|
+
* refusal: the node set moves (ADR-0052), and a pool may scale up a bigger node tomorrow.
|
|
81
|
+
*/
|
|
82
|
+
export function anyNodeHolds(nodes: readonly NodeObject[], size: ResolvedSize): boolean | undefined {
|
|
83
|
+
let known = false;
|
|
84
|
+
for (const node of nodes) {
|
|
85
|
+
const alloc = node.status?.allocatable;
|
|
86
|
+
if (alloc?.memory === undefined || alloc.cpu === undefined) continue;
|
|
87
|
+
let memory: number;
|
|
88
|
+
let cpu: number;
|
|
89
|
+
try {
|
|
90
|
+
memory = memoryBytes(alloc.memory);
|
|
91
|
+
cpu = cpuMillis(alloc.cpu);
|
|
92
|
+
} catch {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
known = true;
|
|
96
|
+
if (memory >= size.memory.bytes && cpu >= size.cpu.millis) return true;
|
|
97
|
+
}
|
|
98
|
+
return known ? false : undefined;
|
|
99
|
+
}
|
package/src/sweep.ts
CHANGED
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
// is not this instance's garbage):
|
|
8
8
|
// 1. the `jr2-images` ConfigMap of every jr2 instance (namespaces labeled `jr2.dev/instance`) — what
|
|
9
9
|
// FUTURE Sandboxes will be provisioned with;
|
|
10
|
-
// 2. every Sandbox CR's `spec.image
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
10
|
+
// 2. every Sandbox CR's `spec.image`, sidecar, init container and `image` volume in those
|
|
11
|
+
// namespaces — a parked Workspace must survive a pod restart, and `imagePullPolicy:
|
|
12
|
+
// IfNotPresent` cannot re-pull a local tag;
|
|
13
|
+
// 3. every pod's container and `image` volume refs in those namespaces plus `jr2-system` — what is
|
|
14
|
+
// actually running (orchestrator, Instance Harness, Custodians, Sandboxes, operator), mid-roll
|
|
15
|
+
// pods INCLUDED, without naming Deployments one by one.
|
|
15
16
|
// The keep set is their union. "Kit images are never pruned" is not a rule here: a kit ref is kept
|
|
16
17
|
// because some instance's map or pod names it, and collects like anything else when the last
|
|
17
18
|
// instance leaves the cluster.
|
|
@@ -53,13 +54,18 @@ export type Roots = { keep: Set<string>; namespaces: string[] };
|
|
|
53
54
|
|
|
54
55
|
type NamespacedObject = { metadata: { name: string; namespace?: string } };
|
|
55
56
|
type ConfigMapObject = NamespacedObject & { data?: Record<string, string> };
|
|
56
|
-
type SandboxObject = NamespacedObject & {
|
|
57
|
+
type SandboxObject = NamespacedObject & {
|
|
58
|
+
spec?: { image?: string; sidecars?: ContainerSpec[]; initContainers?: ContainerSpec[]; volumes?: VolumeSpec[] };
|
|
59
|
+
};
|
|
57
60
|
type ContainerSpec = { image?: string };
|
|
61
|
+
/** Only an `image` volume names a ref: every Sandbox's `/opt/jr2` is one, of the harness image (ADR-0037). */
|
|
62
|
+
type VolumeSpec = { image?: { reference?: string } };
|
|
58
63
|
type PodObject = NamespacedObject & {
|
|
59
64
|
spec?: {
|
|
60
65
|
containers?: ContainerSpec[];
|
|
61
66
|
initContainers?: ContainerSpec[];
|
|
62
67
|
ephemeralContainers?: ContainerSpec[];
|
|
68
|
+
volumes?: VolumeSpec[];
|
|
63
69
|
};
|
|
64
70
|
};
|
|
65
71
|
|
|
@@ -93,18 +99,20 @@ export async function readRoots(kube: KubeAdmin, ctx: { context?: string } = {})
|
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
// 2. what a parked Workspace's pod will be recreated with — the Sandbox Image AND every sidecar's
|
|
96
|
-
// ref. The CR always carries the
|
|
102
|
+
// ref. The CR always carries the Custodian as a sidecar (sandbox-kube.ts), and its ref is not
|
|
97
103
|
// covered by the other roots: a running Sandbox is deliberately never re-imaged, so an `up` that
|
|
98
|
-
//
|
|
104
|
+
// moved the Custodian's pin leaves the CR naming the OLD one while the map names the new. If that pod
|
|
99
105
|
// is then lost (node restart, eviction, drain), the recreated one pulls the CR's sidecar ref —
|
|
100
|
-
// and `IfNotPresent` cannot re-pull a local tag a sweep took.
|
|
106
|
+
// and `IfNotPresent` cannot re-pull a local tag a sweep took. The same holds for the harness ref: it
|
|
107
|
+
// reaches the pod only as the `runtime` image volume (ADR-0037), never as a container's image.
|
|
101
108
|
const sandboxes = await listSandboxes(kube, ctx);
|
|
102
109
|
for (const sandbox of sandboxes) {
|
|
103
110
|
if (!instanceNs.has(sandbox.metadata.namespace ?? "")) continue;
|
|
104
111
|
if (sandbox.spec?.image) keep.add(sandbox.spec.image);
|
|
105
|
-
for (const
|
|
106
|
-
if (
|
|
112
|
+
for (const c of [...(sandbox.spec?.sidecars ?? []), ...(sandbox.spec?.initContainers ?? [])]) {
|
|
113
|
+
if (c.image) keep.add(c.image);
|
|
107
114
|
}
|
|
115
|
+
for (const ref of volumeRefs(sandbox.spec?.volumes)) keep.add(ref);
|
|
108
116
|
}
|
|
109
117
|
|
|
110
118
|
// 3. what is running right now — plus the operator, which lives in the shared `jr2-system`.
|
|
@@ -116,11 +124,17 @@ export async function readRoots(kube: KubeAdmin, ctx: { context?: string } = {})
|
|
|
116
124
|
for (const c of [...(spec.containers ?? []), ...(spec.initContainers ?? []), ...(spec.ephemeralContainers ?? [])]) {
|
|
117
125
|
if (c.image) keep.add(c.image);
|
|
118
126
|
}
|
|
127
|
+
for (const ref of volumeRefs(spec.volumes)) keep.add(ref);
|
|
119
128
|
}
|
|
120
129
|
|
|
121
130
|
return { keep, namespaces };
|
|
122
131
|
}
|
|
123
132
|
|
|
133
|
+
/** The refs a volume list mounts as `image` volumes — invisible to a read of containers alone. */
|
|
134
|
+
function volumeRefs(volumes: VolumeSpec[] | undefined): string[] {
|
|
135
|
+
return (volumes ?? []).flatMap((v) => (v.image?.reference ? [v.image.reference] : []));
|
|
136
|
+
}
|
|
137
|
+
|
|
124
138
|
/**
|
|
125
139
|
* Root #2's read, with the ONE degradation the fail-closed rule allows: a cluster that has no
|
|
126
140
|
* `sandboxes.core.jr2.dev` resource type can hold no Sandbox CRs, so "no Sandboxes" is the complete
|
|
@@ -141,8 +155,8 @@ async function listSandboxes(kube: KubeAdmin, ctx: { context?: string }): Promis
|
|
|
141
155
|
}
|
|
142
156
|
}
|
|
143
157
|
|
|
144
|
-
/** Every ref one instance's image map names: the kit's own at the top level (`harness`,
|
|
145
|
-
* and the `operator` that rides the same JSON), and each Sandbox Image under `sandbox`. An
|
|
158
|
+
/** Every ref one instance's image map names: the kit's own at the top level (`harness`,
|
|
159
|
+
* `custodian`, and the `operator` that rides the same JSON), and each Sandbox Image under `sandbox`. An
|
|
146
160
|
* unreadable map is a FAILED root, not an empty one — a hand-edited ConfigMap must not be read as
|
|
147
161
|
* "that instance needs nothing". */
|
|
148
162
|
function imageMapRefs(cm: ConfigMapObject): string[] {
|