@namzu/sandbox 12.0.1 → 14.0.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/CHANGELOG.md +393 -0
- package/README.md +151 -0
- package/dist/backends/firecracker/protocol.d.ts +22 -0
- package/dist/backends/firecracker/protocol.d.ts.map +1 -1
- package/dist/backends/firecracker/protocol.js.map +1 -1
- package/dist/backends/firecracker/transport.d.ts +104 -9
- package/dist/backends/firecracker/transport.d.ts.map +1 -1
- package/dist/backends/firecracker/transport.js +139 -13
- package/dist/backends/firecracker/transport.js.map +1 -1
- package/dist/backends/kubernetes/egress-policy.d.ts +219 -0
- package/dist/backends/kubernetes/egress-policy.d.ts.map +1 -0
- package/dist/backends/kubernetes/egress-policy.js +314 -0
- package/dist/backends/kubernetes/egress-policy.js.map +1 -0
- package/dist/backends/kubernetes/index.d.ts +374 -0
- package/dist/backends/kubernetes/index.d.ts.map +1 -0
- package/dist/backends/kubernetes/index.js +671 -0
- package/dist/backends/kubernetes/index.js.map +1 -0
- package/dist/backends/kubernetes/k8s-client.d.ts +125 -0
- package/dist/backends/kubernetes/k8s-client.d.ts.map +1 -0
- package/dist/backends/kubernetes/k8s-client.js +246 -0
- package/dist/backends/kubernetes/k8s-client.js.map +1 -0
- package/dist/backends/kubernetes/lease.d.ts +119 -0
- package/dist/backends/kubernetes/lease.d.ts.map +1 -0
- package/dist/backends/kubernetes/lease.js +151 -0
- package/dist/backends/kubernetes/lease.js.map +1 -0
- package/dist/backends/kubernetes/objects.d.ts +282 -0
- package/dist/backends/kubernetes/objects.d.ts.map +1 -0
- package/dist/backends/kubernetes/objects.js +156 -0
- package/dist/backends/kubernetes/objects.js.map +1 -0
- package/dist/backends/kubernetes/privilege-probe.d.ts +136 -0
- package/dist/backends/kubernetes/privilege-probe.d.ts.map +1 -0
- package/dist/backends/kubernetes/privilege-probe.js +185 -0
- package/dist/backends/kubernetes/privilege-probe.js.map +1 -0
- package/dist/backends/kubernetes/sandbox.d.ts +123 -0
- package/dist/backends/kubernetes/sandbox.d.ts.map +1 -0
- package/dist/backends/kubernetes/sandbox.js +299 -0
- package/dist/backends/kubernetes/sandbox.js.map +1 -0
- package/dist/backends/kubernetes/transport.d.ts +122 -0
- package/dist/backends/kubernetes/transport.d.ts.map +1 -0
- package/dist/backends/kubernetes/transport.js +197 -0
- package/dist/backends/kubernetes/transport.js.map +1 -0
- package/dist/backends/kubernetes/workspace.d.ts +381 -0
- package/dist/backends/kubernetes/workspace.d.ts.map +1 -0
- package/dist/backends/kubernetes/workspace.js +1064 -0
- package/dist/backends/kubernetes/workspace.js.map +1 -0
- package/dist/index.d.ts +132 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +102 -34
- package/dist/index.js.map +1 -1
- package/dist/testing/sandbox-conformance.d.ts +193 -0
- package/dist/testing/sandbox-conformance.d.ts.map +1 -0
- package/dist/testing/sandbox-conformance.js +465 -0
- package/dist/testing/sandbox-conformance.js.map +1 -0
- package/package.json +5 -4
- package/src/backends/firecracker/protocol.ts +27 -0
- package/src/backends/firecracker/transport.ts +199 -28
- package/src/backends/kubernetes/egress-policy.ts +437 -0
- package/src/backends/kubernetes/index.ts +1012 -0
- package/src/backends/kubernetes/k8s-client.ts +352 -0
- package/src/backends/kubernetes/lease.ts +198 -0
- package/src/backends/kubernetes/objects.ts +363 -0
- package/src/backends/kubernetes/privilege-probe.ts +261 -0
- package/src/backends/kubernetes/sandbox.ts +395 -0
- package/src/backends/kubernetes/transport.ts +286 -0
- package/src/backends/kubernetes/workspace.ts +1386 -0
- package/src/index.ts +257 -35
- package/src/testing/sandbox-conformance.ts +667 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lease renewal for an acquired kubernetes sandbox.
|
|
3
|
+
*
|
|
4
|
+
* Acquire stamps an ABSOLUTE `shutdownTime` (now + `claimTtlSeconds`) plus
|
|
5
|
+
* `shutdownPolicy: Delete` onto every object it creates. That is the leak
|
|
6
|
+
* guard: a host that dies mid-run costs the cluster one expiry rather than a
|
|
7
|
+
* sandbox that lives forever. It is also, unrenewed, a deadline on the RUN —
|
|
8
|
+
* a session that outlives the TTL has its pod deleted underneath it, mid
|
|
9
|
+
* command, with no error the host can attribute to anything.
|
|
10
|
+
*
|
|
11
|
+
* The two requirements are not in tension, they just need a second half:
|
|
12
|
+
* a live handle renews its own lease, and a dead host stops renewing. So
|
|
13
|
+
* this timer is owned by the Sandbox handle, and `destroy()` stops it.
|
|
14
|
+
*
|
|
15
|
+
* ## The shape of the timer
|
|
16
|
+
*
|
|
17
|
+
* One `setTimeout` chained per tick, never `setInterval`: a renewal PATCH
|
|
18
|
+
* that takes longer than the interval must not queue a second one behind
|
|
19
|
+
* it. The interval is HALF the TTL, so a single failed tick still leaves a
|
|
20
|
+
* whole half-TTL of headroom for the next one to succeed, and it is
|
|
21
|
+
* jittered ±10% so a hundred handles acquired in the same second do not
|
|
22
|
+
* PATCH the API server in the same millisecond forever after.
|
|
23
|
+
*
|
|
24
|
+
* The timer is `unref`'d: a host process that has finished its work should
|
|
25
|
+
* exit, not linger because a sandbox handle is still counting. A handle
|
|
26
|
+
* nobody destroyed then expires on the cluster's clock exactly as an
|
|
27
|
+
* abandoned one does, which is the behaviour the TTL exists for.
|
|
28
|
+
*
|
|
29
|
+
* ## Every tick is bounded
|
|
30
|
+
*
|
|
31
|
+
* A renewal that FAILS is survivable — it is reported and retried with half
|
|
32
|
+
* a TTL of headroom. A renewal that HANGS is not: the next tick is scheduled
|
|
33
|
+
* only after the current one settles, so a PATCH that never answers parks
|
|
34
|
+
* the loop forever, reports nothing, and lets the lease expire in silence —
|
|
35
|
+
* precisely the defect this file exists to close, moved onto the failure
|
|
36
|
+
* path. An API server that accepts a connection and then never responds is
|
|
37
|
+
* an ordinary cluster event, so each PATCH runs under its own deadline: it
|
|
38
|
+
* aborts the request through the signal the client already takes, and an
|
|
39
|
+
* expiry is then just another reported failure that retries on the next
|
|
40
|
+
* tick.
|
|
41
|
+
*
|
|
42
|
+
* ## What each outcome means
|
|
43
|
+
*
|
|
44
|
+
* - Success → the object's expiry moves a full TTL into the future.
|
|
45
|
+
* - Any error, a tick that ran out of time included → reported to
|
|
46
|
+
* `onRenewalError` and RETRIED on the next tick. A transient API blip
|
|
47
|
+
* must not tear down a working sandbox, and there is still half a TTL of
|
|
48
|
+
* headroom.
|
|
49
|
+
* - Already gone (404/410) → the object this handle owns no longer exists.
|
|
50
|
+
* Nothing will bring it back, so the loop stops and the handle is marked
|
|
51
|
+
* gone; every later call fails with a named error instead of dialing an
|
|
52
|
+
* address whose pod the controller has already deleted.
|
|
53
|
+
*/
|
|
54
|
+
import { OperationDeadline } from '../readiness.js';
|
|
55
|
+
import { KubernetesAlreadyGoneError } from './k8s-client.js';
|
|
56
|
+
/**
|
|
57
|
+
* The ceiling on one renewal PATCH. A write to the API server that has not
|
|
58
|
+
* answered in half a minute is not going to; at the one-hour default TTL the
|
|
59
|
+
* derived quarter-interval would otherwise be 7.5 minutes of silence.
|
|
60
|
+
*/
|
|
61
|
+
const MAX_RENEWAL_TIMEOUT_MS = 30_000;
|
|
62
|
+
/** ±10%: enough to spread a synchronised fleet, far too little to matter
|
|
63
|
+
* against a half-TTL of headroom. */
|
|
64
|
+
const JITTER_FRACTION = 0.1;
|
|
65
|
+
export function jitteredInterval(baseMs, random) {
|
|
66
|
+
const factor = 1 - JITTER_FRACTION + random() * (2 * JITTER_FRACTION);
|
|
67
|
+
// Never zero, whatever a caller passes: a zero-delay chain would spin.
|
|
68
|
+
return Math.max(1, Math.round(baseMs * factor));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The renewal loop. Start it when the handle is handed out, stop it on
|
|
72
|
+
* `destroy()`. Both are idempotent.
|
|
73
|
+
*/
|
|
74
|
+
export class KubernetesLeaseRenewal {
|
|
75
|
+
options;
|
|
76
|
+
timer;
|
|
77
|
+
stopped = false;
|
|
78
|
+
started = false;
|
|
79
|
+
baseIntervalMs;
|
|
80
|
+
patchTimeoutMs;
|
|
81
|
+
random;
|
|
82
|
+
constructor(options) {
|
|
83
|
+
this.options = options;
|
|
84
|
+
this.baseIntervalMs = options.intervalMs ?? Math.max(1, (options.ttlSeconds * 1_000) / 2);
|
|
85
|
+
this.patchTimeoutMs =
|
|
86
|
+
options.patchTimeoutMs ??
|
|
87
|
+
Math.max(1, Math.min(MAX_RENEWAL_TIMEOUT_MS, Math.round(this.baseIntervalMs / 4)));
|
|
88
|
+
this.random = options.random ?? Math.random;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The loop is alive: it has been started and not stopped. Deliberately
|
|
92
|
+
* NOT "a timer is pending" — `tick()` clears the timer before it awaits,
|
|
93
|
+
* so a liveness check written that way reads false for the whole duration
|
|
94
|
+
* of an in-flight renewal and would quietly pass against a loop that had
|
|
95
|
+
* parked forever inside one.
|
|
96
|
+
*/
|
|
97
|
+
get active() {
|
|
98
|
+
return !this.stopped && this.started;
|
|
99
|
+
}
|
|
100
|
+
start() {
|
|
101
|
+
if (this.stopped || this.timer !== undefined)
|
|
102
|
+
return;
|
|
103
|
+
this.started = true;
|
|
104
|
+
this.schedule();
|
|
105
|
+
}
|
|
106
|
+
stop() {
|
|
107
|
+
this.stopped = true;
|
|
108
|
+
if (this.timer !== undefined) {
|
|
109
|
+
clearTimeout(this.timer);
|
|
110
|
+
this.timer = undefined;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
schedule() {
|
|
114
|
+
if (this.stopped)
|
|
115
|
+
return;
|
|
116
|
+
const timer = setTimeout(() => {
|
|
117
|
+
void this.tick();
|
|
118
|
+
}, jitteredInterval(this.baseIntervalMs, this.random));
|
|
119
|
+
// A pending renewal must never be the reason a host process stays up.
|
|
120
|
+
timer.unref?.();
|
|
121
|
+
this.timer = timer;
|
|
122
|
+
}
|
|
123
|
+
/** Exposed for tests: one renewal attempt plus its scheduling decision. */
|
|
124
|
+
async tick() {
|
|
125
|
+
this.timer = undefined;
|
|
126
|
+
if (this.stopped)
|
|
127
|
+
return;
|
|
128
|
+
const shutdownTime = new Date(Date.now() + this.options.ttlSeconds * 1_000).toISOString();
|
|
129
|
+
try {
|
|
130
|
+
// On its own clock: the next tick is scheduled only once this one
|
|
131
|
+
// settles, so an unbounded PATCH that never answers would park the
|
|
132
|
+
// loop permanently and let the lease expire with nothing reported.
|
|
133
|
+
// The deadline aborts the request and hands the expiry to the same
|
|
134
|
+
// report-and-retry path every other failure takes.
|
|
135
|
+
await new OperationDeadline(this.patchTimeoutMs, 'kubernetes lease renewal').run(async (signal) => await this.options.renew(shutdownTime, signal));
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
if (error instanceof KubernetesAlreadyGoneError) {
|
|
139
|
+
this.stop();
|
|
140
|
+
this.options.onGone();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Everything else is transient until proven otherwise: report it
|
|
144
|
+
// and try again on the next tick, which is still half a TTL
|
|
145
|
+
// before anything expires.
|
|
146
|
+
this.options.onRenewalError?.(error);
|
|
147
|
+
}
|
|
148
|
+
this.schedule();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=lease.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lease.js","sourceRoot":"","sources":["../../../src/backends/kubernetes/lease.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire shapes and paths for the agent-sandbox CRDs this backend touches.
|
|
3
|
+
*
|
|
4
|
+
* Every field name here was read off the CRDs a real cluster serves —
|
|
5
|
+
* `kubectl get crd -o json` against agent-sandbox v1.0.2 on a kind cluster —
|
|
6
|
+
* and cross-checked against the upstream `sandbox_types.go` /
|
|
7
|
+
* `sandboxclaim_types.go` doc comments. Where the two disagree the served CRD
|
|
8
|
+
* wins, because it is what the API server validates against: the Go source on
|
|
9
|
+
* upstream main has already moved `Sandbox`'s `shutdownTime` /
|
|
10
|
+
* `shutdownPolicy` under a `lifecycle` block, and v1beta1 as served still
|
|
11
|
+
* carries them at the top of `spec`.
|
|
12
|
+
*
|
|
13
|
+
* The shapes are deliberately partial. This backend reads four fields out of a
|
|
14
|
+
* Sandbox status and writes three into a claim spec; typing the rest of a
|
|
15
|
+
* PodSpec would be a vendored copy of the core API that goes stale on its own
|
|
16
|
+
* schedule. A pod template read from a SandboxTemplate is carried through as
|
|
17
|
+
* an opaque record for exactly that reason — it is copied, never interpreted.
|
|
18
|
+
*
|
|
19
|
+
* Two groups, both `v1beta1` and both singular-versioned today:
|
|
20
|
+
* - `agents.x-k8s.io` → sandboxes
|
|
21
|
+
* - `extensions.agents.x-k8s.io` → sandboxtemplates, sandboxwarmpools,
|
|
22
|
+
* sandboxclaims
|
|
23
|
+
*/
|
|
24
|
+
/** Group serving the `Sandbox` kind. */
|
|
25
|
+
export declare const SANDBOX_API_GROUP = "agents.x-k8s.io";
|
|
26
|
+
/** Group serving `SandboxTemplate`, `SandboxWarmPool` and `SandboxClaim`. */
|
|
27
|
+
export declare const SANDBOX_EXTENSIONS_API_GROUP = "extensions.agents.x-k8s.io";
|
|
28
|
+
/** The only version either group serves in agent-sandbox v1.0.2. */
|
|
29
|
+
export declare const SANDBOX_API_VERSION = "v1beta1";
|
|
30
|
+
/** `status.conditions[].type` both kinds report readiness under. */
|
|
31
|
+
export declare const READY_CONDITION = "Ready";
|
|
32
|
+
export interface KubernetesObjectMeta {
|
|
33
|
+
readonly name?: string;
|
|
34
|
+
readonly namespace?: string;
|
|
35
|
+
readonly uid?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Set the moment a DELETE is accepted, long before the object goes away.
|
|
38
|
+
* A pod that carries one is on its way out and must never be bound to —
|
|
39
|
+
* see {@link isPodLive}.
|
|
40
|
+
*/
|
|
41
|
+
readonly deletionTimestamp?: string;
|
|
42
|
+
readonly labels?: Readonly<Record<string, string>>;
|
|
43
|
+
readonly annotations?: Readonly<Record<string, string>>;
|
|
44
|
+
}
|
|
45
|
+
/** `metav1.Condition`, as both CRDs embed it. */
|
|
46
|
+
export interface KubernetesCondition {
|
|
47
|
+
readonly type: string;
|
|
48
|
+
readonly status: 'True' | 'False' | 'Unknown';
|
|
49
|
+
readonly reason?: string;
|
|
50
|
+
readonly message?: string;
|
|
51
|
+
readonly lastTransitionTime?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* True only for an explicit `status: 'True'`. An absent condition, an
|
|
55
|
+
* `Unknown` and a `False` are all "not yet", never "assume so" — the
|
|
56
|
+
* controller writes `Unknown` while it is still deciding.
|
|
57
|
+
*/
|
|
58
|
+
export declare function isConditionTrue(conditions: readonly KubernetesCondition[] | undefined, type: string): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* `SandboxClaim.spec.lifecycle`.
|
|
61
|
+
*
|
|
62
|
+
* `shutdownTime` is the only one of the three that bounds a claim whose owner
|
|
63
|
+
* disappeared: the controller deletes the claim's resources once the wall
|
|
64
|
+
* clock reaches it, whatever the claim is doing. `ttlSecondsAfterFinished`
|
|
65
|
+
* reads like the leak guard and is not one — upstream's own comment says "the
|
|
66
|
+
* timer starts from the mirrored Finished condition's LastTransitionTime", so
|
|
67
|
+
* a claim whose host crashed before finishing never starts that clock.
|
|
68
|
+
*/
|
|
69
|
+
export interface SandboxClaimLifecycle {
|
|
70
|
+
/** RFC 3339. Absolute expiry; the claim never expires without it. */
|
|
71
|
+
readonly shutdownTime?: string;
|
|
72
|
+
/**
|
|
73
|
+
* What happens to the claim OBJECT at expiry. `Retain` (the CRD default)
|
|
74
|
+
* deletes the Sandbox, Pod and Service but leaves the claim behind, so a
|
|
75
|
+
* host that crashes daily accumulates claims forever.
|
|
76
|
+
*/
|
|
77
|
+
readonly shutdownPolicy?: 'Delete' | 'DeleteForeground' | 'Retain';
|
|
78
|
+
readonly ttlSecondsAfterFinished?: number;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* `SandboxClaim.spec`. `warmPoolRef` is REQUIRED by the CRD, which is why
|
|
82
|
+
* there is no such thing as a pool-less claim and the no-pool path has to
|
|
83
|
+
* create a Sandbox directly.
|
|
84
|
+
*
|
|
85
|
+
* `env` and `volumeClaimTemplates` exist on this spec and are deliberately
|
|
86
|
+
* absent from this type: setting either forces the claim to cold-start rather
|
|
87
|
+
* than adopt a warm pool sandbox, which is the one thing the warm path exists
|
|
88
|
+
* to avoid. A field that cannot be named cannot be set by accident.
|
|
89
|
+
*/
|
|
90
|
+
export interface SandboxClaimResourceSpec {
|
|
91
|
+
readonly warmPoolRef: {
|
|
92
|
+
readonly name: string;
|
|
93
|
+
};
|
|
94
|
+
readonly lifecycle?: SandboxClaimLifecycle;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* `SandboxClaim.status`. `sandbox` is the whole reason the claim path reads
|
|
98
|
+
* status back: an adopted pool sandbox keeps the generated name the pool gave
|
|
99
|
+
* it, so the bound object is routinely NOT named after the claim.
|
|
100
|
+
*/
|
|
101
|
+
export interface SandboxClaimResourceStatus {
|
|
102
|
+
readonly conditions?: readonly KubernetesCondition[];
|
|
103
|
+
readonly sandbox?: {
|
|
104
|
+
readonly name?: string;
|
|
105
|
+
readonly podIPs?: readonly string[];
|
|
106
|
+
readonly serviceFQDN?: string;
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
export interface SandboxClaimResource {
|
|
110
|
+
readonly apiVersion?: string;
|
|
111
|
+
readonly kind?: string;
|
|
112
|
+
readonly metadata?: KubernetesObjectMeta;
|
|
113
|
+
readonly spec?: SandboxClaimResourceSpec;
|
|
114
|
+
readonly status?: SandboxClaimResourceStatus;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* `podTemplate` on a Sandbox or a SandboxTemplate. `spec` is a core `PodSpec`,
|
|
118
|
+
* carried opaquely: this backend copies one from a template into a Sandbox and
|
|
119
|
+
* overlays at most `runtimeClassName`.
|
|
120
|
+
*/
|
|
121
|
+
export interface SandboxPodTemplate {
|
|
122
|
+
readonly metadata?: KubernetesObjectMeta;
|
|
123
|
+
readonly spec: Readonly<Record<string, unknown>>;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* One `spec.volumeClaimTemplates` entry.
|
|
127
|
+
*
|
|
128
|
+
* Partial in the same way every other shape here is: a workspace's disk is
|
|
129
|
+
* COPIED verbatim from the `SandboxTemplate` that declares it, and only the
|
|
130
|
+
* two fields this backend has to reason about are named — the entry's own
|
|
131
|
+
* `metadata.name`, which is how the controller wires the mount (StatefulSet
|
|
132
|
+
* style: the PVC is created as `<entry name>-<sandbox name>` and no explicit
|
|
133
|
+
* `volumes:` entry is needed in the podTemplate), and `spec.volumeMode`,
|
|
134
|
+
* which decides whether the guest gets a raw block device or a filesystem
|
|
135
|
+
* passthrough. The index signatures carry everything else across untouched.
|
|
136
|
+
*/
|
|
137
|
+
export interface SandboxVolumeClaimTemplate {
|
|
138
|
+
readonly metadata?: KubernetesObjectMeta;
|
|
139
|
+
readonly spec?: {
|
|
140
|
+
readonly volumeMode?: string;
|
|
141
|
+
readonly [field: string]: unknown;
|
|
142
|
+
};
|
|
143
|
+
readonly [field: string]: unknown;
|
|
144
|
+
}
|
|
145
|
+
export interface SandboxResourceSpec {
|
|
146
|
+
readonly operatingMode?: 'Running' | 'Suspended';
|
|
147
|
+
readonly podTemplate: SandboxPodTemplate;
|
|
148
|
+
/** Create a headless Service, and with it a `status.serviceFQDN`. */
|
|
149
|
+
readonly service?: boolean;
|
|
150
|
+
readonly shutdownPolicy?: 'Delete' | 'Retain';
|
|
151
|
+
/** RFC 3339, top-level on v1beta1 Sandbox (NOT under `lifecycle`). */
|
|
152
|
+
readonly shutdownTime?: string;
|
|
153
|
+
/**
|
|
154
|
+
* CEL-immutable on the served CRD ("volumeClaimTemplates is immutable"),
|
|
155
|
+
* which is why a workspace's disk has to be in the spec from creation and
|
|
156
|
+
* cannot be attached to a sandbox that is already running.
|
|
157
|
+
*/
|
|
158
|
+
readonly volumeClaimTemplates?: readonly SandboxVolumeClaimTemplate[];
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* `Sandbox.status`. Note what is NOT here: a pod name. The backing pod is
|
|
162
|
+
* named after the Sandbox itself in v1.0.2, and `selector` — a serialised
|
|
163
|
+
* label selector, e.g. `agents.x-k8s.io/sandbox-name-hash=<hash>` — is the
|
|
164
|
+
* only thing in the API that finds the pod without relying on that.
|
|
165
|
+
*/
|
|
166
|
+
export interface SandboxResourceStatus {
|
|
167
|
+
readonly conditions?: readonly KubernetesCondition[];
|
|
168
|
+
readonly nodeName?: string;
|
|
169
|
+
readonly podIPs?: readonly string[];
|
|
170
|
+
readonly selector?: string;
|
|
171
|
+
readonly service?: string;
|
|
172
|
+
readonly serviceFQDN?: string;
|
|
173
|
+
}
|
|
174
|
+
export interface SandboxResource {
|
|
175
|
+
readonly apiVersion?: string;
|
|
176
|
+
readonly kind?: string;
|
|
177
|
+
readonly metadata?: KubernetesObjectMeta;
|
|
178
|
+
readonly spec?: SandboxResourceSpec;
|
|
179
|
+
readonly status?: SandboxResourceStatus;
|
|
180
|
+
}
|
|
181
|
+
export interface SandboxTemplateResource {
|
|
182
|
+
readonly metadata?: KubernetesObjectMeta;
|
|
183
|
+
readonly spec?: {
|
|
184
|
+
readonly podTemplate?: SandboxPodTemplate;
|
|
185
|
+
readonly service?: boolean;
|
|
186
|
+
readonly volumeClaimTemplates?: readonly SandboxVolumeClaimTemplate[];
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* `metadata.uid` is the per-instance agent bind token; the other two fields
|
|
191
|
+
* exist only to answer "is this the pod that uid belongs to, or the one being
|
|
192
|
+
* deleted?" — see {@link isPodLive}.
|
|
193
|
+
*/
|
|
194
|
+
export interface PodResource {
|
|
195
|
+
readonly metadata?: KubernetesObjectMeta;
|
|
196
|
+
readonly status?: {
|
|
197
|
+
readonly phase?: string;
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
export interface PodListResource {
|
|
201
|
+
readonly items?: readonly PodResource[];
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* A pod whose uid is still worth binding to: not being deleted, and not in a
|
|
205
|
+
* phase it cannot leave.
|
|
206
|
+
*
|
|
207
|
+
* The case this exists for is resume. A resumed sandbox's pod keeps the
|
|
208
|
+
* SAME NAME and gets a new uid and a new IP, so while the outgoing pod is
|
|
209
|
+
* terminating a `GET` by that name answers with the pod on its way out, and a
|
|
210
|
+
* list by the sandbox's selector returns every pod still carrying its labels,
|
|
211
|
+
* that one included. Binding to the terminating pod's uid produces a token the
|
|
212
|
+
* new agent refuses, and the failure arrives as a flat `unauthorized` with
|
|
213
|
+
* nothing pointing at the race that caused it.
|
|
214
|
+
*/
|
|
215
|
+
export declare function isPodLive(pod: PodResource | undefined): boolean;
|
|
216
|
+
/**
|
|
217
|
+
* A pod whose containers have stopped: it is still an object, and nothing in
|
|
218
|
+
* it is executing any more.
|
|
219
|
+
*
|
|
220
|
+
* NOT the negation of {@link isPodLive}, and the gap between the two is the
|
|
221
|
+
* whole point. A terminating pod — `deletionTimestamp` set, phase still
|
|
222
|
+
* `Running` — is not live (never bind to it: its uid is about to stop being
|
|
223
|
+
* a valid token) and not stopped either (its process is still running, and on
|
|
224
|
+
* a workspace it is still writing to the caller's block device until it exits
|
|
225
|
+
* or `terminationGracePeriodSeconds` runs out). A suspend that treated the
|
|
226
|
+
* timestamp as "gone" would resolve mid-drain and promise a quiesced disk it
|
|
227
|
+
* had not waited for.
|
|
228
|
+
*/
|
|
229
|
+
export declare function isPodStopped(pod: PodResource | undefined): boolean;
|
|
230
|
+
/**
|
|
231
|
+
* Backend-owned pod label naming which `SandboxTemplate` a Sandbox's pod was
|
|
232
|
+
* built from.
|
|
233
|
+
*
|
|
234
|
+
* agent-sandbox's OWN template-adoption controller selects pods by a
|
|
235
|
+
* controller-owned label, `agents.x-k8s.io/sandbox-template-ref-hash` — but
|
|
236
|
+
* that label is written only onto a Sandbox ADOPTED out of a
|
|
237
|
+
* `SandboxWarmPool` (the controller re-parents ownership and re-labels on
|
|
238
|
+
* bind). A Sandbox this backend POSTs directly (the pool-less path in
|
|
239
|
+
* `index.ts`'s `buildSandboxBody`) is never adopted, so it never gets that
|
|
240
|
+
* label — a direct Sandbox's pod would carry nothing a `NetworkPolicy`
|
|
241
|
+
* could reliably select it by. This backend writes its own label instead, on
|
|
242
|
+
* every Sandbox it creates, pooled or direct, so `egress-policy.ts`'s
|
|
243
|
+
* translated `NetworkPolicy` has one selector that always matches.
|
|
244
|
+
*
|
|
245
|
+
* `namzu.ai` matches the published domain (`packages/cli/package.json`'s
|
|
246
|
+
* `homepage`); there is no pre-existing Kubernetes label or annotation
|
|
247
|
+
* prefix anywhere in this repo to follow instead — the closest existing
|
|
248
|
+
* convention, `NAMZU_AGENT_*` / `NAMZU_SANDBOX_*` env vars, is not a
|
|
249
|
+
* label-safe shape.
|
|
250
|
+
*
|
|
251
|
+
* W8's `SandboxTemplate` manifests MUST set this same label (value = the
|
|
252
|
+
* template's own name) on their `podTemplate.metadata.labels`, so a POOLED
|
|
253
|
+
* sandbox's pod carries it too — the pool's pods are built from that
|
|
254
|
+
* template's `podTemplate` directly, not through `buildSandboxBody`, so
|
|
255
|
+
* nothing here can put it there for them. Skipping that step means a
|
|
256
|
+
* translated `NetworkPolicy`'s `podSelector` matches only sandboxes this
|
|
257
|
+
* backend created directly and none of the pooled ones — see
|
|
258
|
+
* `docs/sdk/kubernetes-sandbox.md`'s egress section.
|
|
259
|
+
*/
|
|
260
|
+
export declare const SANDBOX_TEMPLATE_LABEL_KEY = "sandbox.namzu.ai/template";
|
|
261
|
+
/** `{ [SANDBOX_TEMPLATE_LABEL_KEY]: sandboxTemplateName }`, as a matchLabels-ready object. */
|
|
262
|
+
export declare function sandboxTemplateLabel(sandboxTemplateName: string): Readonly<Record<string, string>>;
|
|
263
|
+
/** Core `NetworkPolicy` — a stock resource, no CRD. */
|
|
264
|
+
export declare const CORE_NETWORK_POLICY_API_GROUP = "networking.k8s.io";
|
|
265
|
+
export declare const CORE_NETWORK_POLICY_API_VERSION = "v1";
|
|
266
|
+
/**
|
|
267
|
+
* Cilium's FQDN-capable policy CRD. Only reached when
|
|
268
|
+
* `KubernetesEgressConfig.engine` is explicitly `'cilium'` — see
|
|
269
|
+
* `egress-policy.ts`.
|
|
270
|
+
*/
|
|
271
|
+
export declare const CILIUM_NETWORK_POLICY_API_GROUP = "cilium.io";
|
|
272
|
+
export declare const CILIUM_NETWORK_POLICY_API_VERSION = "v2";
|
|
273
|
+
export declare function claimCollectionPath(namespace: string): string;
|
|
274
|
+
export declare function claimPath(namespace: string, name: string): string;
|
|
275
|
+
export declare function sandboxCollectionPath(namespace: string): string;
|
|
276
|
+
export declare function sandboxPath(namespace: string, name: string): string;
|
|
277
|
+
export declare function sandboxTemplatePath(namespace: string, name: string): string;
|
|
278
|
+
export declare function podPath(namespace: string, name: string): string;
|
|
279
|
+
export declare function podListPath(namespace: string, labelSelector: string): string;
|
|
280
|
+
export declare function networkPolicyPath(namespace: string, name: string): string;
|
|
281
|
+
export declare function ciliumNetworkPolicyPath(namespace: string, name: string): string;
|
|
282
|
+
//# sourceMappingURL=objects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"objects.d.ts","sourceRoot":"","sources":["../../../src/backends/kubernetes/objects.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire shapes and paths for the agent-sandbox CRDs this backend touches.
|
|
3
|
+
*
|
|
4
|
+
* Every field name here was read off the CRDs a real cluster serves —
|
|
5
|
+
* `kubectl get crd -o json` against agent-sandbox v1.0.2 on a kind cluster —
|
|
6
|
+
* and cross-checked against the upstream `sandbox_types.go` /
|
|
7
|
+
* `sandboxclaim_types.go` doc comments. Where the two disagree the served CRD
|
|
8
|
+
* wins, because it is what the API server validates against: the Go source on
|
|
9
|
+
* upstream main has already moved `Sandbox`'s `shutdownTime` /
|
|
10
|
+
* `shutdownPolicy` under a `lifecycle` block, and v1beta1 as served still
|
|
11
|
+
* carries them at the top of `spec`.
|
|
12
|
+
*
|
|
13
|
+
* The shapes are deliberately partial. This backend reads four fields out of a
|
|
14
|
+
* Sandbox status and writes three into a claim spec; typing the rest of a
|
|
15
|
+
* PodSpec would be a vendored copy of the core API that goes stale on its own
|
|
16
|
+
* schedule. A pod template read from a SandboxTemplate is carried through as
|
|
17
|
+
* an opaque record for exactly that reason — it is copied, never interpreted.
|
|
18
|
+
*
|
|
19
|
+
* Two groups, both `v1beta1` and both singular-versioned today:
|
|
20
|
+
* - `agents.x-k8s.io` → sandboxes
|
|
21
|
+
* - `extensions.agents.x-k8s.io` → sandboxtemplates, sandboxwarmpools,
|
|
22
|
+
* sandboxclaims
|
|
23
|
+
*/
|
|
24
|
+
/** Group serving the `Sandbox` kind. */
|
|
25
|
+
export const SANDBOX_API_GROUP = 'agents.x-k8s.io';
|
|
26
|
+
/** Group serving `SandboxTemplate`, `SandboxWarmPool` and `SandboxClaim`. */
|
|
27
|
+
export const SANDBOX_EXTENSIONS_API_GROUP = 'extensions.agents.x-k8s.io';
|
|
28
|
+
/** The only version either group serves in agent-sandbox v1.0.2. */
|
|
29
|
+
export const SANDBOX_API_VERSION = 'v1beta1';
|
|
30
|
+
/** `status.conditions[].type` both kinds report readiness under. */
|
|
31
|
+
export const READY_CONDITION = 'Ready';
|
|
32
|
+
/**
|
|
33
|
+
* True only for an explicit `status: 'True'`. An absent condition, an
|
|
34
|
+
* `Unknown` and a `False` are all "not yet", never "assume so" — the
|
|
35
|
+
* controller writes `Unknown` while it is still deciding.
|
|
36
|
+
*/
|
|
37
|
+
export function isConditionTrue(conditions, type) {
|
|
38
|
+
return conditions?.some((c) => c.type === type && c.status === 'True') === true;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A pod whose uid is still worth binding to: not being deleted, and not in a
|
|
42
|
+
* phase it cannot leave.
|
|
43
|
+
*
|
|
44
|
+
* The case this exists for is resume. A resumed sandbox's pod keeps the
|
|
45
|
+
* SAME NAME and gets a new uid and a new IP, so while the outgoing pod is
|
|
46
|
+
* terminating a `GET` by that name answers with the pod on its way out, and a
|
|
47
|
+
* list by the sandbox's selector returns every pod still carrying its labels,
|
|
48
|
+
* that one included. Binding to the terminating pod's uid produces a token the
|
|
49
|
+
* new agent refuses, and the failure arrives as a flat `unauthorized` with
|
|
50
|
+
* nothing pointing at the race that caused it.
|
|
51
|
+
*/
|
|
52
|
+
export function isPodLive(pod) {
|
|
53
|
+
if (!pod?.metadata)
|
|
54
|
+
return false;
|
|
55
|
+
// `!= null`, not `!== undefined`: an explicit JSON null would otherwise
|
|
56
|
+
// read as "terminating" and make a perfectly healthy pod unbindable. The
|
|
57
|
+
// API server omits the field rather than nulling it, so this never fires
|
|
58
|
+
// against a real cluster — it costs nothing not to depend on that.
|
|
59
|
+
if (pod.metadata.deletionTimestamp != null)
|
|
60
|
+
return false;
|
|
61
|
+
const phase = pod.status?.phase;
|
|
62
|
+
return phase !== 'Succeeded' && phase !== 'Failed';
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* A pod whose containers have stopped: it is still an object, and nothing in
|
|
66
|
+
* it is executing any more.
|
|
67
|
+
*
|
|
68
|
+
* NOT the negation of {@link isPodLive}, and the gap between the two is the
|
|
69
|
+
* whole point. A terminating pod — `deletionTimestamp` set, phase still
|
|
70
|
+
* `Running` — is not live (never bind to it: its uid is about to stop being
|
|
71
|
+
* a valid token) and not stopped either (its process is still running, and on
|
|
72
|
+
* a workspace it is still writing to the caller's block device until it exits
|
|
73
|
+
* or `terminationGracePeriodSeconds` runs out). A suspend that treated the
|
|
74
|
+
* timestamp as "gone" would resolve mid-drain and promise a quiesced disk it
|
|
75
|
+
* had not waited for.
|
|
76
|
+
*/
|
|
77
|
+
export function isPodStopped(pod) {
|
|
78
|
+
const phase = pod?.status?.phase;
|
|
79
|
+
return phase === 'Succeeded' || phase === 'Failed';
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Backend-owned pod label naming which `SandboxTemplate` a Sandbox's pod was
|
|
83
|
+
* built from.
|
|
84
|
+
*
|
|
85
|
+
* agent-sandbox's OWN template-adoption controller selects pods by a
|
|
86
|
+
* controller-owned label, `agents.x-k8s.io/sandbox-template-ref-hash` — but
|
|
87
|
+
* that label is written only onto a Sandbox ADOPTED out of a
|
|
88
|
+
* `SandboxWarmPool` (the controller re-parents ownership and re-labels on
|
|
89
|
+
* bind). A Sandbox this backend POSTs directly (the pool-less path in
|
|
90
|
+
* `index.ts`'s `buildSandboxBody`) is never adopted, so it never gets that
|
|
91
|
+
* label — a direct Sandbox's pod would carry nothing a `NetworkPolicy`
|
|
92
|
+
* could reliably select it by. This backend writes its own label instead, on
|
|
93
|
+
* every Sandbox it creates, pooled or direct, so `egress-policy.ts`'s
|
|
94
|
+
* translated `NetworkPolicy` has one selector that always matches.
|
|
95
|
+
*
|
|
96
|
+
* `namzu.ai` matches the published domain (`packages/cli/package.json`'s
|
|
97
|
+
* `homepage`); there is no pre-existing Kubernetes label or annotation
|
|
98
|
+
* prefix anywhere in this repo to follow instead — the closest existing
|
|
99
|
+
* convention, `NAMZU_AGENT_*` / `NAMZU_SANDBOX_*` env vars, is not a
|
|
100
|
+
* label-safe shape.
|
|
101
|
+
*
|
|
102
|
+
* W8's `SandboxTemplate` manifests MUST set this same label (value = the
|
|
103
|
+
* template's own name) on their `podTemplate.metadata.labels`, so a POOLED
|
|
104
|
+
* sandbox's pod carries it too — the pool's pods are built from that
|
|
105
|
+
* template's `podTemplate` directly, not through `buildSandboxBody`, so
|
|
106
|
+
* nothing here can put it there for them. Skipping that step means a
|
|
107
|
+
* translated `NetworkPolicy`'s `podSelector` matches only sandboxes this
|
|
108
|
+
* backend created directly and none of the pooled ones — see
|
|
109
|
+
* `docs/sdk/kubernetes-sandbox.md`'s egress section.
|
|
110
|
+
*/
|
|
111
|
+
export const SANDBOX_TEMPLATE_LABEL_KEY = 'sandbox.namzu.ai/template';
|
|
112
|
+
/** `{ [SANDBOX_TEMPLATE_LABEL_KEY]: sandboxTemplateName }`, as a matchLabels-ready object. */
|
|
113
|
+
export function sandboxTemplateLabel(sandboxTemplateName) {
|
|
114
|
+
return { [SANDBOX_TEMPLATE_LABEL_KEY]: sandboxTemplateName };
|
|
115
|
+
}
|
|
116
|
+
/** Core `NetworkPolicy` — a stock resource, no CRD. */
|
|
117
|
+
export const CORE_NETWORK_POLICY_API_GROUP = 'networking.k8s.io';
|
|
118
|
+
export const CORE_NETWORK_POLICY_API_VERSION = 'v1';
|
|
119
|
+
/**
|
|
120
|
+
* Cilium's FQDN-capable policy CRD. Only reached when
|
|
121
|
+
* `KubernetesEgressConfig.engine` is explicitly `'cilium'` — see
|
|
122
|
+
* `egress-policy.ts`.
|
|
123
|
+
*/
|
|
124
|
+
export const CILIUM_NETWORK_POLICY_API_GROUP = 'cilium.io';
|
|
125
|
+
export const CILIUM_NETWORK_POLICY_API_VERSION = 'v2';
|
|
126
|
+
function segment(value) {
|
|
127
|
+
return encodeURIComponent(value);
|
|
128
|
+
}
|
|
129
|
+
export function claimCollectionPath(namespace) {
|
|
130
|
+
return `/apis/${SANDBOX_EXTENSIONS_API_GROUP}/${SANDBOX_API_VERSION}/namespaces/${segment(namespace)}/sandboxclaims`;
|
|
131
|
+
}
|
|
132
|
+
export function claimPath(namespace, name) {
|
|
133
|
+
return `${claimCollectionPath(namespace)}/${segment(name)}`;
|
|
134
|
+
}
|
|
135
|
+
export function sandboxCollectionPath(namespace) {
|
|
136
|
+
return `/apis/${SANDBOX_API_GROUP}/${SANDBOX_API_VERSION}/namespaces/${segment(namespace)}/sandboxes`;
|
|
137
|
+
}
|
|
138
|
+
export function sandboxPath(namespace, name) {
|
|
139
|
+
return `${sandboxCollectionPath(namespace)}/${segment(name)}`;
|
|
140
|
+
}
|
|
141
|
+
export function sandboxTemplatePath(namespace, name) {
|
|
142
|
+
return `/apis/${SANDBOX_EXTENSIONS_API_GROUP}/${SANDBOX_API_VERSION}/namespaces/${segment(namespace)}/sandboxtemplates/${segment(name)}`;
|
|
143
|
+
}
|
|
144
|
+
export function podPath(namespace, name) {
|
|
145
|
+
return `/api/v1/namespaces/${segment(namespace)}/pods/${segment(name)}`;
|
|
146
|
+
}
|
|
147
|
+
export function podListPath(namespace, labelSelector) {
|
|
148
|
+
return `/api/v1/namespaces/${segment(namespace)}/pods?labelSelector=${encodeURIComponent(labelSelector)}`;
|
|
149
|
+
}
|
|
150
|
+
export function networkPolicyPath(namespace, name) {
|
|
151
|
+
return `/apis/${CORE_NETWORK_POLICY_API_GROUP}/${CORE_NETWORK_POLICY_API_VERSION}/namespaces/${segment(namespace)}/networkpolicies/${segment(name)}`;
|
|
152
|
+
}
|
|
153
|
+
export function ciliumNetworkPolicyPath(namespace, name) {
|
|
154
|
+
return `/apis/${CILIUM_NETWORK_POLICY_API_GROUP}/${CILIUM_NETWORK_POLICY_API_VERSION}/namespaces/${segment(namespace)}/ciliumnetworkpolicies/${segment(name)}`;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=objects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"objects.js","sourceRoot":"","sources":["../../../src/backends/kubernetes/objects.ts"],"names":[],"mappings":"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"}
|