@namzu/sandbox 13.0.0 → 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 +309 -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
package/src/index.ts
CHANGED
|
@@ -44,6 +44,17 @@ import type {
|
|
|
44
44
|
import { buildAciStandbyPoolBackend } from './backends/aci-standby-pool/index.js'
|
|
45
45
|
import { buildDockerBackend, resolveLayout } from './backends/docker/index.js'
|
|
46
46
|
import { buildFirecrackerBackend } from './backends/firecracker/index.js'
|
|
47
|
+
import type { KubernetesEgressConfig } from './backends/kubernetes/egress-policy.js'
|
|
48
|
+
import {
|
|
49
|
+
type KubernetesBackendInternalConfig,
|
|
50
|
+
type KubernetesClusterAccess,
|
|
51
|
+
buildKubernetesBackend,
|
|
52
|
+
} from './backends/kubernetes/index.js'
|
|
53
|
+
import {
|
|
54
|
+
type KubernetesWorkspace,
|
|
55
|
+
type KubernetesWorkspaceOptions,
|
|
56
|
+
createKubernetesWorkspace as buildKubernetesWorkspace,
|
|
57
|
+
} from './backends/kubernetes/workspace.js'
|
|
47
58
|
|
|
48
59
|
// Re-export the layout types so consumers of `@namzu/sandbox` can
|
|
49
60
|
// import them without also depending on `@namzu/sdk`. The canonical
|
|
@@ -78,12 +89,57 @@ export type {
|
|
|
78
89
|
OrchestratorTokenProvider,
|
|
79
90
|
} from './backends/firecracker/index.js'
|
|
80
91
|
export {
|
|
92
|
+
AgentPreauthFrameTooLargeError,
|
|
81
93
|
FIRECRACKER_AGENT_PROTOCOL_VERSION,
|
|
82
94
|
type SandboxAgentHandle,
|
|
95
|
+
TCP_PREAUTH_FRAME_LIMIT_BYTES,
|
|
83
96
|
type VsockTransportOptions,
|
|
84
97
|
VsockAgentTransport,
|
|
85
98
|
} from './backends/firecracker/transport.js'
|
|
86
99
|
|
|
100
|
+
// Kubernetes (agent-sandbox on any cluster) public surface. The access union
|
|
101
|
+
// is named by `KubernetesBackendConfig.access`, so a host that builds its own
|
|
102
|
+
// credential callback can name what it is passing.
|
|
103
|
+
export type { KubernetesClusterAccess } from './backends/kubernetes/index.js'
|
|
104
|
+
// Egress translation types named by `KubernetesBackendConfig.egress` — see
|
|
105
|
+
// `backends/kubernetes/egress-policy.ts` for what each engine can express.
|
|
106
|
+
export type {
|
|
107
|
+
KubernetesEgressConfig,
|
|
108
|
+
KubernetesEgressEngine,
|
|
109
|
+
} from './backends/kubernetes/egress-policy.js'
|
|
110
|
+
// The errors a caller of a kubernetes sandbox has to be able to catch BY
|
|
111
|
+
// CLASS rather than by matching a message: an acquire refused because the
|
|
112
|
+
// guest is not deprivileged, a call after the handle ended (this host
|
|
113
|
+
// destroyed it, or the cluster deleted it), and a rejected agent token.
|
|
114
|
+
export {
|
|
115
|
+
KubernetesPrivilegeProbeError,
|
|
116
|
+
type PrivilegeProbeFailure,
|
|
117
|
+
type ProcStatusPrivileges,
|
|
118
|
+
} from './backends/kubernetes/privilege-probe.js'
|
|
119
|
+
export {
|
|
120
|
+
KubernetesSandboxDestroyedError,
|
|
121
|
+
KubernetesSandboxGoneError,
|
|
122
|
+
} from './backends/kubernetes/sandbox.js'
|
|
123
|
+
export { KubernetesAgentUnauthorizedError } from './backends/kubernetes/transport.js'
|
|
124
|
+
// The persistent workspace: a `Sandbox` that keeps a block disk across a
|
|
125
|
+
// suspend, plus the four errors its lifecycle can refuse with — a template
|
|
126
|
+
// that cannot carry a disk, a standing object that does not match this
|
|
127
|
+
// configuration, a call on a suspended workspace, and a suspend whose pod
|
|
128
|
+
// outlived the wait. Declared in `@namzu/sandbox` rather than on the SDK's
|
|
129
|
+
// `Sandbox` — see `backends/kubernetes/workspace.ts`.
|
|
130
|
+
export type {
|
|
131
|
+
KubernetesWorkspace,
|
|
132
|
+
KubernetesWorkspaceDestroyOptions,
|
|
133
|
+
KubernetesWorkspaceOptions,
|
|
134
|
+
KubernetesWorkspaceTransitionOptions,
|
|
135
|
+
} from './backends/kubernetes/workspace.js'
|
|
136
|
+
export {
|
|
137
|
+
KubernetesWorkspaceDiskError,
|
|
138
|
+
KubernetesWorkspaceMismatchError,
|
|
139
|
+
KubernetesWorkspaceSuspendTimeoutError,
|
|
140
|
+
KubernetesWorkspaceSuspendedError,
|
|
141
|
+
} from './backends/kubernetes/workspace.js'
|
|
142
|
+
|
|
87
143
|
// ---------------------------------------------------------------------------
|
|
88
144
|
// Backend strategy
|
|
89
145
|
// ---------------------------------------------------------------------------
|
|
@@ -120,6 +176,7 @@ export type SandboxBackendConfig =
|
|
|
120
176
|
| ContainerBackendConfig
|
|
121
177
|
| ACIStandbyPoolBackendConfig
|
|
122
178
|
| MicroVMBackendConfig
|
|
179
|
+
| KubernetesBackendConfig
|
|
123
180
|
|
|
124
181
|
/**
|
|
125
182
|
* Azure Container Instances Standby Pool backend. Container tier,
|
|
@@ -375,6 +432,101 @@ export interface AgentSnapshotRef {
|
|
|
375
432
|
readonly version: string
|
|
376
433
|
}
|
|
377
434
|
|
|
435
|
+
/**
|
|
436
|
+
* `microvm` tier, against a Kubernetes cluster running the agent-sandbox
|
|
437
|
+
* controller (kubernetes-sigs/agent-sandbox) with a VM-isolating
|
|
438
|
+
* RuntimeClass such as Kata.
|
|
439
|
+
*
|
|
440
|
+
* `microvm` because the tier names the strength of the boundary rather than
|
|
441
|
+
* the orchestrator behind it: a pod scheduled onto a Kata RuntimeClass runs
|
|
442
|
+
* in a hardware-virtualized guest, and the same field on the same tier is how
|
|
443
|
+
* a host says "give me a VM, I do not care who starts it".
|
|
444
|
+
*
|
|
445
|
+
* Sandboxes are claimed out of a `SandboxWarmPool` when {@link warmPoolName}
|
|
446
|
+
* names one, which is what makes the acquire sub-second; without it every
|
|
447
|
+
* create is a `Sandbox` built from {@link sandboxTemplateName}'s podTemplate
|
|
448
|
+
* and pays a full pod start. This package speaks the API server with bare
|
|
449
|
+
* `fetch` and carries no Kubernetes client dependency: credentials arrive
|
|
450
|
+
* through {@link access}, and kubeconfig parsing (context merging,
|
|
451
|
+
* exec credential plugins) stays in the host that owns it.
|
|
452
|
+
*/
|
|
453
|
+
export interface KubernetesBackendConfig {
|
|
454
|
+
readonly tier: 'microvm'
|
|
455
|
+
readonly service: 'kubernetes'
|
|
456
|
+
/** Namespace the claims, sandboxes and their pods live in. */
|
|
457
|
+
readonly namespace: string
|
|
458
|
+
/**
|
|
459
|
+
* How to reach the API server. `{ inCluster: true }` reads the projected
|
|
460
|
+
* ServiceAccount volume and the kubelet's `KUBERNETES_SERVICE_*` env, which
|
|
461
|
+
* is the production path; otherwise the host supplies the server URL, an
|
|
462
|
+
* optional cluster CA and a `getToken()` callback — the same boundary this
|
|
463
|
+
* package already draws for ACI's `getArmToken` and Firecracker's
|
|
464
|
+
* `getToken`.
|
|
465
|
+
*/
|
|
466
|
+
readonly access: KubernetesClusterAccess
|
|
467
|
+
/**
|
|
468
|
+
* `SandboxTemplate` whose `podTemplate` a POOL-LESS create copies into the
|
|
469
|
+
* `Sandbox` it posts. Required because `Sandbox.spec` has no `templateRef`
|
|
470
|
+
* — only a `SandboxWarmPool` references a template — so the pod spec has to
|
|
471
|
+
* be carried across by the client. The warm path does not read it; the
|
|
472
|
+
* pool's own `sandboxTemplateRef` decides there.
|
|
473
|
+
*/
|
|
474
|
+
readonly sandboxTemplateName: string
|
|
475
|
+
/**
|
|
476
|
+
* `SandboxWarmPool` to claim from. Absent → every create posts a `Sandbox`
|
|
477
|
+
* directly, because `SandboxClaim.spec.warmPoolRef` is a required field and
|
|
478
|
+
* a pool-less claim does not exist in the API.
|
|
479
|
+
*/
|
|
480
|
+
readonly warmPoolName?: string
|
|
481
|
+
/** TCP port the in-pod guest agent listens on. Default 1024. */
|
|
482
|
+
readonly agentPort?: number
|
|
483
|
+
/** Delay between readiness polls. Default 50ms. */
|
|
484
|
+
readonly readyPollIntervalMs?: number
|
|
485
|
+
/** Total deadline from create to an addressed, Ready sandbox. Default 60000ms. */
|
|
486
|
+
readonly readyTimeoutMs?: number
|
|
487
|
+
/**
|
|
488
|
+
* Wall-clock lifetime written into every object this backend creates, so a
|
|
489
|
+
* host that dies mid-run costs the cluster one expiry rather than a leaked
|
|
490
|
+
* sandbox. Default 3600.
|
|
491
|
+
*/
|
|
492
|
+
readonly claimTtlSeconds?: number
|
|
493
|
+
/**
|
|
494
|
+
* Every lease-renewal failure that is not "the object is already gone".
|
|
495
|
+
*
|
|
496
|
+
* The handle renews its own `shutdownTime` every half-TTL for as long as
|
|
497
|
+
* it is alive, so a run that outlives `claimTtlSeconds` keeps its pod.
|
|
498
|
+
* A failed renewal is retried on the next tick, half a TTL before
|
|
499
|
+
* anything expires; this callback is where the diagnostic goes, because
|
|
500
|
+
* `@namzu/sandbox` owns no logger and reads none from module scope.
|
|
501
|
+
* Setting it changes nothing about behaviour.
|
|
502
|
+
*/
|
|
503
|
+
readonly onLeaseRenewalError?: (error: unknown) => void
|
|
504
|
+
/**
|
|
505
|
+
* RuntimeClass for a POOL-LESS create. Refused together with
|
|
506
|
+
* {@link warmPoolName}: a pooled sandbox is already running under the
|
|
507
|
+
* RuntimeClass its `SandboxTemplate` named, and a claim cannot change it —
|
|
508
|
+
* so accepting it there would quietly drop the choice of VM boundary.
|
|
509
|
+
*/
|
|
510
|
+
readonly runtimeClassName?: string
|
|
511
|
+
/**
|
|
512
|
+
* Egress policy this backend expects an operator to have applied as a
|
|
513
|
+
* `NetworkPolicy` (or, under `engine: 'cilium'`, a `CiliumNetworkPolicy`)
|
|
514
|
+
* scoped to every Sandbox this backend produces. Unset means this backend
|
|
515
|
+
* neither computes nor checks one — the cluster's default posture (the
|
|
516
|
+
* `SandboxTemplate`'s own managed `NetworkPolicy`) is all that applies.
|
|
517
|
+
*
|
|
518
|
+
* This is a CONFIG-level, whole-backend policy, not a per-`create()` one:
|
|
519
|
+
* `SandboxBackendOptions.egress` is still refused by name (see
|
|
520
|
+
* `backends/kubernetes/index.ts`'s `assertEnforceable`), because the
|
|
521
|
+
* enforcement point is one object attached to the template and cannot be
|
|
522
|
+
* rewritten per running sandbox. `static` and `resolver` — hostname
|
|
523
|
+
* allowlists — throw a named error at construction unless `engine` is
|
|
524
|
+
* `'cilium'`: core `NetworkPolicy` has no FQDN concept at all. See
|
|
525
|
+
* `docs/sdk/kubernetes-sandbox.md`'s egress section.
|
|
526
|
+
*/
|
|
527
|
+
readonly egress?: KubernetesEgressConfig
|
|
528
|
+
}
|
|
529
|
+
|
|
378
530
|
/**
|
|
379
531
|
* Egress allowlist resolution. Host-supplied policy decides whether
|
|
380
532
|
* an outbound request is allowed before the proxy opens a socket.
|
|
@@ -505,9 +657,16 @@ export type SandboxProviderConfig =
|
|
|
505
657
|
readonly backend: ContainerBackendConfig
|
|
506
658
|
readonly layout: ContainerSandboxLayout
|
|
507
659
|
})
|
|
660
|
+
| (SandboxProviderConfigBase & {
|
|
661
|
+
readonly backend: ACIStandbyPoolBackendConfig
|
|
662
|
+
readonly layout: ContainerSandboxLayout
|
|
663
|
+
})
|
|
508
664
|
| (SandboxProviderConfigBase & {
|
|
509
665
|
readonly backend: MicroVMBackendConfig
|
|
510
666
|
})
|
|
667
|
+
| (SandboxProviderConfigBase & {
|
|
668
|
+
readonly backend: KubernetesBackendConfig
|
|
669
|
+
})
|
|
511
670
|
|
|
512
671
|
interface SandboxProviderConfigBase {
|
|
513
672
|
readonly defaultEgress?: EgressPolicy
|
|
@@ -580,6 +739,40 @@ export function createSandboxProvider(config: SandboxProviderConfig): SandboxPro
|
|
|
580
739
|
|
|
581
740
|
function pickBackend(config: SandboxProviderConfig): SandboxBackend {
|
|
582
741
|
const backend = config.backend
|
|
742
|
+
// Checked ahead of the `docker` default below: `ACIStandbyPoolBackendConfig`
|
|
743
|
+
// is a real arm of `SandboxProviderConfig` (see the discriminated union
|
|
744
|
+
// above), discriminated from `ContainerBackendConfig` by `runtime`. A
|
|
745
|
+
// plain equality check here narrows `backend` to the ACI shape with no
|
|
746
|
+
// cast, and — because this branch always returns — narrows it AWAY for
|
|
747
|
+
// every check below, so the `docker` branch's `backend.runtime ?? 'docker'`
|
|
748
|
+
// still sees only `ContainerBackendConfig`.
|
|
749
|
+
if (backend.tier === 'container' && backend.runtime === 'aci-standby-pool') {
|
|
750
|
+
const layout = (config as Extract<SandboxProviderConfig, { layout: ContainerSandboxLayout }>)
|
|
751
|
+
.layout
|
|
752
|
+
const resolved = resolveLayout(layout)
|
|
753
|
+
return buildAciStandbyPoolBackend({
|
|
754
|
+
subscriptionId: backend.subscriptionId,
|
|
755
|
+
resourceGroup: backend.resourceGroup,
|
|
756
|
+
location: backend.location,
|
|
757
|
+
standbyPoolResourceId: backend.standbyPoolResourceId,
|
|
758
|
+
containerGroupProfileResourceId: backend.containerGroupProfileResourceId,
|
|
759
|
+
...(backend.containerGroupProfileRevision !== undefined
|
|
760
|
+
? { containerGroupProfileRevision: backend.containerGroupProfileRevision }
|
|
761
|
+
: {}),
|
|
762
|
+
layout: resolved,
|
|
763
|
+
getArmToken: backend.getArmToken,
|
|
764
|
+
...(backend.subnetId !== undefined ? { subnetId: backend.subnetId } : {}),
|
|
765
|
+
...(backend.readyPollIntervalMs !== undefined
|
|
766
|
+
? { readyPollIntervalMs: backend.readyPollIntervalMs }
|
|
767
|
+
: {}),
|
|
768
|
+
...(backend.readyTimeoutMs !== undefined ? { readyTimeoutMs: backend.readyTimeoutMs } : {}),
|
|
769
|
+
...(backend.workerPort !== undefined ? { workerPort: backend.workerPort } : {}),
|
|
770
|
+
...(backend.armApiVersion !== undefined ? { armApiVersion: backend.armApiVersion } : {}),
|
|
771
|
+
...(backend.containerNamePrefix !== undefined
|
|
772
|
+
? { containerNamePrefix: backend.containerNamePrefix }
|
|
773
|
+
: {}),
|
|
774
|
+
})
|
|
775
|
+
}
|
|
583
776
|
if (backend.tier === 'container' && (backend.runtime ?? 'docker') === 'docker') {
|
|
584
777
|
// `layout` is required for container-tier backends by the
|
|
585
778
|
// discriminated union — narrow safely without a non-null
|
|
@@ -605,41 +798,6 @@ function pickBackend(config: SandboxProviderConfig): SandboxBackend {
|
|
|
605
798
|
...(backend.labels !== undefined ? { labels: backend.labels } : {}),
|
|
606
799
|
})
|
|
607
800
|
}
|
|
608
|
-
if (
|
|
609
|
-
backend.tier === 'container' &&
|
|
610
|
-
(backend as unknown as { runtime?: string }).runtime === 'aci-standby-pool'
|
|
611
|
-
) {
|
|
612
|
-
const aciBackend = backend as unknown as ACIStandbyPoolBackendConfig
|
|
613
|
-
const layout = (config as Extract<SandboxProviderConfig, { layout: ContainerSandboxLayout }>)
|
|
614
|
-
.layout
|
|
615
|
-
const resolved = resolveLayout(layout)
|
|
616
|
-
return buildAciStandbyPoolBackend({
|
|
617
|
-
subscriptionId: aciBackend.subscriptionId,
|
|
618
|
-
resourceGroup: aciBackend.resourceGroup,
|
|
619
|
-
location: aciBackend.location,
|
|
620
|
-
standbyPoolResourceId: aciBackend.standbyPoolResourceId,
|
|
621
|
-
containerGroupProfileResourceId: aciBackend.containerGroupProfileResourceId,
|
|
622
|
-
...(aciBackend.containerGroupProfileRevision !== undefined
|
|
623
|
-
? { containerGroupProfileRevision: aciBackend.containerGroupProfileRevision }
|
|
624
|
-
: {}),
|
|
625
|
-
layout: resolved,
|
|
626
|
-
getArmToken: aciBackend.getArmToken,
|
|
627
|
-
...(aciBackend.subnetId !== undefined ? { subnetId: aciBackend.subnetId } : {}),
|
|
628
|
-
...(aciBackend.readyPollIntervalMs !== undefined
|
|
629
|
-
? { readyPollIntervalMs: aciBackend.readyPollIntervalMs }
|
|
630
|
-
: {}),
|
|
631
|
-
...(aciBackend.readyTimeoutMs !== undefined
|
|
632
|
-
? { readyTimeoutMs: aciBackend.readyTimeoutMs }
|
|
633
|
-
: {}),
|
|
634
|
-
...(aciBackend.workerPort !== undefined ? { workerPort: aciBackend.workerPort } : {}),
|
|
635
|
-
...(aciBackend.armApiVersion !== undefined
|
|
636
|
-
? { armApiVersion: aciBackend.armApiVersion }
|
|
637
|
-
: {}),
|
|
638
|
-
...(aciBackend.containerNamePrefix !== undefined
|
|
639
|
-
? { containerNamePrefix: aciBackend.containerNamePrefix }
|
|
640
|
-
: {}),
|
|
641
|
-
})
|
|
642
|
-
}
|
|
643
801
|
if (backend.tier === 'container' && backend.runtime === 'runsc') {
|
|
644
802
|
const layout = (config as Extract<SandboxProviderConfig, { layout: ContainerSandboxLayout }>)
|
|
645
803
|
.layout
|
|
@@ -688,9 +846,73 @@ function pickBackend(config: SandboxProviderConfig): SandboxBackend {
|
|
|
688
846
|
: {}),
|
|
689
847
|
})
|
|
690
848
|
}
|
|
849
|
+
// `microvm:kubernetes` — agent-sandbox on any cluster. Reached through a
|
|
850
|
+
// real arm of `SandboxProviderConfig`, so `backend` narrows here and every
|
|
851
|
+
// field below is read off the narrowed type, same as the ACI and docker
|
|
852
|
+
// branches above.
|
|
853
|
+
if (backend.tier === 'microvm' && backend.service === 'kubernetes') {
|
|
854
|
+
return buildKubernetesBackend(kubernetesInternalConfig(backend))
|
|
855
|
+
}
|
|
691
856
|
throw new SandboxBackendNotImplementedError(describeBackend(backend))
|
|
692
857
|
}
|
|
693
858
|
|
|
859
|
+
/**
|
|
860
|
+
* Public config → the kubernetes backend's own. One function so the two
|
|
861
|
+
* entry points that build against a cluster — {@link createSandboxProvider}
|
|
862
|
+
* for task sandboxes and {@link createKubernetesWorkspace} for persistent
|
|
863
|
+
* ones — cannot drift apart on which fields they forward.
|
|
864
|
+
*/
|
|
865
|
+
function kubernetesInternalConfig(
|
|
866
|
+
backend: KubernetesBackendConfig,
|
|
867
|
+
): KubernetesBackendInternalConfig {
|
|
868
|
+
return {
|
|
869
|
+
access: backend.access,
|
|
870
|
+
namespace: backend.namespace,
|
|
871
|
+
sandboxTemplateName: backend.sandboxTemplateName,
|
|
872
|
+
...(backend.warmPoolName !== undefined ? { warmPoolName: backend.warmPoolName } : {}),
|
|
873
|
+
...(backend.agentPort !== undefined ? { agentPort: backend.agentPort } : {}),
|
|
874
|
+
...(backend.readyPollIntervalMs !== undefined
|
|
875
|
+
? { readyPollIntervalMs: backend.readyPollIntervalMs }
|
|
876
|
+
: {}),
|
|
877
|
+
...(backend.readyTimeoutMs !== undefined ? { readyTimeoutMs: backend.readyTimeoutMs } : {}),
|
|
878
|
+
...(backend.claimTtlSeconds !== undefined ? { claimTtlSeconds: backend.claimTtlSeconds } : {}),
|
|
879
|
+
...(backend.onLeaseRenewalError !== undefined
|
|
880
|
+
? { onLeaseRenewalError: backend.onLeaseRenewalError }
|
|
881
|
+
: {}),
|
|
882
|
+
...(backend.runtimeClassName !== undefined
|
|
883
|
+
? { runtimeClassName: backend.runtimeClassName }
|
|
884
|
+
: {}),
|
|
885
|
+
...(backend.egress !== undefined ? { egress: backend.egress } : {}),
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Create — or reattach to — a persistent workspace on a cluster running the
|
|
891
|
+
* agent-sandbox controller.
|
|
892
|
+
*
|
|
893
|
+
* A workspace is the other half of this backend, and deliberately not
|
|
894
|
+
* something {@link createSandboxProvider} can hand out: a `SandboxProvider`
|
|
895
|
+
* promises an EPHEMERAL sandbox per run (`workspaceModes: ['ephemeral']`),
|
|
896
|
+
* while this returns one object with a name the caller chose, a disk that
|
|
897
|
+
* survives a suspend, and a lifetime nothing reaps on a timer. It is its own
|
|
898
|
+
* verb so that the difference is visible at the call site.
|
|
899
|
+
*
|
|
900
|
+
* `config.warmPoolName` is ignored here: a workspace is always a `Sandbox`
|
|
901
|
+
* POSTed directly, because a claim cannot carry the immutable disk spec.
|
|
902
|
+
* `config.sandboxTemplateName` is the default template, and
|
|
903
|
+
* `options.sandboxTemplateName` overrides it — a deployment normally has a
|
|
904
|
+
* task template with no disk and a workspace template with a block one.
|
|
905
|
+
*
|
|
906
|
+
* Resolves once the workspace is Ready, addressed and has proved it is
|
|
907
|
+
* deprivileged, exactly as `provider.create()` does for a task sandbox.
|
|
908
|
+
*/
|
|
909
|
+
export async function createKubernetesWorkspace(
|
|
910
|
+
config: KubernetesBackendConfig,
|
|
911
|
+
options: KubernetesWorkspaceOptions,
|
|
912
|
+
): Promise<KubernetesWorkspace> {
|
|
913
|
+
return await buildKubernetesWorkspace(kubernetesInternalConfig(config), options)
|
|
914
|
+
}
|
|
915
|
+
|
|
694
916
|
/**
|
|
695
917
|
* Human-readable backend label for error messages. Returns the
|
|
696
918
|
* tier plus the concrete service / runtime when present, e.g.
|