@compforge/harness-toolbox 0.1.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.
Files changed (92) hide show
  1. package/README.md +47 -0
  2. package/dist/chunk-0rs1aacd.js +127 -0
  3. package/dist/chunk-1at7zs0z.js +104 -0
  4. package/dist/chunk-1vfwvgkj.js +104 -0
  5. package/dist/chunk-205a4vma.js +85 -0
  6. package/dist/chunk-3dt25p98.js +59 -0
  7. package/dist/chunk-6fnhzhdb.js +103 -0
  8. package/dist/chunk-8nm9f0hh.js +92 -0
  9. package/dist/chunk-ajhdd4hp.js +267 -0
  10. package/dist/chunk-c6z919qb.js +98 -0
  11. package/dist/chunk-djk4kmxb.js +0 -0
  12. package/dist/chunk-hqx7j9xe.js +14 -0
  13. package/dist/chunk-hv2kx47y.js +92 -0
  14. package/dist/chunk-j46x7j7c.js +73 -0
  15. package/dist/chunk-jr9y0ghr.js +157 -0
  16. package/dist/chunk-mkhyty75.js +166 -0
  17. package/dist/chunk-mp88s7r7.js +12 -0
  18. package/dist/chunk-pfar9cm3.js +0 -0
  19. package/dist/chunk-qq77x9ek.js +34 -0
  20. package/dist/chunk-s06hs7a8.js +79 -0
  21. package/dist/chunk-s1sjyem7.js +41 -0
  22. package/dist/chunk-v3hxvnrc.js +47 -0
  23. package/dist/chunk-w3a4p6jm.js +124 -0
  24. package/dist/chunk-xy4g56g4.js +50 -0
  25. package/dist/chunk-ztenxgjv.js +27 -0
  26. package/dist/client-manager.d.ts +14 -0
  27. package/dist/client-manager.js +6 -0
  28. package/dist/client.d.ts +5 -0
  29. package/dist/client.js +0 -0
  30. package/dist/concurrency.d.ts +7 -0
  31. package/dist/concurrency.js +6 -0
  32. package/dist/datasource.d.ts +26 -0
  33. package/dist/datasource.js +10 -0
  34. package/dist/kubernetes/client-node-pod-log.d.ts +31 -0
  35. package/dist/kubernetes/client-node-pod-log.js +425 -0
  36. package/dist/kubernetes/client.d.ts +17 -0
  37. package/dist/kubernetes/client.js +92 -0
  38. package/dist/kubernetes/endpoint.d.ts +4 -0
  39. package/dist/kubernetes/endpoint.js +0 -0
  40. package/dist/kubernetes/executor.d.ts +45 -0
  41. package/dist/kubernetes/executor.js +13 -0
  42. package/dist/kubernetes/log-capture-file.d.ts +12 -0
  43. package/dist/kubernetes/log-capture-file.js +6 -0
  44. package/dist/kubernetes/log-capture-plan.d.ts +29 -0
  45. package/dist/kubernetes/log-capture-plan.js +9 -0
  46. package/dist/kubernetes/log-timestamp.d.ts +2 -0
  47. package/dist/kubernetes/log-timestamp.js +6 -0
  48. package/dist/kubernetes/pod-log-client.d.ts +25 -0
  49. package/dist/kubernetes/pod-log-client.js +12 -0
  50. package/dist/kubernetes/pod-log-datasource.d.ts +17 -0
  51. package/dist/kubernetes/pod-log-datasource.js +31 -0
  52. package/dist/kubernetes/pod-log.d.ts +52 -0
  53. package/dist/kubernetes/pod-log.js +121 -0
  54. package/dist/kubernetes/pod.d.ts +61 -0
  55. package/dist/kubernetes/pod.js +8 -0
  56. package/dist/kubernetes/port-forward.d.ts +42 -0
  57. package/dist/kubernetes/port-forward.js +10 -0
  58. package/dist/kubernetes/service-port-forward.d.ts +29 -0
  59. package/dist/kubernetes/service-port-forward.js +11 -0
  60. package/dist/kubernetes/service.d.ts +31 -0
  61. package/dist/kubernetes/service.js +17 -0
  62. package/dist/mysql/client.d.ts +36 -0
  63. package/dist/mysql/client.js +13 -0
  64. package/dist/mysql/index.d.ts +3 -0
  65. package/dist/mysql/index.js +18 -0
  66. package/dist/mysql/pod.d.ts +7 -0
  67. package/dist/mysql/pod.js +8 -0
  68. package/dist/mysql/target.d.ts +12 -0
  69. package/dist/mysql/target.js +6 -0
  70. package/dist/mysql/types.d.ts +16 -0
  71. package/dist/mysql/types.js +1 -0
  72. package/dist/opensearch/client.d.ts +62 -0
  73. package/dist/opensearch/client.js +12 -0
  74. package/dist/opensearch/endpoint.d.ts +16 -0
  75. package/dist/opensearch/endpoint.js +6 -0
  76. package/dist/opensearch/index.d.ts +3 -0
  77. package/dist/opensearch/index.js +17 -0
  78. package/dist/opensearch/types.d.ts +8 -0
  79. package/dist/opensearch/types.js +1 -0
  80. package/dist/process/index.d.ts +13 -0
  81. package/dist/process/index.js +8 -0
  82. package/dist/redis/client.d.ts +90 -0
  83. package/dist/redis/client.js +14 -0
  84. package/dist/redis/index.d.ts +3 -0
  85. package/dist/redis/index.js +24 -0
  86. package/dist/redis/key-stats.d.ts +27 -0
  87. package/dist/redis/key-stats.js +8 -0
  88. package/dist/redis/topology.d.ts +23 -0
  89. package/dist/redis/topology.js +6 -0
  90. package/dist/transport/index.d.ts +40 -0
  91. package/dist/transport/index.js +12 -0
  92. package/package.json +96 -0
@@ -0,0 +1,50 @@
1
+ // src/transport/index.ts
2
+ class DirectTransport {
3
+ kind = "tcp";
4
+ name = "direct";
5
+ async connect(endpoint) {
6
+ return endpoint;
7
+ }
8
+ }
9
+
10
+ class PortForwardTransport {
11
+ forward;
12
+ kind = "tcp";
13
+ name = "port-forward";
14
+ constructor(forward) {
15
+ this.forward = forward;
16
+ }
17
+ connect(endpoint) {
18
+ return this.forward(endpoint);
19
+ }
20
+ }
21
+
22
+ class PodPythonTransport {
23
+ exec;
24
+ python;
25
+ kind = "python";
26
+ name = "pod-python";
27
+ constructor(exec, python = "python") {
28
+ this.exec = exec;
29
+ this.python = python;
30
+ }
31
+ run(script, input, timeoutMs) {
32
+ return this.exec([this.python, "-c", script], { stdin: JSON.stringify(input), timeoutMs });
33
+ }
34
+ }
35
+ function isConnectionNetworkError(error) {
36
+ if (!error || typeof error !== "object" || !("code" in error))
37
+ return false;
38
+ return [
39
+ "ECONNREFUSED",
40
+ "ETIMEDOUT",
41
+ "ECONNRESET",
42
+ "EHOSTUNREACH",
43
+ "ENETUNREACH",
44
+ "EADDRNOTAVAIL",
45
+ "ENOTFOUND",
46
+ "EAI_AGAIN"
47
+ ].includes(String(error.code));
48
+ }
49
+
50
+ export { DirectTransport, PortForwardTransport, PodPythonTransport, isConnectionNetworkError };
@@ -0,0 +1,27 @@
1
+ // src/process/index.ts
2
+ import { spawn } from "node:child_process";
3
+ import { Readable } from "node:stream";
4
+ function spawnProcess(argv, opts) {
5
+ if (!argv.length)
6
+ throw new Error("command argv cannot be empty");
7
+ const child = spawn(argv[0], argv.slice(1), {
8
+ stdio: "pipe",
9
+ env: opts?.env
10
+ });
11
+ child.stdin.end(opts?.stdin);
12
+ const exited = new Promise((resolve, reject) => {
13
+ child.once("error", reject);
14
+ child.once("close", (code) => resolve(code ?? 1));
15
+ });
16
+ return {
17
+ stdout: Readable.toWeb(child.stdout),
18
+ stderr: Readable.toWeb(child.stderr),
19
+ exited,
20
+ kill: () => child.kill()
21
+ };
22
+ }
23
+ function sleep(ms) {
24
+ return new Promise((resolve) => setTimeout(resolve, ms));
25
+ }
26
+
27
+ export { spawnProcess, sleep };
@@ -0,0 +1,14 @@
1
+ import type { Client } from "./client";
2
+ import type { DataSource } from "./datasource";
3
+ /** Consumers borrow clients; only the root owner receives the disposal capability. */
4
+ export interface ClientProvider {
5
+ get<C extends Client>(source: DataSource<C>): Promise<C>;
6
+ }
7
+ /** @spec One root execution shares successful initialization per identity and closes clients at finalize. */
8
+ export declare class ClientManager implements ClientProvider {
9
+ #private;
10
+ readonly signal: AbortSignal;
11
+ constructor(signal?: AbortSignal);
12
+ get<C extends Client>(source: DataSource<C>): Promise<C>;
13
+ dispose(): Promise<void>;
14
+ }
@@ -0,0 +1,6 @@
1
+ import {
2
+ ClientManager
3
+ } from "./chunk-3dt25p98.js";
4
+ export {
5
+ ClientManager
6
+ };
@@ -0,0 +1,5 @@
1
+ /** A protocol client owns its initialization and idempotent cleanup, including partial initialization. */
2
+ export interface Client {
3
+ initialize(): Promise<void>;
4
+ dispose(): Promise<void>;
5
+ }
package/dist/client.js ADDED
File without changes
@@ -0,0 +1,7 @@
1
+ /** FIFO slots shared by callers; queued cancellation never starts external work. */
2
+ export declare class ConcurrencyPool {
3
+ #private;
4
+ readonly concurrency: number;
5
+ constructor(concurrency: number);
6
+ run<T>(work: () => Promise<T>, signal?: AbortSignal): Promise<T>;
7
+ }
@@ -0,0 +1,6 @@
1
+ import {
2
+ ConcurrencyPool
3
+ } from "./chunk-v3hxvnrc.js";
4
+ export {
5
+ ConcurrencyPool
6
+ };
@@ -0,0 +1,26 @@
1
+ export type { Client } from "./client";
2
+ export { ClientManager, type ClientProvider } from "./client-manager";
3
+ import type { Client } from "./client";
4
+ import type { Transport } from "./transport";
5
+ /** Identity must include protocol, target, configuration and credentials; never an object address. */
6
+ export interface DataSource<C extends Client> {
7
+ readonly key: string;
8
+ /** Construct only; Client.initialize owns external work. */
9
+ createClient(signal: AbortSignal): C;
10
+ }
11
+ /** Resolution owns configuration semantics; transports preserve the resolved target and identity. */
12
+ export interface ConnectionSource<Target> {
13
+ resolve(): Promise<Target>;
14
+ readonly transports: readonly Transport[];
15
+ }
16
+ export interface ClientLifecycle {
17
+ signal?: AbortSignal;
18
+ onDispose?: (dispose: () => Promise<void>) => void;
19
+ /** Safe route facts only: no credentials, SQL or protocol payloads. */
20
+ onRoute?: (route: {
21
+ transport: string;
22
+ reason?: string;
23
+ }) => void;
24
+ }
25
+ /** Stable in-memory identity; connection credentials must not appear in observable cache keys. */
26
+ export declare function dataSourceKey(protocol: string, configuration: unknown): string;
@@ -0,0 +1,10 @@
1
+ import {
2
+ dataSourceKey
3
+ } from "./chunk-hqx7j9xe.js";
4
+ import {
5
+ ClientManager
6
+ } from "./chunk-3dt25p98.js";
7
+ export {
8
+ dataSourceKey,
9
+ ClientManager
10
+ };
@@ -0,0 +1,31 @@
1
+ import type { RequestInfo, RequestInit, Response } from "node-fetch";
2
+ import type { ExecResult } from "./executor";
3
+ import type { KubernetesPodLogAccess, PodLogRequest, PodLogResult, ServicePodListResult } from "./pod-log";
4
+ export interface ClientNodeLogPolicy {
5
+ idleTimeoutMs: number;
6
+ hardTimeoutMs: number;
7
+ maxAttempts: number;
8
+ }
9
+ export type ClientNodeFetch = (url: RequestInfo, init?: RequestInit) => Promise<Response>;
10
+ export declare const DEFAULT_CLIENT_NODE_LOG_POLICY: ClientNodeLogPolicy;
11
+ export interface ClientNodePodLogOptions {
12
+ namespace: string;
13
+ signal?: AbortSignal;
14
+ kubeconfig?: string;
15
+ context?: string;
16
+ policy?: Partial<ClientNodeLogPolicy>;
17
+ /** Transport seam for deterministic tests; production uses node-fetch. */
18
+ fetchImpl?: ClientNodeFetch;
19
+ }
20
+ /**
21
+ * 迁移期组合:Service/Pod 发现仍走既有 access,只有高体量 Pod Log transport 改为 API 流。
22
+ * 调用侧不需要知道两种 transport;后续发现层 API 化时可直接替换 delegate。
23
+ */
24
+ export declare class ClientNodePodLogAccess implements KubernetesPodLogAccess {
25
+ private readonly discovery;
26
+ private readonly transport;
27
+ constructor(discovery: KubernetesPodLogAccess, options: ClientNodePodLogOptions);
28
+ clientVersion(): Promise<ExecResult>;
29
+ listServicePods(services: readonly string[]): Promise<ServicePodListResult>;
30
+ collectPodLogs(request: PodLogRequest): Promise<PodLogResult>;
31
+ }
@@ -0,0 +1,425 @@
1
+ import {
2
+ logTimestampNanos
3
+ } from "../chunk-mp88s7r7.js";
4
+
5
+ // src/kubernetes/client-node-pod-log.ts
6
+ import { closeSync, openSync, writeSync } from "node:fs";
7
+ import { KubeConfig } from "@kubernetes/client-node";
8
+ import fetch from "node-fetch/lib/index.js";
9
+ var DEFAULT_CLIENT_NODE_LOG_POLICY = {
10
+ idleTimeoutMs: 15000,
11
+ hardTimeoutMs: 120000,
12
+ maxAttempts: 2
13
+ };
14
+ var RFC3339_LINE = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2}))(?:\s|$)/;
15
+ var RECENT_LINE_LIMIT = 256;
16
+ function parseSinceSeconds(value) {
17
+ const input = value.trim();
18
+ if (!input)
19
+ return;
20
+ const units = {
21
+ ns: 0.000000001,
22
+ us: 0.000001,
23
+ "µs": 0.000001,
24
+ ms: 0.001,
25
+ s: 1,
26
+ m: 60,
27
+ h: 3600
28
+ };
29
+ let seconds = 0;
30
+ let cursor = 0;
31
+ const segment = /(\d+(?:\.\d+)?)(ns|us|µs|ms|s|m|h)/gy;
32
+ while (cursor < input.length) {
33
+ segment.lastIndex = cursor;
34
+ const match = segment.exec(input);
35
+ if (!match)
36
+ return;
37
+ seconds += Number(match[1]) * units[match[2]];
38
+ cursor = segment.lastIndex;
39
+ }
40
+ return Math.max(1, Math.ceil(seconds));
41
+ }
42
+ function errorMessage(error) {
43
+ return error instanceof Error ? error.message : String(error);
44
+ }
45
+ function logCommand(namespace, request) {
46
+ const command = [
47
+ "kubernetes-api",
48
+ "logs",
49
+ "-n",
50
+ namespace,
51
+ request.pod
52
+ ];
53
+ if (request.container)
54
+ command.push("-c", request.container);
55
+ if (request.previous)
56
+ command.push("--previous");
57
+ if (request.tail !== undefined)
58
+ command.push(`--tail=${request.tail}`);
59
+ if (request.limitBytes !== undefined)
60
+ command.push(`--limit-bytes=${request.limitBytes}`);
61
+ if (request.sinceTime)
62
+ command.push(`--since-time=${request.sinceTime}`);
63
+ else if (request.since)
64
+ command.push(`--since=${request.since}`);
65
+ if (request.untilTime)
66
+ command.push(`--until-time=${request.untilTime}`);
67
+ return command;
68
+ }
69
+
70
+ class ClientNodeLogTransport {
71
+ options;
72
+ kubeConfig = new KubeConfig;
73
+ policy;
74
+ initializationError;
75
+ constructor(options) {
76
+ this.options = options;
77
+ this.policy = { ...DEFAULT_CLIENT_NODE_LOG_POLICY, ...options.policy };
78
+ try {
79
+ if (this.policy.idleTimeoutMs < 1 || this.policy.hardTimeoutMs < 1) {
80
+ throw new Error("Pod Log timeout 必须为正数");
81
+ }
82
+ if (!Number.isInteger(this.policy.maxAttempts) || this.policy.maxAttempts < 1) {
83
+ throw new Error("Pod Log maxAttempts 必须是正整数");
84
+ }
85
+ if (options.kubeconfig)
86
+ this.kubeConfig.loadFromFile(options.kubeconfig);
87
+ else
88
+ this.kubeConfig.loadFromDefault();
89
+ if (options.context)
90
+ this.kubeConfig.setCurrentContext(options.context);
91
+ const cluster = this.kubeConfig.getCurrentCluster();
92
+ if (!cluster) {
93
+ throw new Error(`kubeconfig context '${this.kubeConfig.getCurrentContext()}' 没有关联 cluster`);
94
+ }
95
+ if (new URL(cluster.server).protocol === "http:") {
96
+ this.kubeConfig.clusters = this.kubeConfig.clusters.map((item) => item === cluster ? { ...item, skipTLSVerify: true } : item);
97
+ }
98
+ } catch (error) {
99
+ this.initializationError = errorMessage(error);
100
+ }
101
+ }
102
+ async capture(request) {
103
+ this.options.signal?.throwIfAborted();
104
+ const command = logCommand(this.options.namespace, request);
105
+ const startedAt = Date.now();
106
+ if (this.initializationError) {
107
+ return this.unavailable(command, startedAt, `kubeconfig 初始化失败:${this.initializationError}`);
108
+ }
109
+ if (!request.container) {
110
+ return this.unavailable(command, startedAt, "Kubernetes Pod Log API 要求指定 container");
111
+ }
112
+ const sinceSeconds = request.since && !request.sinceTime ? parseSinceSeconds(request.since) : undefined;
113
+ if (request.since && !request.sinceTime && sinceSeconds === undefined) {
114
+ return this.unavailable(command, startedAt, `无法解析日志时间窗口:${request.since}`);
115
+ }
116
+ const untilNanos = request.untilTime === undefined ? undefined : logTimestampNanos(request.untilTime);
117
+ if (request.untilTime !== undefined && untilNanos === undefined) {
118
+ return this.unavailable(command, startedAt, "untilTime 必须是 RFC3339 时间戳");
119
+ }
120
+ const fd = request.rawFilePath ? openSync(request.rawFilePath, "w", 384) : undefined;
121
+ const output = [];
122
+ let rawChunks = [];
123
+ let rawBytes = 0;
124
+ const flushRaw = () => {
125
+ if (fd !== undefined && rawChunks.length)
126
+ writeSync(fd, rawChunks.join(""));
127
+ rawChunks = [];
128
+ rawBytes = 0;
129
+ };
130
+ const recentLines = [];
131
+ const recentLineSet = new Set;
132
+ const errors = [];
133
+ let attempts = 0;
134
+ let totalBytesRead = 0;
135
+ let resumeSinceTime = request.sinceTime;
136
+ let timedOut = false;
137
+ let finalStatusCode;
138
+ const hardDeadline = startedAt + this.policy.hardTimeoutMs;
139
+ const emitLine = (line, deduplicate) => {
140
+ if (deduplicate && recentLineSet.has(line))
141
+ return;
142
+ const prefix = request.prefix ? `[pod/${request.pod}/${request.container}] ` : "";
143
+ const rendered = `${prefix}${line}`;
144
+ request.onLine?.(rendered);
145
+ if (fd !== undefined) {
146
+ const chunk = `${rendered}
147
+ `;
148
+ rawChunks.push(chunk);
149
+ rawBytes += Buffer.byteLength(chunk);
150
+ if (rawBytes >= 64 * 1024)
151
+ flushRaw();
152
+ } else if (request.collectStdout !== false)
153
+ output.push(rendered);
154
+ recentLines.push(line);
155
+ recentLineSet.add(line);
156
+ if (recentLines.length > RECENT_LINE_LIMIT) {
157
+ recentLineSet.delete(recentLines.shift());
158
+ }
159
+ };
160
+ try {
161
+ while (attempts < this.policy.maxAttempts) {
162
+ if (this.options.signal?.aborted)
163
+ break;
164
+ const remainingHardTimeoutMs = hardDeadline - Date.now();
165
+ if (remainingHardTimeoutMs <= 0) {
166
+ timedOut = true;
167
+ errors.push(`hard_timeout(hard=${this.policy.hardTimeoutMs}ms)`);
168
+ break;
169
+ }
170
+ const remainingBytes = request.limitBytes === undefined ? undefined : Math.max(0, request.limitBytes - totalBytesRead);
171
+ if (remainingBytes === 0)
172
+ break;
173
+ attempts += 1;
174
+ const attempt = await this.captureAttempt({
175
+ request: remainingBytes === undefined ? request : { ...request, limitBytes: remainingBytes },
176
+ sinceSeconds,
177
+ untilNanos,
178
+ sinceTime: resumeSinceTime,
179
+ hardTimeoutMs: remainingHardTimeoutMs,
180
+ deduplicate: attempts > 1,
181
+ emitLine
182
+ });
183
+ totalBytesRead += attempt.bytesRead;
184
+ timedOut ||= attempt.timedOut;
185
+ finalStatusCode = attempt.statusCode;
186
+ if (attempt.lastTimestamp)
187
+ resumeSinceTime = attempt.lastTimestamp;
188
+ if (attempt.ok) {
189
+ const limited = !attempt.windowComplete && request.limitBytes !== undefined && totalBytesRead >= request.limitBytes;
190
+ return {
191
+ ok: !limited,
192
+ exitCode: limited ? null : 0,
193
+ stdout: output.length ? `${output.join(`
194
+ `)}
195
+ ` : "",
196
+ stderr: limited ? `日志达到单次字节上限 ${request.limitBytes}` : "",
197
+ durationMs: Date.now() - startedAt,
198
+ timedOut,
199
+ command,
200
+ captureStatus: limited ? "partial" : "complete",
201
+ reason: limited ? "byte_limit" : undefined,
202
+ bytesRead: totalBytesRead,
203
+ attempts
204
+ };
205
+ }
206
+ errors.push(attempt.error ?? "Kubernetes Pod Log API 请求失败");
207
+ if (this.options.signal?.aborted || !attempt.retryable || attempts >= this.policy.maxAttempts)
208
+ break;
209
+ }
210
+ } finally {
211
+ try {
212
+ flushRaw();
213
+ } finally {
214
+ if (fd !== undefined)
215
+ closeSync(fd);
216
+ }
217
+ }
218
+ const captureStatus = totalBytesRead > 0 ? "partial" : "unavailable";
219
+ const byteLimitReached = request.limitBytes !== undefined && totalBytesRead >= request.limitBytes;
220
+ if (byteLimitReached)
221
+ errors.push(`日志达到单次字节上限 ${request.limitBytes}`);
222
+ return {
223
+ ok: false,
224
+ exitCode: finalStatusCode ?? null,
225
+ stdout: output.length ? `${output.join(`
226
+ `)}
227
+ ` : "",
228
+ stderr: errors.join(`
229
+ `),
230
+ durationMs: Date.now() - startedAt,
231
+ timedOut,
232
+ command,
233
+ captureStatus,
234
+ reason: this.options.signal?.aborted ? "cancelled" : byteLimitReached ? "byte_limit" : timedOut ? "timeout" : "transport_error",
235
+ bytesRead: totalBytesRead,
236
+ attempts
237
+ };
238
+ }
239
+ unavailable(command, startedAt, reason) {
240
+ return {
241
+ ok: false,
242
+ exitCode: null,
243
+ stdout: "",
244
+ stderr: reason,
245
+ durationMs: Date.now() - startedAt,
246
+ timedOut: false,
247
+ command,
248
+ captureStatus: "unavailable",
249
+ reason,
250
+ bytesRead: 0,
251
+ attempts: 0
252
+ };
253
+ }
254
+ async captureAttempt(input) {
255
+ const cluster = this.kubeConfig.getCurrentCluster();
256
+ const path = `/api/v1/namespaces/${encodeURIComponent(this.options.namespace)}` + `/pods/${encodeURIComponent(input.request.pod)}/log`;
257
+ const url = new URL(path, cluster.server.endsWith("/") ? cluster.server : `${cluster.server}/`);
258
+ url.searchParams.set("container", input.request.container);
259
+ url.searchParams.set("follow", "false");
260
+ url.searchParams.set("timestamps", "true");
261
+ if (input.request.previous)
262
+ url.searchParams.set("previous", "true");
263
+ if (input.request.tail !== undefined)
264
+ url.searchParams.set("tailLines", String(input.request.tail));
265
+ if (input.request.limitBytes !== undefined) {
266
+ url.searchParams.set("limitBytes", String(input.request.limitBytes));
267
+ }
268
+ if (input.sinceTime)
269
+ url.searchParams.set("sinceTime", input.sinceTime);
270
+ else if (input.sinceSeconds !== undefined) {
271
+ url.searchParams.set("sinceSeconds", String(input.sinceSeconds));
272
+ }
273
+ const controller = new AbortController;
274
+ const commandSignal = this.options.signal;
275
+ const abort = () => controller.abort(commandSignal?.reason);
276
+ commandSignal?.addEventListener("abort", abort, { once: true });
277
+ if (commandSignal?.aborted)
278
+ abort();
279
+ let abortReason;
280
+ let idleTimer;
281
+ const resetIdleTimer = () => {
282
+ clearTimeout(idleTimer);
283
+ idleTimer = setTimeout(() => {
284
+ abortReason = "idle_timeout";
285
+ controller.abort();
286
+ }, this.policy.idleTimeoutMs);
287
+ };
288
+ resetIdleTimer();
289
+ const hardTimer = setTimeout(() => {
290
+ abortReason = "hard_timeout";
291
+ controller.abort();
292
+ }, input.hardTimeoutMs);
293
+ const decoder = new TextDecoder;
294
+ let pending = "";
295
+ let bytesRead = 0;
296
+ let lastTimestamp;
297
+ let windowComplete = false;
298
+ const emit = (line) => {
299
+ const timestamp = line.match(RFC3339_LINE)?.[1];
300
+ const nanos = timestamp && input.untilNanos !== undefined ? logTimestampNanos(timestamp) : undefined;
301
+ if (nanos !== undefined && input.untilNanos !== undefined && nanos > input.untilNanos) {
302
+ windowComplete = true;
303
+ return;
304
+ }
305
+ if (timestamp)
306
+ lastTimestamp = timestamp;
307
+ input.emitLine(line, input.deduplicate);
308
+ };
309
+ const consume = (text) => {
310
+ pending += text;
311
+ const lines = pending.split(/\r?\n/);
312
+ pending = lines.pop() ?? "";
313
+ for (const line of lines) {
314
+ emit(line);
315
+ if (windowComplete)
316
+ break;
317
+ }
318
+ };
319
+ try {
320
+ const init = await this.kubeConfig.applyToFetchOptions({});
321
+ const response = await (this.options.fetchImpl ?? fetch)(url, {
322
+ ...init,
323
+ method: "GET",
324
+ signal: controller.signal
325
+ });
326
+ if (!response.ok) {
327
+ const body = (await response.text()).trim().slice(0, 2000);
328
+ return {
329
+ ok: false,
330
+ retryable: response.status === 408 || response.status === 429 || response.status >= 500,
331
+ timedOut: false,
332
+ error: `Kubernetes Pod Log API HTTP ${response.status}${body ? `: ${body}` : ""}`,
333
+ statusCode: response.status,
334
+ bytesRead
335
+ };
336
+ }
337
+ if (!response.body) {
338
+ return {
339
+ ok: false,
340
+ retryable: true,
341
+ timedOut: false,
342
+ error: "Kubernetes Pod Log API 返回空响应流",
343
+ statusCode: response.status,
344
+ bytesRead
345
+ };
346
+ }
347
+ response.body.on("error", () => {
348
+ return;
349
+ });
350
+ for await (const raw of response.body) {
351
+ const chunk = raw instanceof Uint8Array ? raw : Buffer.from(raw);
352
+ bytesRead += chunk.byteLength;
353
+ resetIdleTimer();
354
+ consume(decoder.decode(chunk, { stream: true }));
355
+ if (windowComplete) {
356
+ controller.abort();
357
+ return { ok: true, retryable: false, timedOut: false, bytesRead, lastTimestamp, windowComplete: true };
358
+ }
359
+ }
360
+ commandSignal?.throwIfAborted();
361
+ consume(decoder.decode());
362
+ if (pending && !windowComplete)
363
+ emit(pending);
364
+ if (abortReason) {
365
+ return {
366
+ ok: false,
367
+ retryable: true,
368
+ timedOut: true,
369
+ error: `${abortReason}(idle=${this.policy.idleTimeoutMs}ms, hard=${this.policy.hardTimeoutMs}ms)`,
370
+ statusCode: response.status,
371
+ bytesRead,
372
+ lastTimestamp
373
+ };
374
+ }
375
+ return {
376
+ ok: true,
377
+ retryable: false,
378
+ timedOut: false,
379
+ statusCode: response.status,
380
+ bytesRead,
381
+ lastTimestamp,
382
+ windowComplete
383
+ };
384
+ } catch (error) {
385
+ if (windowComplete && !commandSignal?.aborted) {
386
+ return { ok: true, retryable: false, timedOut: false, bytesRead, lastTimestamp, windowComplete: true };
387
+ }
388
+ const timedOut = abortReason !== undefined;
389
+ return {
390
+ ok: false,
391
+ retryable: true,
392
+ timedOut,
393
+ error: timedOut ? `${abortReason}(idle=${this.policy.idleTimeoutMs}ms, hard=${this.policy.hardTimeoutMs}ms)` : errorMessage(error),
394
+ bytesRead,
395
+ lastTimestamp
396
+ };
397
+ } finally {
398
+ commandSignal?.removeEventListener("abort", abort);
399
+ clearTimeout(idleTimer);
400
+ clearTimeout(hardTimer);
401
+ }
402
+ }
403
+ }
404
+
405
+ class ClientNodePodLogAccess {
406
+ discovery;
407
+ transport;
408
+ constructor(discovery, options) {
409
+ this.discovery = discovery;
410
+ this.transport = new ClientNodeLogTransport(options);
411
+ }
412
+ clientVersion() {
413
+ return this.discovery.clientVersion();
414
+ }
415
+ listServicePods(services) {
416
+ return this.discovery.listServicePods(services);
417
+ }
418
+ collectPodLogs(request) {
419
+ return this.transport.capture(request);
420
+ }
421
+ }
422
+ export {
423
+ DEFAULT_CLIENT_NODE_LOG_POLICY,
424
+ ClientNodePodLogAccess
425
+ };
@@ -0,0 +1,17 @@
1
+ import type { Client } from "../client";
2
+ import { type Executor, type KubectlOptions, type ExecTarget, type RunOptions } from "./executor";
3
+ import type { KubernetesEndpoint } from "./endpoint";
4
+ /** Owns cluster transports; protocol callers borrow this client without closing it. */
5
+ export declare class KubernetesClient implements Client {
6
+ #private;
7
+ private readonly kube;
8
+ readonly signal: AbortSignal;
9
+ constructor(kube: KubectlOptions & {
10
+ namespace: string;
11
+ }, signal?: AbortSignal, executor?: Executor);
12
+ initialize(): Promise<void>;
13
+ run(namespace: string, command: string[], options?: RunOptions): Promise<import("./executor").ExecResult>;
14
+ exec(namespace: string, target: ExecTarget, command: string[], options?: RunOptions): Promise<import("./executor").ExecResult>;
15
+ forward(namespace: string, target: KubernetesEndpoint): Promise<import("./service-port-forward").ForwardedEndpoint>;
16
+ dispose(): Promise<void>;
17
+ }