@compforge/harness-toolbox 0.4.4 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/{chunk-2171tajs.js → chunk-1ts7pswj.js} +4 -3
- package/dist/chunk-835fssgz.js +37 -0
- package/dist/{chunk-mn6dwsvh.js → chunk-9shz0bw1.js} +3 -1
- package/dist/{chunk-0s27bgga.js → chunk-ae84rzq5.js} +1 -1
- package/dist/{chunk-age65q4n.js → chunk-db4an9td.js} +1 -1
- package/dist/{chunk-ftkrjqkc.js → chunk-dpcge1xt.js} +1 -1
- package/dist/{chunk-grpygk6f.js → chunk-dx6yvj81.js} +2 -2
- package/dist/{chunk-sqbew04a.js → chunk-f3vq900j.js} +2 -2
- package/dist/{chunk-yp6tcw22.js → chunk-f98qrpx1.js} +38 -78
- package/dist/{chunk-wv4jwhvj.js → chunk-fqrknvr8.js} +2 -2
- package/dist/chunk-gm4swndx.js +106 -0
- package/dist/{chunk-c2n8zvyn.js → chunk-m391jmpw.js} +8 -8
- package/dist/chunk-m4d29vfd.js +236 -0
- package/dist/{chunk-631vce5z.js → chunk-nftzckdy.js} +57 -7
- package/dist/{chunk-shsabpc1.js → chunk-r3rnsvwv.js} +1 -1
- package/dist/{chunk-20kv4wc3.js → chunk-rkt41za2.js} +7 -1
- package/dist/{chunk-hmah9t8e.js → chunk-rq5fc007.js} +1 -1
- package/dist/{chunk-h73h2b7y.js → chunk-t4hrnaj0.js} +44 -39
- package/dist/{chunk-kkts16w9.js → chunk-ww11q58v.js} +1 -1
- package/dist/{chunk-4ms037k2.js → chunk-x0yhfg29.js} +2 -2
- package/dist/{chunk-j46x7j7c.js → chunk-xgnz10sr.js} +5 -3
- package/dist/{chunk-5zgz1z41.js → chunk-z15gwjvg.js} +1 -1
- package/dist/connection/acquire.d.ts +34 -0
- package/dist/connection/acquire.js +9 -0
- package/dist/datasource.js +1 -1
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +3 -1
- package/dist/file/client.js +2 -2
- package/dist/file/index.js +4 -4
- package/dist/file/local.js +3 -3
- package/dist/file/pod.js +3 -3
- package/dist/kubernetes/client.js +11 -11
- package/dist/kubernetes/compute-quantity.js +2 -2
- package/dist/kubernetes/compute.js +2 -2
- package/dist/kubernetes/environment.js +11 -11
- package/dist/kubernetes/exec-transport.js +2 -2
- package/dist/kubernetes/executor.js +2 -2
- package/dist/kubernetes/pod-log-datasource.js +1 -1
- package/dist/kubernetes/pod-log.js +1 -1
- package/dist/kubernetes/port-forward.d.ts +5 -0
- package/dist/kubernetes/port-forward.js +3 -3
- package/dist/kubernetes/resource-transport.js +3 -3
- package/dist/kubernetes/resources.js +2 -2
- package/dist/kubernetes/service-port-forward.d.ts +2 -0
- package/dist/kubernetes/service-port-forward.js +5 -5
- package/dist/kubernetes/service.d.ts +1 -1
- package/dist/kubernetes/service.js +1 -1
- package/dist/kubernetes/workload.js +2 -2
- package/dist/mysql/client.d.ts +5 -2
- package/dist/mysql/client.js +9 -8
- package/dist/mysql/connection.d.ts +3 -0
- package/dist/mysql/connection.js +4 -2
- package/dist/mysql/index.js +10 -9
- package/dist/mysql/readonly.js +2 -2
- package/dist/opensearch/client.d.ts +14 -3
- package/dist/opensearch/client.js +9 -6
- package/dist/opensearch/index.js +9 -6
- package/dist/process/index.d.ts +1 -1
- package/dist/process/index.js +1 -1
- package/dist/redis/client.js +7 -6
- package/dist/redis/index.js +11 -10
- package/dist/s3/datasource.js +5 -5
- package/dist/s3/index.js +5 -5
- package/dist/transport/index.js +4 -4
- package/dist/transport/pod-relay.js +4 -4
- package/package.json +1 -4
- package/dist/chunk-136egyff.js +0 -11
- package/dist/chunk-8mb6natq.js +0 -178
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import {
|
|
2
|
+
acquireConnection,
|
|
3
|
+
awaitWithSignal
|
|
4
|
+
} from "./chunk-gm4swndx.js";
|
|
5
|
+
import {
|
|
6
|
+
TimeoutError,
|
|
7
|
+
ToolboxError
|
|
8
|
+
} from "./chunk-rkt41za2.js";
|
|
9
|
+
import {
|
|
10
|
+
isConnectionNetworkError
|
|
11
|
+
} from "./chunk-h8fwx0k7.js";
|
|
12
|
+
|
|
13
|
+
// src/opensearch/client.ts
|
|
14
|
+
import { Client, Connection, errors } from "@opensearch-project/opensearch";
|
|
15
|
+
function describeOpenSearchError(error) {
|
|
16
|
+
const parts = [];
|
|
17
|
+
const seen = new Set;
|
|
18
|
+
while (error && typeof error === "object" && !seen.has(error) && parts.length < 4) {
|
|
19
|
+
seen.add(error);
|
|
20
|
+
const value = error;
|
|
21
|
+
const safe = (value2) => typeof value2 === "number" ? String(value2) : typeof value2 === "string" && /^[a-zA-Z0-9_.-]{1,80}$/.test(value2) ? value2 : undefined;
|
|
22
|
+
parts.push([
|
|
23
|
+
`error=${safe(value.name) ?? "Error"}`,
|
|
24
|
+
safe(value.code) && `code=${safe(value.code)}`,
|
|
25
|
+
safe(value.kind) && `kind=${safe(value.kind)}`,
|
|
26
|
+
safe(value.statusCode ?? value.meta?.statusCode) && `status=${safe(value.statusCode ?? value.meta?.statusCode)}`
|
|
27
|
+
].filter(Boolean).join(" "));
|
|
28
|
+
error = value.cause;
|
|
29
|
+
}
|
|
30
|
+
return parts.join(" cause=") || "error=UnknownError";
|
|
31
|
+
}
|
|
32
|
+
function isOpenSearchReadApi(search) {
|
|
33
|
+
return typeof search.request === "function";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
class OpenSearchConnection extends Connection {
|
|
37
|
+
buildRequestObject(params) {
|
|
38
|
+
return { ...super.buildRequestObject(params), servername: this.ssl?.servername };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function createClient(options) {
|
|
42
|
+
return new Client({
|
|
43
|
+
node: options.node,
|
|
44
|
+
Connection: OpenSearchConnection,
|
|
45
|
+
auth: options.auth.username && options.auth.password ? { username: options.auth.username, password: options.auth.password } : undefined,
|
|
46
|
+
ssl: { rejectUnauthorized: false, servername: options.servername },
|
|
47
|
+
requestTimeout: options.requestTimeoutMs ?? 60000,
|
|
48
|
+
maxRetries: 0,
|
|
49
|
+
agent: { keepAlive: !process.versions.bun }
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
class OpenSearchEngine {
|
|
54
|
+
options;
|
|
55
|
+
client;
|
|
56
|
+
constructor(options, client) {
|
|
57
|
+
this.options = options;
|
|
58
|
+
this.client = client ?? createClient(options);
|
|
59
|
+
}
|
|
60
|
+
async count(index, query) {
|
|
61
|
+
const response = await this.client.count({ index, body: { query } });
|
|
62
|
+
return Number(response.body.count ?? 0);
|
|
63
|
+
}
|
|
64
|
+
async search(index, body) {
|
|
65
|
+
const response = await this.client.search({ index, body });
|
|
66
|
+
return response.body;
|
|
67
|
+
}
|
|
68
|
+
async request(path, query) {
|
|
69
|
+
if (!this.client.transport)
|
|
70
|
+
throw new Error("OpenSearch client 不支持通用只读请求");
|
|
71
|
+
const response = await this.client.transport.request({
|
|
72
|
+
method: "GET",
|
|
73
|
+
path,
|
|
74
|
+
querystring: query
|
|
75
|
+
});
|
|
76
|
+
return response.body;
|
|
77
|
+
}
|
|
78
|
+
async ping(signal, timeoutMs = this.options.requestTimeoutMs ?? 60000) {
|
|
79
|
+
signal?.throwIfAborted();
|
|
80
|
+
const started = Date.now();
|
|
81
|
+
const url = new URL(this.options.node);
|
|
82
|
+
const target = `${url.protocol}//${url.host}`;
|
|
83
|
+
const log = this.options.onDiagnostic ?? (() => {});
|
|
84
|
+
log(`OpenSearch probe start target=${target} timeout_ms=${timeoutMs}`);
|
|
85
|
+
const controller = new AbortController;
|
|
86
|
+
const timer = setTimeout(() => controller.abort(new TimeoutError(`OpenSearch probe timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
87
|
+
const active = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
88
|
+
try {
|
|
89
|
+
const request = this.client.ping({}, { requestTimeout: timeoutMs });
|
|
90
|
+
const result = await awaitWithSignal(request, active, () => request.abort?.());
|
|
91
|
+
log(`OpenSearch probe ok target=${target} elapsed_ms=${Date.now() - started}`);
|
|
92
|
+
return result;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
log(`OpenSearch probe failed target=${target} elapsed_ms=${Date.now() - started} ${describeOpenSearchError(error)}`);
|
|
95
|
+
throw error;
|
|
96
|
+
} finally {
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async close() {
|
|
101
|
+
const controller = new AbortController;
|
|
102
|
+
const timer = setTimeout(() => controller.abort(new TimeoutError("OpenSearch close timed out after 2000ms")), 2000);
|
|
103
|
+
try {
|
|
104
|
+
await awaitWithSignal(this.client.close(), controller.signal);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
this.options.onDiagnostic?.(`OpenSearch close failed ${describeOpenSearchError(error)}`);
|
|
107
|
+
throw error;
|
|
108
|
+
} finally {
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function openSearchRetryReason(error) {
|
|
114
|
+
if (isConnectionNetworkError(error))
|
|
115
|
+
return String(error.code);
|
|
116
|
+
if (error instanceof errors.TimeoutError)
|
|
117
|
+
return "ETIMEDOUT";
|
|
118
|
+
if (error instanceof errors.ConnectionError)
|
|
119
|
+
return error.name;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
class OpenSearchClient {
|
|
124
|
+
source;
|
|
125
|
+
lifecycle;
|
|
126
|
+
#controller = new AbortController;
|
|
127
|
+
signal;
|
|
128
|
+
#initialization;
|
|
129
|
+
#disposal;
|
|
130
|
+
#engine;
|
|
131
|
+
#operations = new Set;
|
|
132
|
+
constructor(source, lifecycle = {}) {
|
|
133
|
+
this.source = source;
|
|
134
|
+
this.lifecycle = lifecycle;
|
|
135
|
+
this.signal = lifecycle.signal ? AbortSignal.any([lifecycle.signal, this.#controller.signal]) : this.#controller.signal;
|
|
136
|
+
}
|
|
137
|
+
initialize() {
|
|
138
|
+
this.signal.throwIfAborted();
|
|
139
|
+
return this.#initialization ??= (async () => {
|
|
140
|
+
const target = await awaitWithSignal(this.source.resolve(), this.signal);
|
|
141
|
+
this.signal.throwIfAborted();
|
|
142
|
+
const original = new URL(target.node);
|
|
143
|
+
try {
|
|
144
|
+
this.#engine = await acquireConnection(this.source.transports, {
|
|
145
|
+
name: "OpenSearch connection",
|
|
146
|
+
signal: this.signal,
|
|
147
|
+
connectTimeoutMs: target.connectTimeoutMs ?? target.requestTimeoutMs ?? 1e4,
|
|
148
|
+
acquireTimeoutMs: target.acquireTimeoutMs,
|
|
149
|
+
onRoute: this.lifecycle.onRoute,
|
|
150
|
+
open: async (transport, attempt) => {
|
|
151
|
+
if (transport.kind !== "tcp")
|
|
152
|
+
throw new Error("OpenSearch requires a TCP transport");
|
|
153
|
+
this.lifecycle.onDiagnostic?.(`OpenSearch route start transport=${transport.name} target=${original.protocol}//${original.host} timeout_ms=${attempt.remainingMs()}`);
|
|
154
|
+
const endpoint = await transport.connect({
|
|
155
|
+
host: original.hostname,
|
|
156
|
+
port: Number(original.port || (original.protocol === "https:" ? 443 : 80)),
|
|
157
|
+
servername: target.servername ?? original.hostname
|
|
158
|
+
});
|
|
159
|
+
attempt.signal.throwIfAborted();
|
|
160
|
+
const url = new URL(original);
|
|
161
|
+
url.hostname = endpoint.host;
|
|
162
|
+
url.port = String(endpoint.port);
|
|
163
|
+
const engine = new OpenSearchEngine({ ...target, onDiagnostic: this.lifecycle.onDiagnostic, node: url.toString(), servername: endpoint.servername ?? target.servername ?? original.hostname });
|
|
164
|
+
try {
|
|
165
|
+
await engine.ping(attempt.signal, attempt.remainingMs());
|
|
166
|
+
return { connection: engine, transport: transport.name };
|
|
167
|
+
} catch (error) {
|
|
168
|
+
await engine.close();
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
close: (engine) => engine.close(),
|
|
173
|
+
retryReason: openSearchRetryReason
|
|
174
|
+
});
|
|
175
|
+
} catch (error) {
|
|
176
|
+
this.lifecycle.onDiagnostic?.(`OpenSearch connect failed ${describeOpenSearchError(error)}`);
|
|
177
|
+
this.signal.throwIfAborted();
|
|
178
|
+
if (error instanceof ToolboxError && error.kind === "timeout")
|
|
179
|
+
throw error;
|
|
180
|
+
const reason = openSearchRetryReason(error);
|
|
181
|
+
if (reason === undefined)
|
|
182
|
+
throw error;
|
|
183
|
+
throw new ToolboxError(`OpenSearch connect failed (${reason})`, { kind: "connection_failed", code: reason, cause: error });
|
|
184
|
+
}
|
|
185
|
+
})();
|
|
186
|
+
}
|
|
187
|
+
#ready() {
|
|
188
|
+
this.signal.throwIfAborted();
|
|
189
|
+
if (!this.#engine)
|
|
190
|
+
throw new Error("OpenSearch client is not initialized");
|
|
191
|
+
return this.#engine;
|
|
192
|
+
}
|
|
193
|
+
#run(operation) {
|
|
194
|
+
const engine = this.#ready();
|
|
195
|
+
const pending = operation(engine);
|
|
196
|
+
this.#operations.add(pending);
|
|
197
|
+
pending.then(() => this.#operations.delete(pending), () => this.#operations.delete(pending));
|
|
198
|
+
return pending;
|
|
199
|
+
}
|
|
200
|
+
count(index, query) {
|
|
201
|
+
return this.#run((engine) => engine.count(index, query));
|
|
202
|
+
}
|
|
203
|
+
search(index, body) {
|
|
204
|
+
return this.#run((engine) => engine.search(index, body));
|
|
205
|
+
}
|
|
206
|
+
request(path, query) {
|
|
207
|
+
return this.#run((engine) => engine.request(path, query));
|
|
208
|
+
}
|
|
209
|
+
ping() {
|
|
210
|
+
return this.#run((engine) => engine.ping());
|
|
211
|
+
}
|
|
212
|
+
close() {
|
|
213
|
+
return this.dispose();
|
|
214
|
+
}
|
|
215
|
+
dispose() {
|
|
216
|
+
return this.#disposal ??= (async () => {
|
|
217
|
+
this.#controller.abort(new Error("OpenSearch client disposed"));
|
|
218
|
+
await this.#initialization?.catch(() => {});
|
|
219
|
+
await Promise.allSettled(this.#operations);
|
|
220
|
+
await this.#engine?.close();
|
|
221
|
+
})();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async function openOpenSearch(source, lifecycle = {}) {
|
|
225
|
+
const client = new OpenSearchClient(source, lifecycle);
|
|
226
|
+
try {
|
|
227
|
+
await client.initialize();
|
|
228
|
+
} catch (error) {
|
|
229
|
+
await client.dispose();
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
lifecycle.onDispose?.(() => client.dispose());
|
|
233
|
+
return client;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export { describeOpenSearchError, isOpenSearchReadApi, OpenSearchEngine, OpenSearchClient, openOpenSearch };
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildKubectlArgs
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-rq5fc007.js";
|
|
4
4
|
import {
|
|
5
5
|
spawnProcess
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-9shz0bw1.js";
|
|
7
7
|
|
|
8
8
|
// src/kubernetes/port-forward.ts
|
|
9
9
|
async function startPortForward(opts) {
|
|
10
|
+
opts.signal?.throwIfAborted();
|
|
10
11
|
const target = opts.target ?? (opts.service ? { kind: "service", name: opts.service } : undefined);
|
|
11
12
|
if (!target)
|
|
12
13
|
return { ok: false, reason: "port-forward 缺少 Service/Pod 目标" };
|
|
@@ -24,8 +25,51 @@ async function startPortForward(opts) {
|
|
|
24
25
|
return { ok: false, reason: err instanceof Error ? err.message : String(err) };
|
|
25
26
|
}
|
|
26
27
|
const timeoutMs = opts.timeoutMs ?? 15000;
|
|
27
|
-
const
|
|
28
|
+
const log = opts.onDiagnostic ?? (() => {});
|
|
29
|
+
const started = Date.now();
|
|
30
|
+
log(`port-forward start target=${target.kind}/${target.name} namespace=${opts.namespace ?? "default"} remote_port=${opts.remotePort} timeout_ms=${timeoutMs}`);
|
|
31
|
+
let stderr = "";
|
|
32
|
+
let loggedBytes = 0;
|
|
33
|
+
const stderrReader = proc.stderr.getReader();
|
|
34
|
+
const stderrDone = (async () => {
|
|
35
|
+
const decoder2 = new TextDecoder;
|
|
36
|
+
for (;; ) {
|
|
37
|
+
const chunk = await stderrReader.read();
|
|
38
|
+
if (chunk.done)
|
|
39
|
+
break;
|
|
40
|
+
const text = decoder2.decode(chunk.value, { stream: true }).replace(/(https?:\/\/)[^\s/@]+:[^\s/@]+@/g, "$1[redacted]@").replace(/([?&](?:token|password|secret|key)=)[^&\s]+/gi, "$1[redacted]").replace(/((?:authorization|password|token)[:=]\s*)[^\r\n]+/gi, "$1[redacted]");
|
|
41
|
+
stderr = (stderr + text).slice(-8192);
|
|
42
|
+
if (loggedBytes < 8192) {
|
|
43
|
+
const diagnostic = text.slice(0, 8192 - loggedBytes);
|
|
44
|
+
loggedBytes += diagnostic.length;
|
|
45
|
+
log(`port-forward stderr: ${diagnostic.trim()}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
})().catch(() => {});
|
|
49
|
+
let stopped = false;
|
|
50
|
+
let cancelWait;
|
|
51
|
+
const cancelled = new Promise((resolve) => {
|
|
52
|
+
cancelWait = () => resolve("cancelled");
|
|
53
|
+
});
|
|
54
|
+
const abort = () => {
|
|
55
|
+
log(`port-forward cancelled elapsed_ms=${Date.now() - started}`);
|
|
56
|
+
stop();
|
|
57
|
+
cancelWait();
|
|
58
|
+
};
|
|
59
|
+
const stop = () => {
|
|
60
|
+
if (stopped)
|
|
61
|
+
return;
|
|
62
|
+
stopped = true;
|
|
63
|
+
opts.signal?.removeEventListener("abort", abort);
|
|
64
|
+
proc.kill("SIGKILL");
|
|
65
|
+
stderrReader.cancel().catch(() => {});
|
|
66
|
+
reader.cancel().catch(() => {});
|
|
67
|
+
};
|
|
28
68
|
const reader = proc.stdout.getReader();
|
|
69
|
+
opts.signal?.addEventListener("abort", abort, { once: true });
|
|
70
|
+
if (opts.signal?.aborted)
|
|
71
|
+
abort();
|
|
72
|
+
proc.exited.then(() => opts.signal?.removeEventListener("abort", abort), () => opts.signal?.removeEventListener("abort", abort));
|
|
29
73
|
const decoder = new TextDecoder;
|
|
30
74
|
const deadline = Date.now() + timeoutMs;
|
|
31
75
|
let out = "";
|
|
@@ -36,6 +80,7 @@ async function startPortForward(opts) {
|
|
|
36
80
|
let timer;
|
|
37
81
|
const chunk = await Promise.race([
|
|
38
82
|
reader.read(),
|
|
83
|
+
cancelled,
|
|
39
84
|
new Promise((resolve) => {
|
|
40
85
|
timer = setTimeout(() => resolve("timeout"), remain);
|
|
41
86
|
}),
|
|
@@ -43,10 +88,13 @@ async function startPortForward(opts) {
|
|
|
43
88
|
]).finally(() => {
|
|
44
89
|
clearTimeout(timer);
|
|
45
90
|
});
|
|
91
|
+
opts.signal?.throwIfAborted();
|
|
92
|
+
if (chunk === "cancelled")
|
|
93
|
+
throw opts.signal?.reason;
|
|
46
94
|
if (chunk === "timeout")
|
|
47
95
|
break;
|
|
48
96
|
if (typeof chunk === "object" && "error" in chunk) {
|
|
49
|
-
|
|
97
|
+
stop();
|
|
50
98
|
return { ok: false, reason: chunk.error instanceof Error ? chunk.error.message : String(chunk.error) };
|
|
51
99
|
}
|
|
52
100
|
if (chunk === "exited" || chunk.done)
|
|
@@ -54,16 +102,18 @@ async function startPortForward(opts) {
|
|
|
54
102
|
out += decoder.decode(chunk.value);
|
|
55
103
|
const m = out.match(/Forwarding from 127\.0\.0\.1:(\d+)/);
|
|
56
104
|
if (m) {
|
|
105
|
+
log(`port-forward listening local=127.0.0.1:${m[1]} elapsed_ms=${Date.now() - started}`);
|
|
57
106
|
return {
|
|
58
107
|
ok: true,
|
|
59
|
-
value: { target, localPort: Number(m[1]), command: argv, stop
|
|
108
|
+
value: { target, localPort: Number(m[1]), command: argv, stop }
|
|
60
109
|
};
|
|
61
110
|
}
|
|
62
111
|
}
|
|
63
|
-
|
|
64
|
-
|
|
112
|
+
await Promise.resolve();
|
|
113
|
+
stop();
|
|
65
114
|
const reason = stderr.trim().split(`
|
|
66
115
|
`)[0] || `port-forward 未就绪(${timeoutMs}ms 超时)`;
|
|
116
|
+
log(`port-forward failed elapsed_ms=${Date.now() - started} reason=${reason}`);
|
|
67
117
|
return { ok: false, reason };
|
|
68
118
|
}
|
|
69
119
|
|
|
@@ -15,6 +15,12 @@ class KubernetesError extends ToolboxError {
|
|
|
15
15
|
|
|
16
16
|
class FileError extends ToolboxError {
|
|
17
17
|
}
|
|
18
|
+
|
|
19
|
+
class TimeoutError extends ToolboxError {
|
|
20
|
+
constructor(message, options) {
|
|
21
|
+
super(message, { kind: "timeout", code: "ETIMEDOUT", cause: options?.cause });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
18
24
|
function kubernetesHttpError(status) {
|
|
19
25
|
const kind = {
|
|
20
26
|
401: "authentication_failed",
|
|
@@ -41,4 +47,4 @@ function kubernetesCommandError(result) {
|
|
|
41
47
|
return status ? kubernetesHttpError(status) : new KubernetesError("Kubernetes resource request failed", { kind: "operation_failed" });
|
|
42
48
|
}
|
|
43
49
|
|
|
44
|
-
export { ToolboxError, KubernetesError, FileError, kubernetesHttpError, kubernetesCommandError };
|
|
50
|
+
export { ToolboxError, KubernetesError, FileError, TimeoutError, kubernetesHttpError, kubernetesCommandError };
|
|
@@ -5,13 +5,18 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
queryReadonlySession,
|
|
7
7
|
validateQueryLimits
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-db4an9td.js";
|
|
9
9
|
import {
|
|
10
|
+
connectMysql,
|
|
10
11
|
destroyMysqlConnection
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-835fssgz.js";
|
|
13
|
+
import {
|
|
14
|
+
acquireConnection,
|
|
15
|
+
awaitWithSignal
|
|
16
|
+
} from "./chunk-gm4swndx.js";
|
|
12
17
|
import {
|
|
13
18
|
ToolboxError
|
|
14
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-rkt41za2.js";
|
|
15
20
|
import {
|
|
16
21
|
ConcurrencyPool
|
|
17
22
|
} from "./chunk-v3hxvnrc.js";
|
|
@@ -20,7 +25,6 @@ import {
|
|
|
20
25
|
} from "./chunk-h8fwx0k7.js";
|
|
21
26
|
|
|
22
27
|
// src/mysql/client.ts
|
|
23
|
-
import { createConnection } from "mysql2/promise";
|
|
24
28
|
function mysqlOperationError(error, phase) {
|
|
25
29
|
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "PROTOCOL_CONNECTION_LOST")
|
|
26
30
|
return error;
|
|
@@ -37,7 +41,7 @@ class MysqlDatabase {
|
|
|
37
41
|
connect;
|
|
38
42
|
#queries = new ConcurrencyPool(1);
|
|
39
43
|
#connections = new Map;
|
|
40
|
-
constructor(transports, options, connect =
|
|
44
|
+
constructor(transports, options, connect = connectMysql) {
|
|
41
45
|
this.transports = transports;
|
|
42
46
|
this.options = options;
|
|
43
47
|
this.connect = connect;
|
|
@@ -120,39 +124,40 @@ class MysqlDatabase {
|
|
|
120
124
|
return rows;
|
|
121
125
|
});
|
|
122
126
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
127
|
+
#open(target) {
|
|
128
|
+
return acquireConnection(this.transports, {
|
|
129
|
+
name: "MySQL connection",
|
|
130
|
+
...this.options,
|
|
131
|
+
open: async (transport, attempt) => {
|
|
132
|
+
if (transport.kind === "python")
|
|
133
|
+
return { connection: { kind: "python", transport }, transport: transport.name };
|
|
134
|
+
try {
|
|
135
|
+
const endpoint = await transport.connect(target);
|
|
136
|
+
const connection = await this.connect({
|
|
137
|
+
host: endpoint.host,
|
|
138
|
+
port: endpoint.port,
|
|
139
|
+
user: target.user,
|
|
140
|
+
password: target.password,
|
|
141
|
+
database: target.database,
|
|
142
|
+
connectTimeout: attempt.remainingMs(),
|
|
143
|
+
dateStrings: true,
|
|
144
|
+
supportBigNumbers: true,
|
|
145
|
+
bigNumberStrings: true
|
|
146
|
+
}, attempt.signal);
|
|
147
|
+
return {
|
|
148
|
+
connection: { kind: "tcp", connection },
|
|
149
|
+
transport: endpoint.host === target.host && endpoint.port === target.port ? "direct" : transport.name
|
|
150
|
+
};
|
|
151
|
+
} catch (error) {
|
|
152
|
+
throw mysqlOperationError(error, "connect");
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
close: (session) => {
|
|
156
|
+
if (session.kind === "tcp")
|
|
157
|
+
destroyMysqlConnection(session.connection);
|
|
158
|
+
},
|
|
159
|
+
retryReason: (error) => isConnectionNetworkError(error) || error instanceof ToolboxError && error.kind === "connection_lost" ? String(error.code) : undefined
|
|
160
|
+
});
|
|
156
161
|
}
|
|
157
162
|
async queryOne(target, sql, values) {
|
|
158
163
|
return (await this.query(target, sql, values))[0];
|
|
@@ -186,7 +191,7 @@ class MysqlClient {
|
|
|
186
191
|
initialize() {
|
|
187
192
|
this.signal.throwIfAborted();
|
|
188
193
|
return this.#initialization ??= (async () => {
|
|
189
|
-
this.#target = await this.source.resolve();
|
|
194
|
+
this.#target = await awaitWithSignal(this.source.resolve(), this.signal);
|
|
190
195
|
this.signal.throwIfAborted();
|
|
191
196
|
this.#database = new MysqlDatabase(this.source.transports, { ...this.options, signal: this.signal });
|
|
192
197
|
await this.#database.initialize(this.#target);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resourcePath
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-dpcge1xt.js";
|
|
4
4
|
import {
|
|
5
5
|
clientKey
|
|
6
6
|
} from "./chunk-a9htb6cr.js";
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
KubernetesError,
|
|
9
9
|
ToolboxError,
|
|
10
10
|
kubernetesHttpError
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-rkt41za2.js";
|
|
12
12
|
import {
|
|
13
13
|
ConcurrencyPool
|
|
14
14
|
} from "./chunk-v3hxvnrc.js";
|
|
@@ -53,11 +53,13 @@ function findPodsForService(services, pods, serviceName, namespace) {
|
|
|
53
53
|
return [];
|
|
54
54
|
return pods.filter((pod) => pod.namespace === namespace && pod.phase === "Running" && selector.every(([name, value]) => pod.labels[name] === value)).sort((left, right) => left.name.localeCompare(right.name));
|
|
55
55
|
}
|
|
56
|
-
async function listServiceNetwork(executor, namespace) {
|
|
56
|
+
async function listServiceNetwork(executor, namespace, signal) {
|
|
57
|
+
signal?.throwIfAborted();
|
|
57
58
|
const [serviceResult, podResult] = await Promise.all([
|
|
58
|
-
executor.run(["get", "services", "-o", "json"], { timeoutMs: 20000 }),
|
|
59
|
-
executor.run(["get", "pods", "-o", "json"], { timeoutMs: 20000 })
|
|
59
|
+
executor.run(["get", "services", "-o", "json"], { timeoutMs: 20000, signal }),
|
|
60
|
+
executor.run(["get", "pods", "-o", "json"], { timeoutMs: 20000, signal })
|
|
60
61
|
]);
|
|
62
|
+
signal?.throwIfAborted();
|
|
61
63
|
if (!serviceResult.ok) {
|
|
62
64
|
throw new Error(`读取 Service 失败:${serviceResult.stderr.trim() || `exit=${serviceResult.exitCode}`}`);
|
|
63
65
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ClientLifecycle } from "../datasource";
|
|
2
|
+
/** Stop waiting without leaving rejected preparation promises unobserved. */
|
|
3
|
+
export declare function awaitWithSignal<T>(pending: Promise<T>, signal: AbortSignal, cancel?: () => void): Promise<T>;
|
|
4
|
+
export interface ConnectionAttempt {
|
|
5
|
+
readonly signal: AbortSignal;
|
|
6
|
+
/** Remaining mapping + handshake budget, not a fresh timeout for each phase. */
|
|
7
|
+
remainingMs(): number;
|
|
8
|
+
}
|
|
9
|
+
interface Acquisition<R, C> {
|
|
10
|
+
name: string;
|
|
11
|
+
connectTimeoutMs: number;
|
|
12
|
+
acquireTimeoutMs?: number;
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
open(route: R, attempt: ConnectionAttempt): Promise<{
|
|
15
|
+
connection: C;
|
|
16
|
+
transport: string;
|
|
17
|
+
}>;
|
|
18
|
+
close(connection: C): void | Promise<void>;
|
|
19
|
+
/** Undefined means terminal; authentication and protocol errors must not change route. */
|
|
20
|
+
retryReason(error: unknown): string | undefined;
|
|
21
|
+
onRoute?: ClientLifecycle["onRoute"];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* @spec One acquisition owns ordered attempts and a deadline covering mapping and handshake.
|
|
25
|
+
* @spec Cancellation keeps its original reason; a late successful connection is closed, never published.
|
|
26
|
+
* @rule Only connection preparation enters this function. Commands and queries must never be replayed.
|
|
27
|
+
* @why Protocol adapters own partial handles and error classification; all protocols share the same lifecycle.
|
|
28
|
+
* @see {@link component://docs/toolbox.md}
|
|
29
|
+
* @case id=connection_mapping_cancel,desc=`Cancel while mapping a route`,expect=`Acquisition exits without a late handshake`
|
|
30
|
+
*/
|
|
31
|
+
export declare function acquireConnection<R extends {
|
|
32
|
+
readonly name: string;
|
|
33
|
+
}, C>(routes: readonly R[], options: Acquisition<R, C>): Promise<C>;
|
|
34
|
+
export {};
|
package/dist/datasource.js
CHANGED
package/dist/errors.d.ts
CHANGED
|
@@ -13,6 +13,10 @@ export declare class KubernetesError extends ToolboxError {
|
|
|
13
13
|
}
|
|
14
14
|
export declare class FileError extends ToolboxError {
|
|
15
15
|
}
|
|
16
|
+
/** A local deadline expiry, distinct from a connection failure reported by a driver. */
|
|
17
|
+
export declare class TimeoutError extends ToolboxError {
|
|
18
|
+
constructor(message: string, options?: ErrorOptions);
|
|
19
|
+
}
|
|
16
20
|
export declare function kubernetesHttpError(status: number): KubernetesError;
|
|
17
21
|
/**
|
|
18
22
|
* Normalize a failed kubectl resource request at the transport boundary.
|
package/dist/errors.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FileError,
|
|
3
3
|
KubernetesError,
|
|
4
|
+
TimeoutError,
|
|
4
5
|
ToolboxError,
|
|
5
6
|
kubernetesCommandError,
|
|
6
7
|
kubernetesHttpError
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-rkt41za2.js";
|
|
8
9
|
export {
|
|
9
10
|
kubernetesHttpError,
|
|
10
11
|
kubernetesCommandError,
|
|
11
12
|
ToolboxError,
|
|
13
|
+
TimeoutError,
|
|
12
14
|
KubernetesError,
|
|
13
15
|
FileError
|
|
14
16
|
};
|
package/dist/file/client.js
CHANGED