@skaile/provider-k8s 0.2.0 → 0.2.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/CHANGELOG.md +6 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @skaile/provider-k8s
|
|
2
2
|
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#1100](https://github.com/skaile-ai/workspaces/pull/1100) [`3a3aa72`](https://github.com/skaile-ai/workspaces/commit/3a3aa72a169904ebc758a7d029ffa95b2c8178a7) Thanks [@mortegro](https://github.com/mortegro)! - Peer range for `@skaile/workspaces` is now `^6.0.0` (harness replaces driver in 6.0.0; these plugins use no renamed API).
|
|
8
|
+
|
|
3
9
|
## 0.2.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/target.ts","../src/port-forward.ts","../src/sdk-types.ts","../src/index.ts"],"sourcesContent":["/**\n * `k8sDeployTarget` — deploy a workspace as a Pod + ClusterIP Service in\n * Kubernetes, reached via `kubectl port-forward`. `tlsTermination: \"edge\"`\n * (TLS terminates at the cluster ingress; v0.1.0 reaches the service over a\n * local port-forward tunnel).\n *\n * `buildStrategy` defaults to `local` — k8s is bring-your-own-image, so the\n * operator supplies `config.image`. `@kubernetes/client-node` is an *optional*\n * peer, lazy-loaded inside `create()`/`restore()` via an injectable loader so\n * the package builds standalone and the test stays hermetic.\n */\n\nimport {\n buildStrategySchema,\n type DeployContext,\n type DeployHandle,\n type DeployTarget,\n makeDeployHandle,\n pollUntil,\n} from \"@skaile/workspaces/plugin-registry\";\nimport * as z from \"zod\";\nimport { defaultPortForwarder, type PortForward, type PortForwarder } from \"./port-forward.js\";\nimport { type CoreV1Api, defaultSdkLoader, type SdkLoader } from \"./sdk-types.js\";\n\nconst k8sConfigSchema = z\n .object({\n namespace: z.string().default(\"default\"),\n image: z.string().min(1),\n port: z.number().int().positive().default(8080),\n serviceAccount: z.string().optional(),\n env: z.record(z.string(), z.string()).default({}),\n // v0.1.0 ships ClusterIP + port-forward only; NodePort/LoadBalancer/Ingress\n // are documented as future work.\n serviceType: z.literal(\"ClusterIP\").default(\"ClusterIP\"),\n localForwardPort: z.number().int().positive().optional(),\n buildStrategy: buildStrategySchema.default(\"local\"),\n })\n .strict();\n\ntype K8sConfig = z.infer<typeof k8sConfigSchema>;\n\nconst k8sPayloadSchema = z.object({\n namespace: z.string(),\n podName: z.string(),\n serviceName: z.string(),\n localForwardPort: z.number().optional(),\n});\n\ntype K8sPayload = z.infer<typeof k8sPayloadSchema>;\n\nconst DEFAULT_READY_TIMEOUT_MS = 120_000;\nconst DEFAULT_POLL_INTERVAL_MS = 1_000;\n// Container port to forward to on restore; the payload doesn't carry it, so we\n// fall back to the config schema's default (k8s is ClusterIP, port-only).\nconst DEFAULT_TARGET_PORT = 8080;\n\n/** Injectable seams so tests never load the real `@kubernetes/client-node`. */\nexport interface K8sTargetOptions {\n loadSdk?: SdkLoader;\n pollIntervalMs?: number;\n /** Override the `kubectl port-forward` spawn so tests stay hermetic. */\n portForward?: PortForwarder;\n}\n\nasync function makeApi(loadSdk: SdkLoader): Promise<CoreV1Api> {\n const k8s = await loadSdk();\n const kc = new k8s.KubeConfig();\n kc.loadFromDefault();\n return kc.makeApiClient(k8s.CoreV1Api);\n}\n\n/**\n * Build the per-target seams `makeDeployHandle` drives. `health` reads the pod\n * phase; `stop` does idempotent teardown only — the factory writes state.\n * `wsUrl` reflects the port `kubectl` actually bound, and `stop` kills the\n * tunnel alongside the cluster objects.\n */\nfunction buildHandle(\n api: CoreV1Api,\n payload: K8sPayload,\n forward: PortForward,\n ctx: DeployContext,\n ready: (timeoutMs: number, signal: AbortSignal) => Promise<void>,\n initialState: DeployHandle[\"state\"],\n): DeployHandle<K8sPayload> {\n return makeDeployHandle<K8sPayload>({\n // Reached over the local port-forward tunnel kubectl established.\n wsUrl: `ws://127.0.0.1:${forward.localPort}`,\n ctx,\n payload,\n initialState,\n defaultReadyTimeoutMs: DEFAULT_READY_TIMEOUT_MS,\n ready,\n async health() {\n try {\n const pod = await api.readNamespacedPod({\n name: payload.podName,\n namespace: payload.namespace,\n });\n const healthy = pod.status?.phase === \"Running\";\n return healthy\n ? { healthy }\n : { healthy, detail: `pod ${payload.podName} phase=${pod.status?.phase ?? \"unknown\"}` };\n } catch (err) {\n return { healthy: false, detail: (err as Error).message };\n }\n },\n async stop() {\n // Kill the tunnel first so no client lingers on a soon-to-be-deleted svc.\n forward.stop();\n // Idempotent teardown: ignore 404s when the objects are already gone.\n await api\n .deleteNamespacedService({ name: payload.serviceName, namespace: payload.namespace })\n .catch(() => {});\n await api\n .deleteNamespacedPod({ name: payload.podName, namespace: payload.namespace })\n .catch(() => {});\n },\n });\n}\n\n/** Build the Kubernetes deploy target with optional injected seams. */\nexport function makeK8sDeployTarget(\n opts: K8sTargetOptions = {},\n): DeployTarget<K8sConfig, K8sPayload> {\n const loadSdk = opts.loadSdk ?? defaultSdkLoader;\n const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n const portForward = opts.portForward ?? defaultPortForwarder;\n\n return {\n id: \"k8s\",\n displayName: \"Kubernetes (ClusterIP + port-forward)\",\n apiVersion: 1,\n tlsTermination: \"edge\",\n configSchema: k8sConfigSchema as unknown as z.ZodType<K8sConfig>,\n payloadSchema: k8sPayloadSchema,\n\n async create(config, ctx) {\n const api = await makeApi(loadSdk);\n const podName = `skaile-ws-${ctx.workspaceId}`;\n const serviceName = `skaile-ws-${ctx.workspaceId}-svc`;\n const image = config.image ?? ctx.imageRef;\n if (!image) throw new Error(\"k8s deploy requires an image (buildStrategy is local)\");\n\n const podBody = {\n metadata: { name: podName, namespace: config.namespace, labels: { app: podName } },\n spec: {\n serviceAccountName: config.serviceAccount,\n containers: [\n {\n name: \"workspace\",\n image,\n ports: [{ containerPort: config.port }],\n env: Object.entries({ ...config.env, ...ctx.env }).map(([name, value]) => ({\n name,\n value,\n })),\n },\n ],\n },\n };\n await api.createNamespacedPod({ namespace: config.namespace, body: podBody });\n\n const serviceBody = {\n metadata: { name: serviceName, namespace: config.namespace },\n spec: {\n type: config.serviceType,\n selector: { app: podName },\n ports: [{ port: config.port, targetPort: config.port }],\n },\n };\n try {\n await api.createNamespacedService({ namespace: config.namespace, body: serviceBody });\n } catch (err) {\n // Service create failed after the pod was created: best-effort delete the\n // orphaned pod (ignore 404s, mirroring stop()) before propagating.\n await api\n .deleteNamespacedPod({ name: podName, namespace: config.namespace })\n .catch(() => {});\n throw err;\n }\n ctx.log.info(\"k8s pod + service created\", { podName, serviceName });\n\n // Establish the tunnel now so wsUrl reflects the port kubectl actually\n // bound. A forward failure must not leak the pod/service we just created.\n let forward: PortForward;\n try {\n forward = await portForward({\n serviceName,\n namespace: config.namespace,\n targetPort: config.port,\n localPort: config.localForwardPort,\n });\n } catch (err) {\n await api\n .deleteNamespacedService({ name: serviceName, namespace: config.namespace })\n .catch(() => {});\n await api\n .deleteNamespacedPod({ name: podName, namespace: config.namespace })\n .catch(() => {});\n throw err;\n }\n ctx.log.info(\"k8s port-forward established\", { serviceName, localPort: forward.localPort });\n\n const payload: K8sPayload = {\n namespace: config.namespace,\n podName,\n serviceName,\n localForwardPort: forward.localPort,\n };\n const ready = (timeoutMs: number, signal: AbortSignal) =>\n pollUntil({\n check: async () =>\n (await api.readNamespacedPod({ name: podName, namespace: config.namespace })).status\n ?.phase === \"Running\",\n timeoutMs,\n intervalMs: pollIntervalMs,\n signal,\n timeoutMessage: `timed out waiting for pod ${podName} to reach Running`,\n });\n return buildHandle(api, payload, forward, ctx, ready, \"starting\");\n },\n\n async restore(payload, ctx) {\n const api = await makeApi(loadSdk);\n const pod = await api.readNamespacedPod({\n name: payload.podName,\n namespace: payload.namespace,\n });\n if (pod.status?.phase !== \"Running\") {\n throw new Error(\n `k8s pod ${payload.podName} is not running (phase=${pod.status?.phase ?? \"unknown\"})`,\n );\n }\n // The forward is a local child process that doesn't survive a restart, so\n // re-establish it. Prefer the previously bound port for a stable wsUrl.\n const forward = await portForward({\n serviceName: payload.serviceName,\n namespace: payload.namespace,\n targetPort: DEFAULT_TARGET_PORT,\n localPort: payload.localForwardPort,\n });\n ctx.log.info(\"re-attached to k8s pod\", {\n podName: payload.podName,\n localPort: forward.localPort,\n });\n const restored: K8sPayload = { ...payload, localForwardPort: forward.localPort };\n return buildHandle(api, restored, forward, ctx, async () => {}, \"ready\");\n },\n };\n}\n\n/** Default Kubernetes deploy target registered by `register()`. */\nexport const k8sDeployTarget = makeK8sDeployTarget();\n","/**\n * Port-forward seam for the k8s deploy target.\n *\n * The provider reaches a ClusterIP Service through `kubectl port-forward`. We\n * shell out to `kubectl` rather than driving `@kubernetes/client-node`'s\n * `PortForward` + a local `net.Server`: a child process is a single injectable\n * seam (mirroring the SDK loader) that tests can fake with no real socket, and\n * it reuses the operator's existing kubeconfig/auth without re-implementing the\n * stream plumbing. The spawn is injectable so the suite stays hermetic.\n */\n\nimport { spawn } from \"node:child_process\";\n\n/** A live port-forward tunnel. `localPort` is the port `kubectl` actually bound. */\nexport interface PortForward {\n readonly localPort: number;\n /** Idempotent: kill the tunnel; safe to call more than once. */\n stop(): void;\n}\n\n/** Inputs needed to establish a `svc/<name>` forward. */\nexport interface PortForwardSpec {\n serviceName: string;\n namespace: string;\n targetPort: number;\n /** Local port to bind; `0` (or omitted) lets the OS pick a free one. */\n localPort?: number;\n /** Cap on how long to wait for `kubectl` to report it is forwarding. */\n timeoutMs?: number;\n}\n\n/** Seam: establish a forward and resolve once it is actually serving locally. */\nexport type PortForwarder = (spec: PortForwardSpec) => Promise<PortForward>;\n\nconst DEFAULT_FORWARD_TIMEOUT_MS = 30_000;\n// kubectl prints e.g. \"Forwarding from 127.0.0.1:54321 -> 8080\" once bound.\nconst FORWARDING_RE = /Forwarding from (?:127\\.0\\.0\\.1|\\[::1\\]):(\\d+)/;\n\n/**\n * Default forwarder: spawn `kubectl port-forward svc/<name> <local>:<target>`\n * and resolve with the bound local port once kubectl reports it is forwarding.\n * Rejects (and kills the child) on early exit, timeout, or a missing binary.\n */\nexport const defaultPortForwarder: PortForwarder = (spec) => {\n const timeoutMs = spec.timeoutMs ?? DEFAULT_FORWARD_TIMEOUT_MS;\n const local = spec.localPort ?? 0;\n const child = spawn(\n \"kubectl\",\n [\n \"port-forward\",\n `svc/${spec.serviceName}`,\n `${local}:${spec.targetPort}`,\n \"-n\",\n spec.namespace,\n ],\n { stdio: [\"ignore\", \"pipe\", \"pipe\"] },\n );\n\n return new Promise<PortForward>((resolve, reject) => {\n let settled = false;\n let stderr = \"\";\n\n const cleanup = () => {\n clearTimeout(timer);\n child.stdout?.removeAllListeners();\n child.stderr?.removeAllListeners();\n };\n const stop = () => {\n try {\n child.kill();\n } catch {\n /* already gone */\n }\n };\n const fail = (err: Error) => {\n if (settled) return;\n settled = true;\n cleanup();\n stop();\n reject(err);\n };\n\n const timer = setTimeout(\n () => fail(new Error(`timed out waiting for kubectl port-forward to bind (${timeoutMs}ms)`)),\n timeoutMs,\n );\n\n child.stdout?.on(\"data\", (chunk: Buffer) => {\n const m = FORWARDING_RE.exec(chunk.toString());\n if (m && !settled) {\n settled = true;\n cleanup();\n resolve({ localPort: Number(m[1]), stop });\n }\n });\n child.stderr?.on(\"data\", (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n child.on(\"error\", (err) =>\n fail(new Error(`failed to spawn kubectl port-forward: ${err.message}`)),\n );\n child.on(\"exit\", (code) =>\n fail(\n new Error(\n `kubectl port-forward exited (code=${code ?? \"null\"}) before forwarding${stderr ? `: ${stderr.trim()}` : \"\"}`,\n ),\n ),\n );\n });\n};\n","/**\n * Minimal local interface for `@kubernetes/client-node` — only the members this\n * provider actually calls.\n *\n * The SDK is an *optional* peer dependency, lazy-loaded via a string-literal\n * dynamic `import()`. Typing against this minimal surface (rather than the real\n * package's types) keeps the package building + typechecking standalone\n * offline; the host project supplies the real SDK at runtime.\n */\n\n/** A `V1Pod`-ish object (subset we read). */\nexport interface K8sPod {\n metadata?: { name?: string; namespace?: string };\n status?: { phase?: string };\n}\n\n/** A `V1Service`-ish object (subset we read). */\nexport interface K8sService {\n metadata?: { name?: string; namespace?: string };\n}\n\n/** `CoreV1Api` subset. */\nexport interface CoreV1Api {\n createNamespacedPod(arg: { namespace: string; body: unknown }): Promise<K8sPod>;\n readNamespacedPod(arg: { name: string; namespace: string }): Promise<K8sPod>;\n deleteNamespacedPod(arg: { name: string; namespace: string }): Promise<unknown>;\n createNamespacedService(arg: { namespace: string; body: unknown }): Promise<K8sService>;\n deleteNamespacedService(arg: { name: string; namespace: string }): Promise<unknown>;\n}\n\n/** `KubeConfig` subset. */\nexport interface KubeConfig {\n loadFromDefault(): void;\n // biome-ignore lint/suspicious/noExplicitAny: api ctor passed through opaquely\n makeApiClient(api: any): CoreV1Api;\n}\n\n/** The `@kubernetes/client-node` module surface we depend on. */\nexport interface K8sModule {\n KubeConfig: new () => KubeConfig;\n CoreV1Api: unknown;\n}\n\n/** Loader seam: returns the SDK module. Default does a real dynamic import. */\nexport type SdkLoader = () => Promise<K8sModule>;\n\n/**\n * Default loader: lazy `import(\"@kubernetes/client-node\")`. The string literal\n * keeps bundlers from eagerly resolving the optional peer; only evaluated when\n * `create()`/`restore()` run.\n */\nexport const defaultSdkLoader: SdkLoader = async () => {\n const mod = (await import(\"@kubernetes/client-node\" as string)) as unknown as K8sModule;\n if (!mod?.KubeConfig) {\n throw new Error(\n \"@kubernetes/client-node is not installed. Run `skaile plugin install @skaile/provider-k8s` and add @kubernetes/client-node to your project.\",\n );\n }\n return mod;\n};\n","/**\n * `@skaile/provider-k8s` — Kubernetes deploy target for `@skaile/workspaces`.\n *\n * v0.1.0 ships ClusterIP + port-forward only. Requires the optional peer\n * `@kubernetes/client-node` to be installed in the host project (lazy-loaded at\n * runtime). Install via `skaile plugin install @skaile/provider-k8s`.\n */\n\nimport type { PluginRegistry } from \"@skaile/workspaces/plugin-registry\";\nimport { k8sDeployTarget } from \"./target.js\";\n\nexport { k8sDeployTarget, makeK8sDeployTarget } from \"./target.js\";\nexport type { K8sTargetOptions } from \"./target.js\";\nexport { defaultPortForwarder } from \"./port-forward.js\";\nexport type { PortForward, PortForwarder, PortForwardSpec } from \"./port-forward.js\";\nexport type { CoreV1Api, K8sModule, KubeConfig, SdkLoader } from \"./sdk-types.js\";\n\n/** Register this plugin's deploy target with a registry. */\nexport function register(registry: PluginRegistry): void {\n registry.register(\"deployTarget\", k8sDeployTarget);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/target.ts","../src/port-forward.ts","../src/sdk-types.ts","../src/index.ts"],"sourcesContent":["/**\n * `k8sDeployTarget` — deploy a workspace as a Pod + ClusterIP Service in\n * Kubernetes, reached via `kubectl port-forward`. `tlsTermination: \"edge\"`\n * (TLS terminates at the cluster ingress; v0.1.0 reaches the service over a\n * local port-forward tunnel).\n *\n * `buildStrategy` defaults to `local` — k8s is bring-your-own-image, so the\n * operator supplies `config.image`. `@kubernetes/client-node` is an *optional*\n * peer, lazy-loaded inside `create()`/`restore()` via an injectable loader so\n * the package builds standalone and the test stays hermetic.\n */\n\nimport {\n buildStrategySchema,\n type DeployContext,\n type DeployHandle,\n type DeployTarget,\n makeDeployHandle,\n pollUntil,\n} from \"@skaile/workspaces/plugin-registry\";\nimport * as z from \"zod\";\nimport { defaultPortForwarder, type PortForward, type PortForwarder } from \"./port-forward.js\";\nimport { type CoreV1Api, defaultSdkLoader, type SdkLoader } from \"./sdk-types.js\";\n\nconst k8sConfigSchema = z\n .object({\n namespace: z.string().default(\"default\"),\n image: z.string().min(1),\n port: z.number().int().positive().default(8080),\n serviceAccount: z.string().optional(),\n env: z.record(z.string(), z.string()).default({}),\n // v0.1.0 ships ClusterIP + port-forward only; NodePort/LoadBalancer/Ingress\n // are documented as future work.\n serviceType: z.literal(\"ClusterIP\").default(\"ClusterIP\"),\n localForwardPort: z.number().int().positive().optional(),\n buildStrategy: buildStrategySchema.default(\"local\"),\n })\n .strict();\n\ntype K8sConfig = z.infer<typeof k8sConfigSchema>;\n\nconst k8sPayloadSchema = z.object({\n namespace: z.string(),\n podName: z.string(),\n serviceName: z.string(),\n localForwardPort: z.number().optional(),\n});\n\ntype K8sPayload = z.infer<typeof k8sPayloadSchema>;\n\nconst DEFAULT_READY_TIMEOUT_MS = 120_000;\nconst DEFAULT_POLL_INTERVAL_MS = 1_000;\n// Container port to forward to on restore; the payload doesn't carry it, so we\n// fall back to the config schema's default (k8s is ClusterIP, port-only).\nconst DEFAULT_TARGET_PORT = 8080;\n\n/** Injectable seams so tests never load the real `@kubernetes/client-node`. */\nexport interface K8sTargetOptions {\n loadSdk?: SdkLoader;\n pollIntervalMs?: number;\n /** Override the `kubectl port-forward` spawn so tests stay hermetic. */\n portForward?: PortForwarder;\n}\n\nasync function makeApi(loadSdk: SdkLoader): Promise<CoreV1Api> {\n const k8s = await loadSdk();\n const kc = new k8s.KubeConfig();\n kc.loadFromDefault();\n return kc.makeApiClient(k8s.CoreV1Api);\n}\n\n/**\n * Build the per-target seams `makeDeployHandle` drives. `health` reads the pod\n * phase; `stop` does idempotent teardown only — the factory writes state.\n * `wsUrl` reflects the port `kubectl` actually bound, and `stop` kills the\n * tunnel alongside the cluster objects.\n */\nfunction buildHandle(\n api: CoreV1Api,\n payload: K8sPayload,\n forward: PortForward,\n ctx: DeployContext,\n ready: (timeoutMs: number, signal: AbortSignal) => Promise<void>,\n initialState: DeployHandle[\"state\"],\n): DeployHandle<K8sPayload> {\n return makeDeployHandle<K8sPayload>({\n // Reached over the local port-forward tunnel kubectl established.\n wsUrl: `ws://127.0.0.1:${forward.localPort}`,\n ctx,\n payload,\n initialState,\n defaultReadyTimeoutMs: DEFAULT_READY_TIMEOUT_MS,\n ready,\n async health() {\n try {\n const pod = await api.readNamespacedPod({\n name: payload.podName,\n namespace: payload.namespace,\n });\n const healthy = pod.status?.phase === \"Running\";\n return healthy\n ? { healthy }\n : { healthy, detail: `pod ${payload.podName} phase=${pod.status?.phase ?? \"unknown\"}` };\n } catch (err) {\n return { healthy: false, detail: (err as Error).message };\n }\n },\n async stop() {\n // Kill the tunnel first so no client lingers on a soon-to-be-deleted svc.\n forward.stop();\n // Idempotent teardown: ignore 404s when the objects are already gone.\n await api\n .deleteNamespacedService({ name: payload.serviceName, namespace: payload.namespace })\n .catch(() => {});\n await api\n .deleteNamespacedPod({ name: payload.podName, namespace: payload.namespace })\n .catch(() => {});\n },\n });\n}\n\n/** Build the Kubernetes deploy target with optional injected seams. */\nexport function makeK8sDeployTarget(\n opts: K8sTargetOptions = {},\n): DeployTarget<K8sConfig, K8sPayload> {\n const loadSdk = opts.loadSdk ?? defaultSdkLoader;\n const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n const portForward = opts.portForward ?? defaultPortForwarder;\n\n return {\n id: \"k8s\",\n displayName: \"Kubernetes (ClusterIP + port-forward)\",\n apiVersion: 1,\n tlsTermination: \"edge\",\n configSchema: k8sConfigSchema as unknown as z.ZodType<K8sConfig>,\n payloadSchema: k8sPayloadSchema,\n\n async create(config, ctx) {\n const api = await makeApi(loadSdk);\n const podName = `skaile-ws-${ctx.workspaceId}`;\n const serviceName = `skaile-ws-${ctx.workspaceId}-svc`;\n const image = config.image ?? ctx.imageRef;\n if (!image) throw new Error(\"k8s deploy requires an image (buildStrategy is local)\");\n\n const podBody = {\n metadata: { name: podName, namespace: config.namespace, labels: { app: podName } },\n spec: {\n serviceAccountName: config.serviceAccount,\n containers: [\n {\n name: \"workspace\",\n image,\n ports: [{ containerPort: config.port }],\n env: Object.entries({ ...config.env, ...ctx.env }).map(([name, value]) => ({\n name,\n value,\n })),\n },\n ],\n },\n };\n await api.createNamespacedPod({ namespace: config.namespace, body: podBody });\n\n const serviceBody = {\n metadata: { name: serviceName, namespace: config.namespace },\n spec: {\n type: config.serviceType,\n selector: { app: podName },\n ports: [{ port: config.port, targetPort: config.port }],\n },\n };\n try {\n await api.createNamespacedService({ namespace: config.namespace, body: serviceBody });\n } catch (err) {\n // Service create failed after the pod was created: best-effort delete the\n // orphaned pod (ignore 404s, mirroring stop()) before propagating.\n await api\n .deleteNamespacedPod({ name: podName, namespace: config.namespace })\n .catch(() => {});\n throw err;\n }\n ctx.log.info(\"k8s pod + service created\", { podName, serviceName });\n\n // Establish the tunnel now so wsUrl reflects the port kubectl actually\n // bound. A forward failure must not leak the pod/service we just created.\n let forward: PortForward;\n try {\n forward = await portForward({\n serviceName,\n namespace: config.namespace,\n targetPort: config.port,\n localPort: config.localForwardPort,\n });\n } catch (err) {\n await api\n .deleteNamespacedService({ name: serviceName, namespace: config.namespace })\n .catch(() => {});\n await api\n .deleteNamespacedPod({ name: podName, namespace: config.namespace })\n .catch(() => {});\n throw err;\n }\n ctx.log.info(\"k8s port-forward established\", { serviceName, localPort: forward.localPort });\n\n const payload: K8sPayload = {\n namespace: config.namespace,\n podName,\n serviceName,\n localForwardPort: forward.localPort,\n };\n const ready = (timeoutMs: number, signal: AbortSignal) =>\n pollUntil({\n check: async () =>\n (await api.readNamespacedPod({ name: podName, namespace: config.namespace })).status\n ?.phase === \"Running\",\n timeoutMs,\n intervalMs: pollIntervalMs,\n signal,\n timeoutMessage: `timed out waiting for pod ${podName} to reach Running`,\n });\n return buildHandle(api, payload, forward, ctx, ready, \"starting\");\n },\n\n async restore(payload, ctx) {\n const api = await makeApi(loadSdk);\n const pod = await api.readNamespacedPod({\n name: payload.podName,\n namespace: payload.namespace,\n });\n if (pod.status?.phase !== \"Running\") {\n throw new Error(\n `k8s pod ${payload.podName} is not running (phase=${pod.status?.phase ?? \"unknown\"})`,\n );\n }\n // The forward is a local child process that doesn't survive a restart, so\n // re-establish it. Prefer the previously bound port for a stable wsUrl.\n const forward = await portForward({\n serviceName: payload.serviceName,\n namespace: payload.namespace,\n targetPort: DEFAULT_TARGET_PORT,\n localPort: payload.localForwardPort,\n });\n ctx.log.info(\"re-attached to k8s pod\", {\n podName: payload.podName,\n localPort: forward.localPort,\n });\n const restored: K8sPayload = { ...payload, localForwardPort: forward.localPort };\n return buildHandle(api, restored, forward, ctx, async () => {}, \"ready\");\n },\n };\n}\n\n/** Default Kubernetes deploy target registered by `register()`. */\nexport const k8sDeployTarget = makeK8sDeployTarget();\n","/**\n * Port-forward seam for the k8s deploy target.\n *\n * The provider reaches a ClusterIP Service through `kubectl port-forward`. We\n * shell out to `kubectl` rather than driving `@kubernetes/client-node`'s\n * `PortForward` + a local `net.Server`: a child process is a single injectable\n * seam (mirroring the SDK loader) that tests can fake with no real socket, and\n * it reuses the operator's existing kubeconfig/auth without re-implementing the\n * stream plumbing. The spawn is injectable so the suite stays hermetic.\n */\n\nimport { spawn } from \"node:child_process\";\n\n/** A live port-forward tunnel. `localPort` is the port `kubectl` actually bound. */\nexport interface PortForward {\n readonly localPort: number;\n /** Idempotent: kill the tunnel; safe to call more than once. */\n stop(): void;\n}\n\n/** Inputs needed to establish a `svc/<name>` forward. */\nexport interface PortForwardSpec {\n serviceName: string;\n namespace: string;\n targetPort: number;\n /** Local port to bind; `0` (or omitted) lets the OS pick a free one. */\n localPort?: number;\n /** Cap on how long to wait for `kubectl` to report it is forwarding. */\n timeoutMs?: number;\n}\n\n/** Seam: establish a forward and resolve once it is actually serving locally. */\nexport type PortForwarder = (spec: PortForwardSpec) => Promise<PortForward>;\n\nconst DEFAULT_FORWARD_TIMEOUT_MS = 30_000;\n// kubectl prints e.g. \"Forwarding from 127.0.0.1:54321 -> 8080\" once bound.\nconst FORWARDING_RE = /Forwarding from (?:127\\.0\\.0\\.1|\\[::1\\]):(\\d+)/;\n\n/**\n * Default forwarder: spawn `kubectl port-forward svc/<name> <local>:<target>`\n * and resolve with the bound local port once kubectl reports it is forwarding.\n * Rejects (and kills the child) on early exit, timeout, or a missing binary.\n */\nexport const defaultPortForwarder: PortForwarder = (spec) => {\n const timeoutMs = spec.timeoutMs ?? DEFAULT_FORWARD_TIMEOUT_MS;\n const local = spec.localPort ?? 0;\n const child = spawn(\n \"kubectl\",\n [\n \"port-forward\",\n `svc/${spec.serviceName}`,\n `${local}:${spec.targetPort}`,\n \"-n\",\n spec.namespace,\n ],\n { stdio: [\"ignore\", \"pipe\", \"pipe\"] },\n );\n\n return new Promise<PortForward>((resolve, reject) => {\n let settled = false;\n let stderr = \"\";\n\n const cleanup = () => {\n clearTimeout(timer);\n child.stdout?.removeAllListeners();\n child.stderr?.removeAllListeners();\n };\n const stop = () => {\n try {\n child.kill();\n } catch {\n /* already gone */\n }\n };\n const fail = (err: Error) => {\n if (settled) return;\n settled = true;\n cleanup();\n stop();\n reject(err);\n };\n\n const timer = setTimeout(\n () => fail(new Error(`timed out waiting for kubectl port-forward to bind (${timeoutMs}ms)`)),\n timeoutMs,\n );\n\n child.stdout?.on(\"data\", (chunk: Buffer) => {\n const m = FORWARDING_RE.exec(chunk.toString());\n if (m && !settled) {\n settled = true;\n cleanup();\n resolve({ localPort: Number(m[1]), stop });\n }\n });\n child.stderr?.on(\"data\", (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n child.on(\"error\", (err) =>\n fail(new Error(`failed to spawn kubectl port-forward: ${err.message}`)),\n );\n child.on(\"exit\", (code) =>\n fail(\n new Error(\n `kubectl port-forward exited (code=${code ?? \"null\"}) before forwarding${stderr ? `: ${stderr.trim()}` : \"\"}`,\n ),\n ),\n );\n });\n};\n","/**\n * Minimal local interface for `@kubernetes/client-node` — only the members this\n * provider actually calls.\n *\n * The SDK is an *optional* peer dependency, lazy-loaded via a string-literal\n * dynamic `import()`. Typing against this minimal surface (rather than the real\n * package's types) keeps the package building + typechecking standalone\n * offline; the host project supplies the real SDK at runtime.\n */\n\n/** A `V1Pod`-ish object (subset we read). */\nexport interface K8sPod {\n metadata?: { name?: string; namespace?: string };\n status?: { phase?: string };\n}\n\n/** A `V1Service`-ish object (subset we read). */\nexport interface K8sService {\n metadata?: { name?: string; namespace?: string };\n}\n\n/** `CoreV1Api` subset. */\nexport interface CoreV1Api {\n createNamespacedPod(arg: { namespace: string; body: unknown }): Promise<K8sPod>;\n readNamespacedPod(arg: { name: string; namespace: string }): Promise<K8sPod>;\n deleteNamespacedPod(arg: { name: string; namespace: string }): Promise<unknown>;\n createNamespacedService(arg: { namespace: string; body: unknown }): Promise<K8sService>;\n deleteNamespacedService(arg: { name: string; namespace: string }): Promise<unknown>;\n}\n\n/** `KubeConfig` subset. */\nexport interface KubeConfig {\n loadFromDefault(): void;\n // biome-ignore lint/suspicious/noExplicitAny: api ctor passed through opaquely\n makeApiClient(api: any): CoreV1Api;\n}\n\n/** The `@kubernetes/client-node` module surface we depend on. */\nexport interface K8sModule {\n KubeConfig: new () => KubeConfig;\n CoreV1Api: unknown;\n}\n\n/** Loader seam: returns the SDK module. Default does a real dynamic import. */\nexport type SdkLoader = () => Promise<K8sModule>;\n\n/**\n * Default loader: lazy `import(\"@kubernetes/client-node\")`. The string literal\n * keeps bundlers from eagerly resolving the optional peer; only evaluated when\n * `create()`/`restore()` run.\n */\nexport const defaultSdkLoader: SdkLoader = async () => {\n const mod = (await import(\"@kubernetes/client-node\" as string)) as unknown as K8sModule;\n if (!mod?.KubeConfig) {\n throw new Error(\n \"@kubernetes/client-node is not installed. Run `skaile plugin install @skaile/provider-k8s` and add @kubernetes/client-node to your project.\",\n );\n }\n return mod;\n};\n","/**\n * `@skaile/provider-k8s` — Kubernetes deploy target for `@skaile/workspaces`.\n *\n * v0.1.0 ships ClusterIP + port-forward only. Requires the optional peer\n * `@kubernetes/client-node` to be installed in the host project (lazy-loaded at\n * runtime). Install via `skaile plugin install @skaile/provider-k8s`.\n */\n\nimport type { PluginRegistry } from \"@skaile/workspaces/plugin-registry\";\nimport { k8sDeployTarget } from \"./target.js\";\n\nexport type { PortForward, PortForwarder, PortForwardSpec } from \"./port-forward.js\";\nexport { defaultPortForwarder } from \"./port-forward.js\";\nexport type { CoreV1Api, K8sModule, KubeConfig, SdkLoader } from \"./sdk-types.js\";\nexport type { K8sTargetOptions } from \"./target.js\";\nexport { k8sDeployTarget, makeK8sDeployTarget } from \"./target.js\";\n\n/** Register this plugin's deploy target with a registry. */\nexport function register(registry: PluginRegistry): void {\n registry.register(\"deployTarget\", k8sDeployTarget);\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skaile/provider-k8s",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Kubernetes (ClusterIP + port-forward) deploy target for @skaile/workspaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"node": ">=20"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@skaile/workspaces": "
|
|
25
|
+
"@skaile/workspaces": "^6.0.0",
|
|
26
26
|
"@kubernetes/client-node": ">=0.20.0"
|
|
27
27
|
},
|
|
28
28
|
"peerDependenciesMeta": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@skaile/workspaces": "^0.
|
|
34
|
+
"@skaile/workspaces": "^6.0.0"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
37
|
"build": "tsup",
|